flotilla/src/app/components/AlertAdd.svelte

233 lines
6.8 KiB
Svelte
Raw Normal View History

2025-02-25 21:36:32 +00:00
<script lang="ts">
2025-06-19 17:22:17 +00:00
import {onMount} from "svelte"
2025-02-25 21:36:32 +00:00
import {preventDefault} from "@lib/html"
2025-04-28 19:28:39 +00:00
import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
2025-02-25 21:36:32 +00:00
import type {Filter} from "@welshman/util"
2025-04-28 16:48:09 +00:00
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
2025-02-25 21:36:32 +00:00
import {pubkey} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
2025-05-27 22:04:44 +00:00
import {alerts, getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state"
import {loadAlertStatuses, requestRelayClaims} from "@app/requests"
2025-06-19 17:22:17 +00:00
import {publishAlert} from "@app/commands"
import type {AlertParams} from "@app/commands"
import {platform, canSendPushNotifications, getPushInfo} from "@app/push"
2025-02-25 21:36:32 +00:00
import {pushToast} from "@app/toast"
2025-04-23 18:05:28 +00:00
type Props = {
channel?: string
relay?: string
notifyChat?: boolean
notifyThreads?: boolean
notifyCalendar?: boolean
}
let {
relay = "",
channel = "email",
notifyChat = true,
notifyThreads = true,
notifyCalendar = true,
}: Props = $props()
2025-04-23 18:05:28 +00:00
const timezoneOffset = parseInt(TIMEZONE.slice(3)) / 100
2025-02-27 17:33:40 +00:00
const minute = randomInt(0, 59)
2025-02-25 21:36:32 +00:00
const hour = (17 - timezoneOffset) % 24
2025-02-27 17:33:40 +00:00
const WEEKLY = `0 ${minute} ${hour} * * 1`
const DAILY = `0 ${minute} ${hour} * * *`
2025-02-25 21:36:32 +00:00
let loading = $state(false)
let cron = $state(WEEKLY)
let email = $state($alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "")
2025-02-25 21:36:32 +00:00
const back = () => history.back()
const submit = async () => {
2025-06-19 17:22:17 +00:00
if (channel === "email" && !email.includes("@")) {
2025-02-25 21:36:32 +00:00
return pushToast({
theme: "error",
message: "Please provide an email address",
})
}
if (!relay) {
return pushToast({
theme: "error",
message: "Please select a space",
})
}
if (!notifyThreads && !notifyCalendar && !notifyChat) {
return pushToast({
theme: "error",
message: "Please select something to be notified about",
})
}
const filters: Filter[] = []
2025-04-28 16:48:09 +00:00
const display: string[] = []
2025-02-25 21:36:32 +00:00
if (notifyThreads) {
2025-04-28 16:48:09 +00:00
display.push("threads")
2025-02-25 21:36:32 +00:00
filters.push({kinds: [THREAD]})
filters.push({kinds: [COMMENT], "#k": [String(THREAD)]})
}
if (notifyCalendar) {
2025-04-28 16:48:09 +00:00
display.push("calendar events")
2025-02-25 21:36:32 +00:00
filters.push({kinds: [EVENT_TIME]})
filters.push({kinds: [COMMENT], "#k": [String(EVENT_TIME)]})
}
if (notifyChat) {
2025-04-28 16:48:09 +00:00
display.push("chat")
2025-06-19 17:22:17 +00:00
filters.push({kinds: [MESSAGE]})
2025-02-25 21:36:32 +00:00
}
loading = true
try {
const claims = await requestRelayClaims([relay])
2025-04-28 16:48:09 +00:00
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(relay))
2025-06-19 17:22:17 +00:00
const description = `for ${displayList(display)} on ${displayRelayUrl(relay)}`
const params: AlertParams = {feed, claims, description}
if (channel === "email") {
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
params.description = `${cadence} alert ${description}, sent via email.`
params.email = {
cron,
email,
handler: [
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
"wss://relay.nostr.band/",
"web",
],
}
} else {
try {
// @ts-ignore
params[platform] = await getPushInfo()
} catch (e: any) {
return pushToast({
theme: "error",
message: String(e),
})
}
params.description = `Push notification alert ${description}.`
}
const thunk = await publishAlert(params)
2025-03-20 16:38:57 +00:00
await thunk.result
2025-02-25 21:36:32 +00:00
await loadAlertStatuses($pubkey!)
pushToast({message: "Your alert has been successfully created!"})
back()
} finally {
loading = false
}
}
2025-06-19 17:22:17 +00:00
onMount(() => {
if (!canSendPushNotifications()) {
channel = "email"
}
})
2025-02-25 21:36:32 +00:00
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
Add an Alert
{/snippet}
</ModalHeader>
2025-06-19 17:22:17 +00:00
{#if canSendPushNotifications()}
<FieldInline>
{#snippet label()}
<p>Alert Type*</p>
{/snippet}
{#snippet input()}
<select bind:value={channel} class="select select-bordered">
<option value="email">Email Digest</option>
<option value="push">Push Notification</option>
</select>
{/snippet}
</FieldInline>
{/if}
{#if channel === "email"}
<FieldInline>
{#snippet label()}
<p>Email Address*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input placeholder="email@example.com" bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Frequency*</p>
{/snippet}
{#snippet input()}
<select bind:value={cron} class="select select-bordered">
<option value={WEEKLY}>Weekly</option>
<option value={DAILY}>Daily</option>
</select>
{/snippet}
</FieldInline>
{/if}
<FieldInline>
{#snippet label()}
<p>Space*</p>
{/snippet}
{#snippet input()}
<select bind:value={relay} class="select select-bordered">
<option value="" disabled selected>Choose a space URL</option>
{#each getMembershipUrls($userMembership) as url (url)}
<option value={url}>{displayRelayUrl(url)}</option>
{/each}
</select>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Notifications*</p>
{/snippet}
{#snippet input()}
<div class="flex items-center justify-end gap-4">
<span class="flex gap-3">
<input type="checkbox" class="checkbox" bind:checked={notifyThreads} />
Threads
</span>
<span class="flex gap-3">
<input type="checkbox" class="checkbox" bind:checked={notifyCalendar} />
Calendar
</span>
<span class="flex gap-3">
<input type="checkbox" class="checkbox" bind:checked={notifyChat} />
Chat
2025-02-25 21:36:32 +00:00
</span>
</div>
{/snippet}
</FieldInline>
2025-02-25 21:36:32 +00:00
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary" disabled={loading}>
2025-02-25 21:36:32 +00:00
<Spinner {loading}>Confirm</Spinner>
<Icon icon="alt-arrow-right" />
</Button>
</ModalFooter>
</form>