[feat] UI for user notifications (#3267)

This commit is contained in:
Vlad Stan 2025-07-15 09:04:14 +03:00 committed by dni ⚡
parent 5e9919c5c5
commit 094a9fc1a1
No known key found for this signature in database
GPG key ID: D1F416F29AD26E87
7 changed files with 142 additions and 6 deletions

View file

@ -25,6 +25,9 @@ class UserExtra(BaseModel):
first_name: str | None = None first_name: str | None = None
last_name: str | None = None last_name: str | None = None
display_name: str | None = None display_name: str | None = None
nostr_notification_identifiers: list[str] = []
telegram_chat_id: str | None = None
notifications_excluded_wallets: list[str] = []
picture: str | None = None picture: str | None = None
# Auth provider, possible values: # Auth provider, possible values:
# - "env": the user was created automatically by the system # - "env": the user was created automatically by the system

View file

@ -20,6 +20,11 @@
:label="$t('look_and_feel')" :label="$t('look_and_feel')"
@update="val => tab = val.name" @update="val => tab = val.name"
></q-tab> ></q-tab>
<q-tab
name="notifications"
:label="$t('notifications')"
@update="val => tab = val.name"
></q-tab>
<q-tab <q-tab
name="api_acls" name="api_acls"
:label="$t('access_control_list')" :label="$t('access_control_list')"
@ -566,6 +571,86 @@
</q-btn> </q-btn>
</div> </div>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="notifications">
<q-card-section>
<div class="row q-mb-md">
<div class="col-4">
<span
v-text="$t('notifications_nostr_identifiers')"
></span>
</div>
<div class="col-8">
<q-input
filled
dense
v-model="notifications.nostr.identifier"
:hint="$t('notifications_nostr_identifiers_desc')"
@keydown.enter="addNostrNotificationIdentifier"
>
<q-btn
@click="addNostrNotificationIdentifier()"
dense
flat
icon="add"
></q-btn>
</q-input>
<div>
<q-chip
v-for="identifier in user.extra.nostr_notification_identifiers"
:key="identifier"
removable
@remove="removeNostrNotificationIdentifier(identifier)"
color="primary"
text-color="white"
><span class="ellipsis" v-text="identifier"></span
></q-chip>
</div>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('notifications_chat_id')"></span>
</div>
<div class="col-8">
<q-input
filled
dense
v-model="user.extra.telegram_chat_id"
:hint="$t('notifications_chat_id_desc')"
/>
</div>
</div>
<div class="row q-mb-md">
<div class="col-4">
<span v-text="$t('exclude_wallets')"></span>
</div>
<div class="col-8">
<q-select
filled
dense
emit-value
map-options
multiple
v-model="user.extra.notifications_excluded_wallets"
:options="g.user.walletOptions"
:label="$t('exclude_wallets')"
:hint="$t('notifications_excluded_wallets_desc')"
class="q-mt-sm"
>
</q-select>
</div>
</div>
</q-card-section>
<q-card-section>
<q-separator></q-separator>
<div class="row q-mb-md q-mt-md">
<q-btn @click="updateAccount" unelevated color="primary">
<span v-text="$t('update_account')"></span>
</q-btn>
</div>
</q-card-section>
</q-tab-panel>
<q-tab-panel name="api_acls"> <q-tab-panel name="api_acls">
<div class="row q-mb-md"> <div class="row q-mb-md">
<q-badge v-if="user.admin"> <q-badge v-if="user.admin">

View file

@ -206,11 +206,15 @@ async def account(
request: Request, request: Request,
user: User = Depends(check_user_exists), user: User = Depends(check_user_exists),
): ):
nostr_configured = settings.is_nostr_notifications_configured()
telegram_configured = settings.is_telegram_notifications_configured()
return template_renderer().TemplateResponse( return template_renderer().TemplateResponse(
request, request,
"core/account.html", "core/account.html",
{ {
"user": user.json(), "user": user.json(),
"nostr_configured": nostr_configured,
"telegram_configured": telegram_configured,
"ajax": _is_ajax_request(request), "ajax": _is_ajax_request(request),
}, },
) )
@ -361,7 +365,6 @@ async def node_public(request: Request):
request, request,
"node/public.html", "node/public.html",
{ {
"settings": settings.dict(),
"balance": balance, "balance": balance,
}, },
) )
@ -398,7 +401,6 @@ async def users_index(request: Request, user: User = Depends(check_admin)):
{ {
"request": request, "request": request,
"user": user.json(), "user": user.json(),
"settings": settings.dict(),
"currencies": list(currencies.keys()), "currencies": list(currencies.keys()),
"ajax": _is_ajax_request(request), "ajax": _is_ajax_request(request),
}, },

View file

@ -432,6 +432,18 @@ class NotificationsSettings(LNbitsSettings):
default=1_000_000, ge=0 default=1_000_000, ge=0
) )
def is_nostr_notifications_configured(self) -> bool:
return (
self.lnbits_nostr_notifications_enabled
and self.lnbits_nostr_notifications_private_key is not None
)
def is_telegram_notifications_configured(self) -> bool:
return (
self.lnbits_telegram_notifications_enabled
and self.lnbits_telegram_notifications_access_token is not None
)
class FakeWalletFundingSource(LNbitsSettings): class FakeWalletFundingSource(LNbitsSettings):
fake_wallet_secret: str = Field(default="ToTheMoon1") fake_wallet_secret: str = Field(default="ToTheMoon1")

File diff suppressed because one or more lines are too long

View file

@ -52,6 +52,7 @@ window.localisation.en = {
wallet: 'Wallet: ', wallet: 'Wallet: ',
wallet_name: 'Wallet name', wallet_name: 'Wallet name',
wallets: 'Wallets', wallets: 'Wallets',
exclude_wallets: 'Exclude Wallets',
add_wallet: 'Add wallet', add_wallet: 'Add wallet',
add_new_wallet: 'Add a new wallet', add_new_wallet: 'Add a new wallet',
pin_wallet: 'Pin wallet', pin_wallet: 'Pin wallet',
@ -238,9 +239,10 @@ window.localisation.en = {
notifications_enable_telegram_desc: 'Send notfications over Telegram', notifications_enable_telegram_desc: 'Send notfications over Telegram',
notifications_telegram_access_token: 'Access Token', notifications_telegram_access_token: 'Access Token',
notifications_telegram_access_token_desc: 'Access token for the bot', notifications_telegram_access_token_desc: 'Access token for the bot',
notifications_chat_id: 'Chat ID', notifications_chat_id: 'Telegram Chat ID',
notifications_chat_id_desc: 'Chat ID to send the notifications to', notifications_chat_id_desc: 'Telegram Chat ID to send the notifications to',
notifications_excluded_wallets_desc:
'Do not send notifications for these wallets',
notifications_email_config: 'Email Configuration', notifications_email_config: 'Email Configuration',
notifications_enable_email: 'Enable Email', notifications_enable_email: 'Enable Email',
notifications_enable_email_desc: 'Send notifications over email', notifications_enable_email_desc: 'Send notifications over email',

View file

@ -80,6 +80,11 @@ window.AccountPageLogic = {
token_id_list: [], token_id_list: [],
allRead: false, allRead: false,
allWrite: false allWrite: false
},
notifications: {
nostr: {
identifier: ''
}
} }
} }
}, },
@ -394,8 +399,35 @@ window.AccountPageLogic = {
} finally { } finally {
this.apiAcl.password = '' this.apiAcl.password = ''
} }
},
addNostrNotificationIdentifier() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(this.notifications.nostr.identifier)) {
Quasar.Notify.create({
type: 'warning',
message: 'Invalid email format.'
})
return
}
const identifier = this.user.extra.nostr_notification_identifiers.find(
i => i === this.notifications.nostr.identifier
)
if (identifier) {
return
}
this.user.extra.nostr_notification_identifiers.push(
this.notifications.nostr.identifier
)
this.notifications.nostr.identifier = ''
},
removeNostrNotificationIdentifier(identifier) {
this.user.extra.nostr_notification_identifiers =
this.user.extra.nostr_notification_identifiers.filter(
i => i !== identifier
)
} }
}, },
async created() { async created() {
try { try {
const {data} = await LNbits.api.getAuthenticatedUser() const {data} = await LNbits.api.getAuthenticatedUser()