tasks/static/js/index.js
Padreug 48a63c0338 add API routes, admin UI, and SPA bootstrap
views_api wires the full task lifecycle: create / update / delete /
list (per-wallet, public, admin-all) and the completion flow (claim /
start / complete via POST, unclaim via DELETE, plus a "mine" lookup
for the current user's claim on a task or specific occurrence).

Auth model: tasks are owned by an LNbits wallet but signed with the
wallet owner's account.pubkey — _wallet_pubkey resolves that pubkey at
create time and refuses to create tasks for accounts that haven't
generated a keypair yet, so we never publish a task we can't sign.

Completions optimistically insert with a local hash, publish to Nostr,
then re-insert under the actual event id so a later delete can find it.

Static SPA: Quasar-UMD index.vue / index.js mirroring the events
extension layout — wallet picker, task table, create/edit dialog with
optional daily/weekly recurrence, plus an admin-only public_listing
toggle. /tasks/:id and display.vue intentionally left out until the
public task page lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 11:43:59 +02:00

192 lines
5 KiB
JavaScript

window.PageTasks = {
template: '#page-tasks',
data() {
return {
tasks: [],
isAdmin: false,
settings: {
public_listing: false
},
tasksTable: {
columns: [
{name: 'title', align: 'left', label: 'Title', field: 'title'},
{
name: 'start_date',
align: 'left',
label: 'Start',
field: 'start_date'
},
{
name: 'end_date',
align: 'left',
label: 'End',
field: 'end_date'
},
{
name: 'recurrence',
align: 'left',
label: 'Recurrence',
field: row => (row.recurrence ? row.recurrence.frequency : '')
},
{name: 'status', align: 'left', label: 'Status', field: 'status'},
{name: 'location', align: 'left', label: 'Location', field: 'location'}
],
pagination: {
rowsPerPage: 10
}
},
taskDialog: {
show: false,
data: {}
}
}
},
computed: {
canSubmit() {
return (
this.taskDialog.data.title &&
this.taskDialog.data.start_date &&
this.taskDialog.data.wallet
)
}
},
methods: {
openTaskDialog() {
this.taskDialog.data = {
title: '',
start_date: '',
end_date: '',
description: '',
location: '',
wallet: this.g.user.wallets[0]?.id,
recurrence_frequency: '',
recurrence_day_of_week: '',
recurrence_end_date: ''
}
this.taskDialog.show = true
},
editTask(row) {
this.taskDialog.data = {
id: row.id,
wallet: row.wallet,
title: row.title,
start_date: row.start_date,
end_date: row.end_date || '',
description: row.description || '',
location: row.location || '',
recurrence_frequency: row.recurrence?.frequency || '',
recurrence_day_of_week: row.recurrence?.day_of_week || '',
recurrence_end_date: row.recurrence?.end_date || ''
}
this.taskDialog.show = true
},
_buildPayload(d) {
const payload = {
wallet: d.wallet,
title: d.title,
start_date: d.start_date,
end_date: d.end_date || null,
description: d.description || '',
location: d.location || null
}
if (d.recurrence_frequency) {
payload.recurrence = {
frequency: d.recurrence_frequency,
day_of_week: d.recurrence_day_of_week || null,
end_date: d.recurrence_end_date || null
}
}
return payload
},
async submitTask() {
const d = this.taskDialog.data
const wallet = _.findWhere(this.g.user.wallets, {id: d.wallet})
if (!wallet) return
const payload = this._buildPayload(d)
try {
if (d.id) {
await LNbits.api.request(
'PUT',
'/tasks/api/v1/tasks/' + d.id,
wallet.adminkey,
payload
)
} else {
await LNbits.api.request(
'POST',
'/tasks/api/v1/tasks',
wallet.adminkey,
payload
)
}
this.taskDialog.show = false
this.getTasks()
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
async deleteTask(taskId) {
LNbits.utils
.confirmDialog('Delete this task?')
.onOk(async () => {
const task = _.findWhere(this.tasks, {id: taskId})
const wallet = _.findWhere(this.g.user.wallets, {id: task.wallet})
if (!wallet) return
try {
await LNbits.api.request(
'DELETE',
'/tasks/api/v1/tasks/' + taskId,
wallet.adminkey
)
this.getTasks()
} catch (error) {
LNbits.utils.notifyApiError(error)
}
})
},
async getTasks() {
try {
const wallet = this.g.user.wallets[0]
if (!wallet) return
const {data} = await LNbits.api.request(
'GET',
'/tasks/api/v1/tasks?all_wallets=true',
wallet.inkey
)
this.tasks = data
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
async getSettings() {
if (!this.isAdmin) return
try {
const {data} = await LNbits.api.request(
'GET',
'/tasks/api/v1/tasks/settings',
this.g.user.wallets[0]?.adminkey
)
this.settings = data
} catch (_) {
// Non-admin or settings not seeded — silent.
}
},
async saveSettings() {
try {
await LNbits.api.request(
'PUT',
'/tasks/api/v1/tasks/settings',
this.g.user.wallets[0]?.adminkey,
this.settings
)
} catch (error) {
LNbits.utils.notifyApiError(error)
}
}
},
created() {
this.isAdmin = !!(window.user && window.user.admin)
this.getTasks()
this.getSettings()
}
}