306 lines
9.6 KiB
Svelte
306 lines
9.6 KiB
Svelte
<script lang="ts">
|
|
import cx from "classnames"
|
|
import {derived} from "svelte/store"
|
|
import {sleep} from "@welshman/lib"
|
|
import type {Relay} from "@welshman/domain"
|
|
import {Capacitor} from "@capacitor/core"
|
|
import {Badge} from "@capawesome/capacitor-badge"
|
|
import Bell from "@assets/icons/bell.svg?dataurl"
|
|
import {preventDefault} from "@lib/html"
|
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
|
import ToggleInput from "@lib/components/ToggleInput.svelte"
|
|
import Spinner from "@lib/components/Spinner.svelte"
|
|
import Icon from "@lib/components/Icon.svelte"
|
|
import Button from "@lib/components/Button.svelte"
|
|
import PageContent from "@lib/components/PageContent.svelte"
|
|
import {pushToast} from "@app/toast"
|
|
import {pushModal} from "@app/modal"
|
|
import {clearBadges} from "@app/notifications"
|
|
import {Push} from "@app/push"
|
|
import {Mail} from "@app/mail"
|
|
import {notificationSettings, publishSettings, userSettingsValues} from "@app/settings"
|
|
import {emailNotificationState} from "@app/push/adapters/common"
|
|
import {EMAIL_NOTIFICATION_SERVER} from "@app/env"
|
|
import {relays} from "@app/core"
|
|
import {userSpaceUrls} from "@app/rooms"
|
|
import EmailNotificationsInfo from "@app/components/EmailNotificationsInfo.svelte"
|
|
|
|
const clearError = () => {
|
|
if ($emailNotificationState.error) {
|
|
emailNotificationState.set({...emailNotificationState.get(), error: undefined})
|
|
}
|
|
}
|
|
|
|
const buildSettings = () => {
|
|
const local = notificationSettings.get()
|
|
|
|
return {
|
|
push: local.push,
|
|
sound: local.sound,
|
|
badge: local.badge,
|
|
spaces: local.spaces,
|
|
mentions: local.mentions,
|
|
messages: local.messages,
|
|
email: $userSettingsValues.email,
|
|
emailAddress: $userSettingsValues.emailAddress,
|
|
emailFrequency: $userSettingsValues.emailFrequency,
|
|
}
|
|
}
|
|
|
|
const reset = () => {
|
|
settings = buildSettings()
|
|
clearError()
|
|
}
|
|
|
|
const onsubmit = preventDefault(async () => {
|
|
loading = true
|
|
|
|
try {
|
|
if (!settings.badge) {
|
|
clearBadges()
|
|
}
|
|
|
|
let permission = "granted"
|
|
|
|
if (settings.push) {
|
|
permission = await Push.request()
|
|
|
|
if (!permission.startsWith("granted")) {
|
|
await sleep(300)
|
|
|
|
settings.push = false
|
|
|
|
return pushToast({
|
|
theme: "error",
|
|
message: `Failed to request notification permissions (${permission}).`,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Handle email notification registration
|
|
if (settings.email && settings.emailAddress) {
|
|
try {
|
|
await Mail.register(settings.emailAddress, settings.emailFrequency)
|
|
await Mail.enable()
|
|
} catch (e) {
|
|
settings.email = false
|
|
console.error(e)
|
|
await sleep(300)
|
|
return pushToast({
|
|
theme: "error",
|
|
message: `Failed to enable email notifications.`,
|
|
action: {message: "Details", onclick: () => {}},
|
|
})
|
|
}
|
|
} else if (!settings.email) {
|
|
await Mail.disable()
|
|
}
|
|
|
|
// Sync email prefs across devices via AppData
|
|
await publishSettings({
|
|
email: settings.email,
|
|
emailAddress: settings.emailAddress,
|
|
emailFrequency: settings.emailFrequency,
|
|
})
|
|
|
|
notificationSettings.set({
|
|
push: settings.push,
|
|
sound: settings.sound,
|
|
badge: settings.badge,
|
|
spaces: settings.spaces,
|
|
mentions: settings.mentions,
|
|
messages: settings.messages,
|
|
})
|
|
|
|
pushToast({message: "Your settings have been saved!"})
|
|
} finally {
|
|
loading = false
|
|
}
|
|
})
|
|
|
|
// Communities whose relay doesn't advertise NIP-9a won't be included in the email digest.
|
|
// Subscribing to each space's NIP-11 info triggers a fetch for any that hasn't loaded yet.
|
|
const communitiesWithoutEmail = derived(
|
|
userSpaceUrls,
|
|
($urls, set: (value: string[]) => void) => {
|
|
const unsubscribers = $urls.map(url =>
|
|
relays
|
|
.get()
|
|
.one(url)
|
|
.subscribe(() =>
|
|
set(
|
|
$urls
|
|
.map(url => relays.get().get(url))
|
|
.filter((relay): relay is Relay => relay !== undefined && !relay.hasNip("9a"))
|
|
.map(relay => relay.display()),
|
|
),
|
|
),
|
|
)
|
|
|
|
return () =>
|
|
unsubscribers.forEach(unsubscribe => {
|
|
unsubscribe()
|
|
})
|
|
},
|
|
)
|
|
|
|
const showEmailInfo = () => pushModal(EmailNotificationsInfo)
|
|
|
|
let loading = $state(false)
|
|
let settings = $state(buildSettings())
|
|
let settingsHydrated = $state(false)
|
|
|
|
$effect(() => {
|
|
if (settingsHydrated) return
|
|
|
|
const {email, emailAddress} = $userSettingsValues
|
|
if (!email && !emailAddress) return
|
|
|
|
settingsHydrated = true
|
|
settings = buildSettings()
|
|
})
|
|
</script>
|
|
|
|
<form {onsubmit}>
|
|
<PageContent>
|
|
<div class="card flex flex-col gap-4 shadow-md">
|
|
<strong class="flex items-center gap-3 text-lg">
|
|
<Icon icon={Bell} />
|
|
Alert Settings
|
|
</strong>
|
|
{#await Badge.isSupported()}
|
|
<!-- pass -->
|
|
{:then { isSupported }}
|
|
{#if isSupported}
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Show badge for unread alerts</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.badge} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
{/if}
|
|
{/await}
|
|
{#if !Capacitor.isNativePlatform()}
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Play sound for new activity</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.sound} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
{/if}
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Enable push notifications</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.push} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
</div>
|
|
<div
|
|
class={cx("card bg-surface flex flex-col gap-4 shadow-md", {
|
|
"pointer-events-none opacity-50": !settings.badge && !settings.sound && !settings.push,
|
|
})}>
|
|
<strong class="text-lg">Alert Types</strong>
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Notify me about new activity</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.spaces} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Always notify me when mentioned</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.mentions} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Notify me about new messages</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.messages} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
</div>
|
|
{#if EMAIL_NOTIFICATION_SERVER}
|
|
<div class="card bg-surface flex flex-col gap-4 shadow-md">
|
|
<strong class="text-lg">Email Digest Settings</strong>
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Receive email digest of missed activity</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<ToggleInput bind:checked={settings.email} />
|
|
{/snippet}
|
|
</FieldInline>
|
|
{#if settings.email}
|
|
{#if $communitiesWithoutEmail.length > 0}
|
|
<div class="flex flex-col gap-1 rounded-2xl border border-warning bg-warning/10 p-4">
|
|
<p class="flex items-center gap-2 text-sm text-warning">
|
|
{#if $communitiesWithoutEmail.length === 1}
|
|
1 of your communities does not support email notifications.
|
|
{:else}
|
|
{$communitiesWithoutEmail.length} of your communities do not support email notifications.
|
|
{/if}
|
|
<Button class="link" onclick={showEmailInfo}>Learn more</Button>
|
|
</p>
|
|
<ul class="list-inside list-disc text-sm text-warning">
|
|
{#each $communitiesWithoutEmail as name (name)}
|
|
<li>{name}</li>
|
|
{/each}
|
|
</ul>
|
|
</div>
|
|
{/if}
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Email address</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<input
|
|
type="email"
|
|
class="input input-bordered w-full"
|
|
bind:value={settings.emailAddress}
|
|
oninput={clearError}
|
|
placeholder="you@example.com" />
|
|
{/snippet}
|
|
</FieldInline>
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Frequency</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<select
|
|
class="select select-bordered"
|
|
bind:value={settings.emailFrequency}
|
|
onchange={clearError}>
|
|
<option value="daily">Daily</option>
|
|
<option value="weekly">Weekly</option>
|
|
</select>
|
|
{/snippet}
|
|
</FieldInline>
|
|
{/if}
|
|
{#if $emailNotificationState.error}
|
|
<div class="text-sm text-error p-3">
|
|
{$emailNotificationState.error}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
<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}
|
|
>Discard Changes</Button>
|
|
<Button type="submit" class="button button-primary" disabled={loading}>
|
|
<Spinner {loading}>Save Changes</Spinner>
|
|
</Button>
|
|
</div>
|
|
</PageContent>
|
|
</form>
|