Show email registration errors in UI
- Add error field to EmailNotificationState type - Set user-friendly error messages in MailNotifications.register() for: missing signer, missing server config, NIP-98 auth failure, network errors, and server errors - Clear error on successful registration - Show inline error banner in the email settings card - Return early on registration failure (like push does) - Clear error when user edits email/frequency or clicks reset
This commit is contained in:
parent
fee6f565ff
commit
5cbc1e459c
3 changed files with 57 additions and 12 deletions
|
|
@ -16,21 +16,46 @@ export class MailNotifications {
|
||||||
const $pubkey = User.require($app).pubkey
|
const $pubkey = User.require($app).pubkey
|
||||||
const $signer = $app.signer
|
const $signer = $app.signer
|
||||||
|
|
||||||
if (!$signer) throw new Error("No signer available")
|
if (!$signer) {
|
||||||
|
emailNotificationState.set({error: "No signer available. Check your browser extension."})
|
||||||
|
throw new Error("No signer available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EMAIL_NOTIFICATION_SERVER) {
|
||||||
|
emailNotificationState.set({error: "Email notification server is not configured."})
|
||||||
|
throw new Error("Email notification server not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
let authEvent
|
||||||
|
try {
|
||||||
|
authEvent = await $signer.sign(await makeHttpAuth(EMAIL_NOTIFICATION_SERVER, "POST"))
|
||||||
|
} catch (e) {
|
||||||
|
emailNotificationState.set({error: "Failed to create authentication. Check your signer."})
|
||||||
|
throw new Error("Failed to sign NIP-98 auth")
|
||||||
|
}
|
||||||
|
|
||||||
const authEvent = await $signer.sign(await makeHttpAuth(EMAIL_NOTIFICATION_SERVER, "POST"))
|
|
||||||
const authHeader = makeHttpAuthHeader(authEvent)
|
const authHeader = makeHttpAuthHeader(authEvent)
|
||||||
|
|
||||||
const res = await fetch(buildUrl(EMAIL_NOTIFICATION_SERVER, "subscription", "email"), {
|
let res
|
||||||
method: "POST",
|
try {
|
||||||
body: JSON.stringify({email, frequency, pubkey: $pubkey}),
|
res = await fetch(buildUrl(EMAIL_NOTIFICATION_SERVER, "subscription", "email"), {
|
||||||
headers: {Authorization: authHeader, "Content-Type": "application/json"},
|
method: "POST",
|
||||||
})
|
body: JSON.stringify({email, frequency, pubkey: $pubkey}),
|
||||||
|
headers: {Authorization: authHeader, "Content-Type": "application/json"},
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
emailNotificationState.set({error: `Could not reach email server. Check your connection.`})
|
||||||
|
throw new Error("Network error")
|
||||||
|
}
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`Registration failed: ${await res.text()}`)
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "unknown error")
|
||||||
|
emailNotificationState.set({error: `Registration failed: ${text}`})
|
||||||
|
throw new Error(`Registration failed: ${text}`)
|
||||||
|
}
|
||||||
|
|
||||||
const {key, callback} = await res.json()
|
const {key, callback} = await res.json()
|
||||||
emailNotificationState.set({key, callback, email, frequency})
|
emailNotificationState.set({key, callback, email, frequency, error: undefined})
|
||||||
}
|
}
|
||||||
|
|
||||||
async unregister() {
|
async unregister() {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ export type EmailNotificationState = {
|
||||||
callback?: string
|
callback?: string
|
||||||
email?: string
|
email?: string
|
||||||
frequency?: string
|
frequency?: string
|
||||||
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emailNotificationState = withGetter(writable<EmailNotificationState>({}))
|
export const emailNotificationState = withGetter(writable<EmailNotificationState>({}))
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,17 @@
|
||||||
import {Push} from "@app/push"
|
import {Push} from "@app/push"
|
||||||
import {Mail} from "@app/mail"
|
import {Mail} from "@app/mail"
|
||||||
import {notificationSettings} from "@app/settings"
|
import {notificationSettings} from "@app/settings"
|
||||||
|
import {emailNotificationState} from "@app/push/adapters/common"
|
||||||
|
|
||||||
|
const clearError = () => {
|
||||||
|
if ($emailNotificationState.error) {
|
||||||
|
emailNotificationState.set({...emailNotificationState.get(), error: undefined})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
settings = {...notificationSettings.get()}
|
settings = {...notificationSettings.get()}
|
||||||
|
clearError()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onsubmit = preventDefault(async () => {
|
const onsubmit = preventDefault(async () => {
|
||||||
|
|
@ -52,8 +60,13 @@
|
||||||
await Mail.register(settings.emailAddress, settings.emailFrequency)
|
await Mail.register(settings.emailAddress, settings.emailFrequency)
|
||||||
await Mail.enable()
|
await Mail.enable()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Failed to enable email notifications:", e)
|
settings.email = false
|
||||||
pushToast({theme: "error", message: "Failed to enable email notifications."})
|
await sleep(300)
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: `Failed to enable email notifications.`,
|
||||||
|
action: {message: "Details", onclick: () => emailNotificationState.set({...emailNotificationState.get(), error: undefined})},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else if (!settings.email) {
|
} else if (!settings.email) {
|
||||||
await Mail.disable()
|
await Mail.disable()
|
||||||
|
|
@ -161,6 +174,7 @@
|
||||||
type="email"
|
type="email"
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
bind:value={settings.emailAddress}
|
bind:value={settings.emailAddress}
|
||||||
|
oninput={clearError}
|
||||||
placeholder="you@example.com" />
|
placeholder="you@example.com" />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
|
|
@ -169,13 +183,18 @@
|
||||||
<p>Frequency</p>
|
<p>Frequency</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<select class="select select-bordered" bind:value={settings.emailFrequency}>
|
<select class="select select-bordered" bind:value={settings.emailFrequency} onchange={clearError}>
|
||||||
<option value="daily">Daily</option>
|
<option value="daily">Daily</option>
|
||||||
<option value="weekly">Weekly</option>
|
<option value="weekly">Weekly</option>
|
||||||
</select>
|
</select>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $emailNotificationState.error}
|
||||||
|
<div class="text-sm text-red-500 bg-red-50 rounded-lg p-3">
|
||||||
|
{$emailNotificationState.error}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="card sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
|
<div class="card sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
|
||||||
<Button class="button button-neutral" onclick={reset} disabled={loading}
|
<Button class="button button-neutral" onclick={reset} disabled={loading}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue