Update alert form to include push notifications
This commit is contained in:
+45
-7
@@ -14,6 +14,8 @@ import {
|
||||
AUTH_JOIN,
|
||||
ROOMS,
|
||||
COMMENT,
|
||||
ALERT_REQUEST_PUSH,
|
||||
ALERT_REQUEST_EMAIL,
|
||||
isSignedEvent,
|
||||
makeEvent,
|
||||
displayProfile,
|
||||
@@ -54,7 +56,6 @@ import {
|
||||
PROTECTED,
|
||||
userMembership,
|
||||
INDEXER_RELAYS,
|
||||
ALERT,
|
||||
NOTIFIER_PUBKEY,
|
||||
NOTIFIER_RELAY,
|
||||
userRoomsByUrl,
|
||||
@@ -368,7 +369,7 @@ export const makeComment = ({event, content, tags = []}: CommentParams) =>
|
||||
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeComment(params), relays})
|
||||
|
||||
export type AlertParams = {
|
||||
export type EmailAlertParams = {
|
||||
feed: Feed
|
||||
cron: string
|
||||
email: string
|
||||
@@ -376,7 +377,13 @@ export type AlertParams = {
|
||||
claims: Record<string, string>
|
||||
}
|
||||
|
||||
export const makeAlert = async ({cron, email, feed, claims, description}: AlertParams) => {
|
||||
export const makeEmailAlert = async ({
|
||||
cron,
|
||||
email,
|
||||
feed,
|
||||
claims,
|
||||
description,
|
||||
}: EmailAlertParams) => {
|
||||
const tags = [
|
||||
["feed", JSON.stringify(feed)],
|
||||
["cron", cron],
|
||||
@@ -384,7 +391,6 @@ export const makeAlert = async ({cron, email, feed, claims, description}: AlertP
|
||||
["locale", LOCALE],
|
||||
["timezone", TIMEZONE],
|
||||
["description", description],
|
||||
["channel", "email"],
|
||||
[
|
||||
"handler",
|
||||
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
|
||||
@@ -397,7 +403,7 @@ export const makeAlert = async ({cron, email, feed, claims, description}: AlertP
|
||||
tags.push(["claim", relay, claim])
|
||||
}
|
||||
|
||||
return makeEvent(ALERT, {
|
||||
return makeEvent(ALERT_REQUEST_EMAIL, {
|
||||
content: await signer.get().nip44.encrypt(NOTIFIER_PUBKEY, JSON.stringify(tags)),
|
||||
tags: [
|
||||
["d", randomId()],
|
||||
@@ -406,5 +412,37 @@ export const makeAlert = async ({cron, email, feed, claims, description}: AlertP
|
||||
})
|
||||
}
|
||||
|
||||
export const publishAlert = async (params: AlertParams) =>
|
||||
publishThunk({event: await makeAlert(params), relays: [NOTIFIER_RELAY]})
|
||||
export const publishEmailAlert = async (params: EmailAlertParams) =>
|
||||
publishThunk({event: await makeEmailAlert(params), relays: [NOTIFIER_RELAY]})
|
||||
|
||||
export type PushAlertParams = {
|
||||
feed: Feed
|
||||
description: string
|
||||
claims: Record<string, string>
|
||||
}
|
||||
|
||||
export const makePushAlert = async ({feed, claims, description}: PushAlertParams) => {
|
||||
const tags = [
|
||||
["feed", JSON.stringify(feed)],
|
||||
["locale", LOCALE],
|
||||
["timezone", TIMEZONE],
|
||||
["description", description],
|
||||
["token", ""],
|
||||
["platform", ""],
|
||||
]
|
||||
|
||||
for (const [relay, claim] of Object.entries(claims)) {
|
||||
tags.push(["claim", relay, claim])
|
||||
}
|
||||
|
||||
return makeEvent(ALERT_REQUEST_PUSH, {
|
||||
content: await signer.get().nip44.encrypt(NOTIFIER_PUBKEY, JSON.stringify(tags)),
|
||||
tags: [
|
||||
["d", randomId()],
|
||||
["p", NOTIFIER_PUBKEY],
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export const publishPushAlert = async (params: PushAlertParams) =>
|
||||
publishThunk({event: await makePushAlert(params), relays: [NOTIFIER_RELAY]})
|
||||
|
||||
@@ -13,22 +13,34 @@
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import {alerts, getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state"
|
||||
import {loadAlertStatuses, requestRelayClaims} from "@app/requests"
|
||||
import {publishAlert} from "@app/commands"
|
||||
import {publishEmailAlert, publishPushAlert} from "@app/commands"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
type Props = {
|
||||
channel?: string
|
||||
relay?: string
|
||||
notifyChat?: boolean
|
||||
notifyThreads?: boolean
|
||||
notifyCalendar?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
relay = "",
|
||||
channel = "email",
|
||||
notifyChat = true,
|
||||
notifyThreads = true,
|
||||
notifyCalendar = true,
|
||||
}: Props = $props()
|
||||
|
||||
const timezoneOffset = parseInt(TIMEZONE.slice(3)) / 100
|
||||
const minute = randomInt(0, 59)
|
||||
const hour = (17 - timezoneOffset) % 24
|
||||
const WEEKLY = `0 ${minute} ${hour} * * 1`
|
||||
const DAILY = `0 ${minute} ${hour} * * *`
|
||||
|
||||
let loading = false
|
||||
let cron = WEEKLY
|
||||
let email = $alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || ""
|
||||
let relay = ""
|
||||
let notifyThreads = true
|
||||
let notifyCalendar = true
|
||||
let notifyChat = false
|
||||
let loading = $state(false)
|
||||
let cron = $state(WEEKLY)
|
||||
let email = $state($alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "")
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
@@ -84,7 +96,10 @@
|
||||
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
|
||||
const description = `${cadence} alert for ${displayList(display)} on ${displayRelayUrl(relay)}, sent via email.`
|
||||
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(relay))
|
||||
const thunk = await publishAlert({cron, email, feed, claims, description})
|
||||
const thunk =
|
||||
channel === "email"
|
||||
? await publishEmailAlert({cron, email, feed, claims, description})
|
||||
: await publishPushAlert({feed, claims, description})
|
||||
|
||||
await thunk.result
|
||||
await loadAlertStatuses($pubkey!)
|
||||
@@ -105,25 +120,38 @@
|
||||
</ModalHeader>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Email Address*</p>
|
||||
<p>Alert Type*</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 bind:value={channel} class="select select-bordered">
|
||||
<option value="push">Push Notification</option>
|
||||
<option value="email">Email Digest</option>
|
||||
</select>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{#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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import {displayRelayUrl, getTagValue} from "@welshman/util"
|
||||
import {pubkey, deriveRelay} from "@welshman/app"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -13,6 +13,8 @@
|
||||
import SpaceExit from "@app/components/SpaceExit.svelte"
|
||||
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
||||
import ProfileList from "@app/components/ProfileList.svelte"
|
||||
import AlertAdd from "@app/components/AlertAdd.svelte"
|
||||
import AlertDelete from "@app/components/AlertDelete.svelte"
|
||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||
import MenuSpaceRoomItem from "@app/components/MenuSpaceRoomItem.svelte"
|
||||
import InfoMissingRooms from "@app/components/InfoMissingRooms.svelte"
|
||||
@@ -23,7 +25,9 @@
|
||||
deriveUserRooms,
|
||||
deriveOtherRooms,
|
||||
hasNip29,
|
||||
alerts,
|
||||
} from "@app/state"
|
||||
import {loadAlerts} from "@app/requests"
|
||||
import {notifications} from "@app/notifications"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
@@ -36,6 +40,7 @@
|
||||
const calendarPath = makeSpacePath(url, "calendar")
|
||||
const userRooms = deriveUserRooms(url)
|
||||
const otherRooms = deriveOtherRooms(url)
|
||||
const alert = $derived($alerts.find(a => getTagValue("feed", a.tags)?.includes(url)))
|
||||
|
||||
const openMenu = () => {
|
||||
showMenu = true
|
||||
@@ -62,6 +67,10 @@
|
||||
|
||||
const addRoom = () => pushModal(RoomCreate, {url}, {replaceState})
|
||||
|
||||
const addAlert = () => pushModal(AlertAdd, {relay: url, channel: "push"})
|
||||
|
||||
const deleteAlert = () => pushModal(AlertDelete, {alert})
|
||||
|
||||
let showMenu = $state(false)
|
||||
let replaceState = $state(false)
|
||||
let element: Element | undefined = $state()
|
||||
@@ -72,6 +81,7 @@
|
||||
|
||||
onMount(() => {
|
||||
replaceState = Boolean(element?.closest(".drawer"))
|
||||
loadAlerts($pubkey!)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -86,7 +96,7 @@
|
||||
<Popover hideOnClick onClose={toggleMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu absolute z-popover mt-2 w-full rounded-box bg-base-100 p-2 shadow-xl">
|
||||
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl">
|
||||
<li>
|
||||
<Button onclick={showMembers}>
|
||||
<Icon icon="user-rounded" />
|
||||
@@ -99,6 +109,21 @@
|
||||
Create Invite
|
||||
</Button>
|
||||
</li>
|
||||
{#if alert}
|
||||
<li>
|
||||
<Button onclick={deleteAlert}>
|
||||
<Icon icon="bell" />
|
||||
Disable alerts
|
||||
</Button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Button onclick={addAlert}>
|
||||
<Icon icon="bell" />
|
||||
Enable alerts
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
{#if $userRoomsByUrl.has(url)}
|
||||
<Button onclick={leaveSpace} class="text-error">
|
||||
|
||||
+4
-3
@@ -25,6 +25,9 @@ import {
|
||||
EVENT_TIME,
|
||||
AUTH_INVITE,
|
||||
COMMENT,
|
||||
ALERT_REQUEST_EMAIL,
|
||||
ALERT_REQUEST_PUSH,
|
||||
ALERT_STATUS,
|
||||
matchFilters,
|
||||
getTagValues,
|
||||
getTagValue,
|
||||
@@ -53,8 +56,6 @@ import {
|
||||
import {createScroller} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
import {
|
||||
ALERT,
|
||||
ALERT_STATUS,
|
||||
NOTIFIER_RELAY,
|
||||
INDEXER_RELAYS,
|
||||
getDefaultPubkeys,
|
||||
@@ -348,7 +349,7 @@ export const makeCalendarFeed = ({
|
||||
export const loadAlerts = (pubkey: string) =>
|
||||
load({
|
||||
relays: [NOTIFIER_RELAY],
|
||||
filters: [{kinds: [ALERT], authors: [pubkey]}],
|
||||
filters: [{kinds: [ALERT_REQUEST_EMAIL, ALERT_REQUEST_PUSH], authors: [pubkey]}],
|
||||
})
|
||||
|
||||
export const loadAlertStatuses = (pubkey: string) =>
|
||||
|
||||
+4
-5
@@ -41,6 +41,9 @@ import {
|
||||
ROOM_JOIN,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
ALERT_REQUEST_EMAIL,
|
||||
ALERT_REQUEST_PUSH,
|
||||
ALERT_STATUS,
|
||||
getGroupTags,
|
||||
getRelayTagValues,
|
||||
getPubkeyTagValues,
|
||||
@@ -84,10 +87,6 @@ export const ROOM = "h"
|
||||
|
||||
export const PROTECTED = ["-"]
|
||||
|
||||
export const ALERT = 32830
|
||||
|
||||
export const ALERT_STATUS = 32831
|
||||
|
||||
export const NOTIFIER_PUBKEY = import.meta.env.VITE_NOTIFIER_PUBKEY
|
||||
|
||||
export const NOTIFIER_RELAY = import.meta.env.VITE_NOTIFIER_RELAY
|
||||
@@ -344,7 +343,7 @@ export type Alert = {
|
||||
}
|
||||
|
||||
export const alerts = deriveEventsMapped<Alert>(repository, {
|
||||
filters: [{kinds: [ALERT]}],
|
||||
filters: [{kinds: [ALERT_REQUEST_EMAIL, ALERT_REQUEST_PUSH]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async event => {
|
||||
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||
|
||||
Reference in New Issue
Block a user