@12-apps/notifications 4.7.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADOPTING.md +32 -0
- package/dist/{chunk-TIGTBSAQ.js → chunk-7234ROOP.js} +11 -5
- package/dist/chunk-7234ROOP.js.map +1 -0
- package/dist/{chunk-Y34FX24X.js → chunk-BGI4BQ66.js} +2 -10
- package/dist/chunk-BGI4BQ66.js.map +1 -0
- package/dist/chunk-BW723CX2.js +214 -0
- package/dist/chunk-BW723CX2.js.map +1 -0
- package/dist/chunk-CQZMTFPY.js +76 -0
- package/dist/chunk-CQZMTFPY.js.map +1 -0
- package/dist/{chunk-HQU4R4SG.js → chunk-GGAZOLNN.js} +2 -2
- package/dist/{chunk-QRAXX3GR.js → chunk-KU4FVOFA.js} +2 -2
- package/dist/chunk-M2TVBVH2.js +15 -0
- package/dist/chunk-M2TVBVH2.js.map +1 -0
- package/dist/chunk-M5XTROYM.js +263 -0
- package/dist/chunk-M5XTROYM.js.map +1 -0
- package/dist/chunk-O5BVUXPO.js +22 -0
- package/dist/chunk-O5BVUXPO.js.map +1 -0
- package/dist/{create-api-notifications-B3u6Kx3x.d.ts → create-api-notifications-WaexrZnn.d.ts} +12 -4
- package/dist/{create-web-notifications-BpNR8qH3.d.ts → create-web-notifications-DrgqtU4Q.d.ts} +15 -4
- package/dist/{generators-B9xt3sRh.d.ts → generators-CH5m8Ucj.d.ts} +1 -1
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +4 -3
- package/dist/hono/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -3
- package/dist/{jobs-BDZ7aGHV.d.ts → jobs-CcsSA0YR.d.ts} +1 -1
- package/dist/manifest/server.d.ts +6 -6
- package/dist/manifest/server.js +5 -4
- package/dist/manifest/server.js.map +1 -1
- package/dist/manifest/web.d.ts +3 -3
- package/dist/manifest/web.js +3 -2
- package/dist/manifest/web.js.map +1 -1
- package/dist/panel-UFXNO4AF.js +243 -0
- package/dist/panel-UFXNO4AF.js.map +1 -0
- package/dist/preferences-screen-V3ZBWP7A.js +294 -0
- package/dist/preferences-screen-V3ZBWP7A.js.map +1 -0
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +17 -11
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +5 -4
- package/dist/{types-CXLAG3UU.d.ts → types-CYJN1xCM.d.ts} +39 -2
- package/dist/web-push/index.d.ts +2 -2
- package/dist/{web-push-Cs14Wp9u.d.ts → web-push-iER5StBm.d.ts} +1 -1
- package/dist/{wire-6dzyfDE7.d.ts → wire-CmRrB3_s.d.ts} +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/react/create-web-notifications.tsx +19 -10
- package/src/react/page-lazy.tsx +73 -0
- package/src/react/panel-lazy.tsx +74 -0
- package/src/server/db.ts +12 -1
- package/src/server/dispatch.ts +4 -0
- package/src/server/router.ts +17 -1
- package/src/types.ts +39 -1
- package/dist/chunk-6HLHQDKS.js +0 -1022
- package/dist/chunk-6HLHQDKS.js.map +0 -1
- package/dist/chunk-TIGTBSAQ.js.map +0 -1
- package/dist/chunk-Y34FX24X.js.map +0 -1
- /package/dist/{chunk-HQU4R4SG.js.map → chunk-GGAZOLNN.js.map} +0 -0
- /package/dist/{chunk-QRAXX3GR.js.map → chunk-KU4FVOFA.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/panel.tsx","../src/react/row.tsx"],"sourcesContent":["/**\n * The notification-centre slide-over: newest-first list with unread styling,\n * per-item open (marks read + deep-links), soft delete, mark-all, empty /\n * loading / error states and a \"load more\" cursor pager.\n *\n * Rendering is app-agnostic — the host passes `onNavigate` (its router's\n * navigate) for deep links. Without one a link is simply not followed, which is\n * what lets the panel mount in a host that has no router at all.\n */\nimport { useCallback, type JSX } from 'react';\n\nimport { EmptyState } from '@12-apps/ui/data-display/EmptyState';\nimport { LoadingState } from '@12-apps/ui/data-display/LoadingState';\nimport { Button } from '@12-apps/ui/form/Button';\nimport { Drawer, DrawerContent, DrawerHeader } from '@12-apps/ui/layout/Drawer';\nimport { Box } from '@12-apps/ui/mui/Box';\nimport { useMediaQuery } from '@12-apps/ui/mui/useMediaQuery';\nimport { useTheme } from '@12-apps/ui/mui/styles';\n\nimport type { NotificationMessages } from '../messages';\nimport type { InboxNotification } from '../wire';\n\nimport { BellIcon } from './bell-icon';\nimport { useInboxList } from './hooks';\nimport type { InboxState, InboxStore } from './inbox-state';\nimport { NotificationRow } from './row';\n\ninterface PanelBodyProps {\n state: InboxState;\n messages: NotificationMessages;\n onRetry: () => void;\n onLoadMore: () => void;\n onOpen: (notification: InboxNotification) => void;\n onDelete: (id: string) => void;\n}\n\n/** The scrollable panel body: loading / error / empty / the list + pager. */\nfunction PanelBody({\n state,\n messages,\n onRetry,\n onLoadMore,\n onOpen,\n onDelete,\n}: PanelBodyProps): JSX.Element {\n if (state.status === 'pending' || state.status === 'idle') {\n return (\n <LoadingState\n variant=\"spinner\"\n message={messages.loading}\n size=\"md\"\n dataTestId=\"notifications-loading\"\n />\n );\n }\n if (state.status === 'error') {\n return (\n <EmptyState\n variant=\"minimal\"\n title={messages.loadFailedTitle}\n description={messages.loadFailedBody}\n onRefresh={onRetry}\n refreshLabel={messages.retry}\n dataTestId=\"notifications-error\"\n />\n );\n }\n if (state.items.length === 0) {\n return (\n <EmptyState\n variant=\"illustrated\"\n illustration={<BellIcon size={44} dim />}\n title={messages.emptyTitle}\n description={messages.emptyBody}\n dataTestId=\"notifications-empty\"\n />\n );\n }\n return (\n <Box>\n {state.items.map((notification) => (\n <NotificationRow\n key={notification.id}\n notification={notification}\n messages={messages}\n onOpen={onOpen}\n onDelete={onDelete}\n />\n ))}\n {state.nextCursor ? (\n <Box sx={{ display: 'flex', justifyContent: 'center', py: 1.5 }}>\n <Button\n variant=\"outline\"\n color=\"neutral\"\n size=\"sm\"\n disabled={state.loadingMore}\n onClick={onLoadMore}\n dataTestId=\"notifications-load-more\"\n >\n {state.loadingMore ? messages.loadingMore : messages.loadMore}\n </Button>\n </Box>\n ) : null}\n </Box>\n );\n}\n\nexport interface NotificationsPanelProps {\n open: boolean;\n onClose: () => void;\n /** Navigate to a notification's in-app link (the host's router). */\n onNavigate?: (link: string) => void;\n}\n\nexport function NotificationsPanel({\n open,\n onClose,\n onNavigate,\n store,\n messages,\n}: NotificationsPanelProps & {\n store: InboxStore;\n messages: NotificationMessages;\n}): JSX.Element {\n // `useTheme` from @mui/material/styles falls back to the DEFAULT theme when\n // no provider is mounted, where the callback form of `useMediaQuery` would\n // hand the callback a null theme and throw. A published component must render\n // in a host that has not wrapped it yet.\n const theme = useTheme();\n const isMobile = useMediaQuery(theme.breakpoints.down('sm'));\n const state = useInboxList(store, open);\n\n const openNotification = useCallback(\n (notification: InboxNotification) => {\n if (notification.readAt === null) store.markRead([notification.id]);\n if (notification.link && onNavigate) {\n onClose();\n onNavigate(notification.link);\n }\n },\n [store, onClose, onNavigate],\n );\n\n const hasUnread = state.items.some((item) => item.readAt === null);\n\n return (\n <Drawer\n open={open}\n onClose={onClose}\n anchor=\"right\"\n variant=\"right\"\n width={isMobile ? '100vw' : 400}\n dataTestId=\"notifications-panel\"\n >\n <DrawerHeader onClose={onClose}>{messages.panelTitle}</DrawerHeader>\n <DrawerContent>\n {hasUnread ? (\n <Box sx={{ display: 'flex', justifyContent: 'flex-end', pb: 1 }}>\n <Button\n variant=\"ghost\"\n color=\"primary\"\n size=\"xs\"\n onClick={() => store.markAllRead()}\n dataTestId=\"notifications-mark-all-read\"\n >\n {messages.markAllRead}\n </Button>\n </Box>\n ) : null}\n <PanelBody\n state={state}\n messages={messages}\n onRetry={() => store.invalidate()}\n onLoadMore={() => store.loadMore()}\n onOpen={openNotification}\n onDelete={(id) => store.remove(id)}\n />\n </DrawerContent>\n </Drawer>\n );\n}\n","/** One inbox row: unread accent, content (opens/marks read), timestamp, delete. */\nimport type { JSX } from 'react';\n\nimport { Button } from '@12-apps/ui/form/Button';\nimport { Box } from '@12-apps/ui/mui/Box';\nimport { alpha, type Theme } from '@12-apps/ui/mui/styles';\nimport { Text } from '@12-apps/ui/typography/Text';\n\nimport type { NotificationMessages } from '../messages';\nimport type { InboxNotification } from '../wire';\n\nimport { relativeTime } from './relative-time';\n\nconst contentButtonSx = {\n flex: 1,\n minWidth: 0,\n display: 'flex',\n flexDirection: 'column',\n gap: 0.25,\n textAlign: 'left',\n border: 'none',\n background: 'none',\n p: 0,\n cursor: 'pointer',\n color: 'text.primary',\n fontFamily: 'inherit',\n} as const;\n\nconst unreadDotSx = {\n width: 8,\n height: 8,\n borderRadius: '50%',\n bgcolor: 'primary.main',\n flex: '0 0 auto',\n} as const;\n\nexport function NotificationRow({\n notification,\n messages,\n onOpen,\n onDelete,\n}: {\n notification: InboxNotification;\n messages: NotificationMessages;\n onOpen: (notification: InboxNotification) => void;\n onDelete: (id: string) => void;\n}): JSX.Element {\n const unread = notification.readAt === null;\n return (\n <Box\n data-testid={`notification-${notification.id}`}\n sx={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: 1,\n py: 1.5,\n px: 1,\n borderBottom: '1px solid',\n borderColor: 'divider',\n bgcolor: unread ? (t: Theme) => alpha(t.palette.primary.main, 0.06) : 'transparent',\n }}\n >\n <Box\n component=\"button\"\n type=\"button\"\n onClick={() => onOpen(notification)}\n aria-label={\n unread ? `${notification.title} (${messages.unreadSuffix})` : notification.title\n }\n sx={contentButtonSx}\n >\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>\n {unread ? <Box aria-hidden sx={unreadDotSx} /> : null}\n <Text variant=\"body\" size=\"sm\" weight={unread ? 'bold' : 'medium'} as=\"span\">\n {notification.title}\n </Text>\n </Box>\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"span\">\n {notification.body}\n </Text>\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"span\" italic>\n {relativeTime(notification.createdAt, messages)}\n </Text>\n </Box>\n\n <Button\n variant=\"ghost\"\n color=\"neutral\"\n size=\"xs\"\n aria-label={messages.deleteOne(notification.title)}\n onClick={() => onDelete(notification.id)}\n dataTestId={`notification-delete-${notification.id}`}\n >\n ✕\n </Button>\n </Box>\n );\n}\n"],"mappings":"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","names":["Button","Box","jsx","jsxs","Box","Button"]}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import {
|
|
2
|
+
enableWebPush,
|
|
3
|
+
getExistingPushSubscription
|
|
4
|
+
} from "./chunk-CQZMTFPY.js";
|
|
5
|
+
import {
|
|
6
|
+
NOTIFICATION_CHANNELS
|
|
7
|
+
} from "./chunk-BGI4BQ66.js";
|
|
8
|
+
import {
|
|
9
|
+
__name
|
|
10
|
+
} from "./chunk-7QVYU63E.js";
|
|
11
|
+
|
|
12
|
+
// src/react/preferences-screen.tsx
|
|
13
|
+
import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
|
|
14
|
+
import { LoadingState } from "@12-apps/ui/data-display/LoadingState";
|
|
15
|
+
import { Switch } from "@12-apps/ui/form/Switch";
|
|
16
|
+
import { Box as Box2 } from "@12-apps/ui/mui/Box";
|
|
17
|
+
import { Text as Text2 } from "@12-apps/ui/typography/Text";
|
|
18
|
+
|
|
19
|
+
// src/react/web-push-setup.tsx
|
|
20
|
+
import { useEffect, useState } from "react";
|
|
21
|
+
import { Button } from "@12-apps/ui/form/Button";
|
|
22
|
+
import { Box } from "@12-apps/ui/mui/Box";
|
|
23
|
+
import { Text } from "@12-apps/ui/typography/Text";
|
|
24
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
25
|
+
var cardSx = {
|
|
26
|
+
p: 2,
|
|
27
|
+
border: "1px solid",
|
|
28
|
+
borderColor: "divider",
|
|
29
|
+
borderRadius: 2
|
|
30
|
+
};
|
|
31
|
+
function InstallFirstHint({ hint }) {
|
|
32
|
+
return /* @__PURE__ */ jsxs(Box, { sx: cardSx, "data-testid": "web-push-install-hint", children: [
|
|
33
|
+
/* @__PURE__ */ jsx(Text, { variant: "body", size: "sm", weight: "semibold", as: "p", children: hint.title }),
|
|
34
|
+
/* @__PURE__ */ jsx(Text, { variant: "caption", size: "xs", color: "secondary", as: "p", children: hint.body })
|
|
35
|
+
] });
|
|
36
|
+
}
|
|
37
|
+
__name(InstallFirstHint, "InstallFirstHint");
|
|
38
|
+
async function resolveState(api) {
|
|
39
|
+
const subscription = await getExistingPushSubscription();
|
|
40
|
+
if (!subscription) return "idle";
|
|
41
|
+
const registration = await api.getPushRegistration({ endpoint: subscription.endpoint }).catch(() => null);
|
|
42
|
+
return registration?.registered ? "on" : "idle";
|
|
43
|
+
}
|
|
44
|
+
__name(resolveState, "resolveState");
|
|
45
|
+
function statusText(state, messages) {
|
|
46
|
+
if (state === "on") return messages.devicePushOn;
|
|
47
|
+
if (state === "denied") return messages.devicePushDenied;
|
|
48
|
+
if (state === "failed") return messages.devicePushFailed;
|
|
49
|
+
return messages.devicePushIdle;
|
|
50
|
+
}
|
|
51
|
+
__name(statusText, "statusText");
|
|
52
|
+
function WebPushDeviceSetup({
|
|
53
|
+
available,
|
|
54
|
+
api,
|
|
55
|
+
messages,
|
|
56
|
+
config
|
|
57
|
+
}) {
|
|
58
|
+
const [state, setState] = useState("checking");
|
|
59
|
+
const [needsInstall] = useState(() => config.needsInstallFirst?.() ?? false);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
void resolveState(api).then((next) => {
|
|
63
|
+
if (!cancelled) setState(next);
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, [api]);
|
|
69
|
+
if (!available) return null;
|
|
70
|
+
if (needsInstall && config.installHint) return /* @__PURE__ */ jsx(InstallFirstHint, { hint: config.installHint });
|
|
71
|
+
const enable = /* @__PURE__ */ __name(async () => {
|
|
72
|
+
setState("busy");
|
|
73
|
+
const result = await enableWebPush(api, config.swPath);
|
|
74
|
+
if (result.ok) setState("on");
|
|
75
|
+
else setState(result.reason === "permission-denied" ? "denied" : "failed");
|
|
76
|
+
}, "enable");
|
|
77
|
+
return /* @__PURE__ */ jsxs(
|
|
78
|
+
Box,
|
|
79
|
+
{
|
|
80
|
+
sx: {
|
|
81
|
+
...cardSx,
|
|
82
|
+
display: "flex",
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
justifyContent: "space-between",
|
|
85
|
+
gap: 2
|
|
86
|
+
},
|
|
87
|
+
"data-testid": "web-push-device-setup",
|
|
88
|
+
children: [
|
|
89
|
+
/* @__PURE__ */ jsxs(Box, { sx: { minWidth: 0 }, children: [
|
|
90
|
+
/* @__PURE__ */ jsx(Text, { variant: "body", size: "sm", weight: "semibold", as: "p", children: messages.devicePushTitle }),
|
|
91
|
+
/* @__PURE__ */ jsx(Text, { variant: "caption", size: "xs", color: "secondary", as: "p", children: statusText(state, messages) })
|
|
92
|
+
] }),
|
|
93
|
+
state !== "on" ? /* @__PURE__ */ jsx(
|
|
94
|
+
Button,
|
|
95
|
+
{
|
|
96
|
+
variant: "outline",
|
|
97
|
+
color: "primary",
|
|
98
|
+
size: "sm",
|
|
99
|
+
disabled: state === "busy" || state === "checking",
|
|
100
|
+
onClick: () => void enable(),
|
|
101
|
+
dataTestId: "web-push-enable",
|
|
102
|
+
children: state === "busy" ? messages.devicePushEnabling : messages.devicePushEnable
|
|
103
|
+
}
|
|
104
|
+
) : null
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
__name(WebPushDeviceSetup, "WebPushDeviceSetup");
|
|
110
|
+
|
|
111
|
+
// src/react/preferences-screen.tsx
|
|
112
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
113
|
+
function CategoryCard({
|
|
114
|
+
category,
|
|
115
|
+
channels,
|
|
116
|
+
availability,
|
|
117
|
+
messages,
|
|
118
|
+
onToggle
|
|
119
|
+
}) {
|
|
120
|
+
const labels = messages.categoryLabels[category];
|
|
121
|
+
return /* @__PURE__ */ jsxs2(
|
|
122
|
+
Box2,
|
|
123
|
+
{
|
|
124
|
+
sx: { p: 2, border: "1px solid", borderColor: "divider", borderRadius: 2 },
|
|
125
|
+
"data-testid": `prefs-${category}`,
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ jsx2(Text2, { variant: "body", size: "sm", weight: "semibold", as: "p", children: labels?.title ?? messages.categoryFallbackTitle(category) }),
|
|
128
|
+
labels?.description ? /* @__PURE__ */ jsx2(Text2, { variant: "caption", size: "xs", color: "secondary", as: "p", children: labels.description }) : null,
|
|
129
|
+
/* @__PURE__ */ jsx2(
|
|
130
|
+
Box2,
|
|
131
|
+
{
|
|
132
|
+
sx: {
|
|
133
|
+
mt: 1.5,
|
|
134
|
+
display: "grid",
|
|
135
|
+
gridTemplateColumns: { xs: "1fr 1fr", sm: "repeat(4, 1fr)" },
|
|
136
|
+
gap: 1
|
|
137
|
+
},
|
|
138
|
+
children: NOTIFICATION_CHANNELS.map((channel) => /* @__PURE__ */ jsx2(
|
|
139
|
+
Switch,
|
|
140
|
+
{
|
|
141
|
+
size: "sm",
|
|
142
|
+
color: "primary",
|
|
143
|
+
label: messages.channelLabels[channel] ?? channel,
|
|
144
|
+
checked: channels[channel] && availability[channel],
|
|
145
|
+
disabled: !availability[channel],
|
|
146
|
+
onChange: (_, checked) => onToggle(channel, checked),
|
|
147
|
+
dataTestId: `prefs-${category}-${channel}`
|
|
148
|
+
},
|
|
149
|
+
channel
|
|
150
|
+
))
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
__name(CategoryCard, "CategoryCard");
|
|
158
|
+
function UnavailableHints({
|
|
159
|
+
availability,
|
|
160
|
+
messages
|
|
161
|
+
}) {
|
|
162
|
+
const unavailable = NOTIFICATION_CHANNELS.filter((channel) => !availability[channel]);
|
|
163
|
+
if (unavailable.length === 0) return null;
|
|
164
|
+
return /* @__PURE__ */ jsx2(Box2, { sx: { display: "flex", flexDirection: "column", gap: 0.5 }, children: unavailable.map((channel) => /* @__PURE__ */ jsxs2(
|
|
165
|
+
Text2,
|
|
166
|
+
{
|
|
167
|
+
variant: "caption",
|
|
168
|
+
size: "xs",
|
|
169
|
+
color: "secondary",
|
|
170
|
+
as: "p",
|
|
171
|
+
"data-testid": `prefs-hint-${channel}`,
|
|
172
|
+
children: [
|
|
173
|
+
messages.channelLabels[channel] ?? channel,
|
|
174
|
+
":",
|
|
175
|
+
" ",
|
|
176
|
+
messages.channelUnavailableHints[channel] ?? ""
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
channel
|
|
180
|
+
)) });
|
|
181
|
+
}
|
|
182
|
+
__name(UnavailableHints, "UnavailableHints");
|
|
183
|
+
function usePreferences(api) {
|
|
184
|
+
const [payload, setPayload] = useState2(null);
|
|
185
|
+
useEffect2(() => {
|
|
186
|
+
let cancelled = false;
|
|
187
|
+
void api.getPreferences().then((next) => {
|
|
188
|
+
if (!cancelled) setPayload(next);
|
|
189
|
+
}).catch(() => void 0);
|
|
190
|
+
return () => {
|
|
191
|
+
cancelled = true;
|
|
192
|
+
};
|
|
193
|
+
}, [api]);
|
|
194
|
+
const toggle = useCallback(
|
|
195
|
+
(category, channel, enabled) => {
|
|
196
|
+
setPayload((current) => {
|
|
197
|
+
const row = current?.preferences[category];
|
|
198
|
+
if (!current || !row) return current;
|
|
199
|
+
return {
|
|
200
|
+
...current,
|
|
201
|
+
preferences: { ...current.preferences, [category]: { ...row, [channel]: enabled } }
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
const reconcile = /* @__PURE__ */ __name(() => {
|
|
205
|
+
void api.getPreferences().then(setPayload).catch(() => void 0);
|
|
206
|
+
}, "reconcile");
|
|
207
|
+
void api.savePreference(category, channel, enabled).then((result) => {
|
|
208
|
+
if (result.ok) setPayload(result.data);
|
|
209
|
+
else reconcile();
|
|
210
|
+
}).catch(reconcile);
|
|
211
|
+
},
|
|
212
|
+
[api]
|
|
213
|
+
);
|
|
214
|
+
return { payload, toggle };
|
|
215
|
+
}
|
|
216
|
+
__name(usePreferences, "usePreferences");
|
|
217
|
+
function PreferencesScreen({
|
|
218
|
+
footer,
|
|
219
|
+
api,
|
|
220
|
+
messages,
|
|
221
|
+
webPush
|
|
222
|
+
}) {
|
|
223
|
+
const { payload, toggle } = usePreferences(api);
|
|
224
|
+
if (!payload) {
|
|
225
|
+
return /* @__PURE__ */ jsx2(
|
|
226
|
+
LoadingState,
|
|
227
|
+
{
|
|
228
|
+
variant: "spinner",
|
|
229
|
+
message: messages.loadingMore,
|
|
230
|
+
size: "md",
|
|
231
|
+
dataTestId: "notification-prefs-loading"
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const { preferences, availability, categories } = payload;
|
|
236
|
+
return /* @__PURE__ */ jsxs2(
|
|
237
|
+
Box2,
|
|
238
|
+
{
|
|
239
|
+
component: "section",
|
|
240
|
+
"data-testid": "notification-prefs-view",
|
|
241
|
+
sx: {
|
|
242
|
+
maxWidth: 640,
|
|
243
|
+
mx: "auto",
|
|
244
|
+
width: "100%",
|
|
245
|
+
px: 2,
|
|
246
|
+
py: 4,
|
|
247
|
+
display: "flex",
|
|
248
|
+
flexDirection: "column",
|
|
249
|
+
gap: 2
|
|
250
|
+
},
|
|
251
|
+
children: [
|
|
252
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
253
|
+
/* @__PURE__ */ jsx2(Text2, { variant: "heading", size: "lg", as: "h1", children: messages.preferencesTitle }),
|
|
254
|
+
/* @__PURE__ */ jsxs2(Text2, { variant: "caption", size: "sm", color: "secondary", as: "p", children: [
|
|
255
|
+
messages.preferencesLead,
|
|
256
|
+
" ",
|
|
257
|
+
footer
|
|
258
|
+
] })
|
|
259
|
+
] }),
|
|
260
|
+
/* @__PURE__ */ jsx2(
|
|
261
|
+
WebPushDeviceSetup,
|
|
262
|
+
{
|
|
263
|
+
available: availability.WEB_PUSH,
|
|
264
|
+
api,
|
|
265
|
+
messages,
|
|
266
|
+
config: webPush
|
|
267
|
+
}
|
|
268
|
+
),
|
|
269
|
+
categories.map((category) => /* @__PURE__ */ jsx2(
|
|
270
|
+
CategoryCard,
|
|
271
|
+
{
|
|
272
|
+
category,
|
|
273
|
+
channels: preferences[category] ?? {
|
|
274
|
+
EMAIL: false,
|
|
275
|
+
SMS: false,
|
|
276
|
+
WHATSAPP: false,
|
|
277
|
+
WEB_PUSH: false
|
|
278
|
+
},
|
|
279
|
+
availability,
|
|
280
|
+
messages,
|
|
281
|
+
onToggle: (channel, enabled) => toggle(category, channel, enabled)
|
|
282
|
+
},
|
|
283
|
+
category
|
|
284
|
+
)),
|
|
285
|
+
/* @__PURE__ */ jsx2(UnavailableHints, { availability, messages })
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
__name(PreferencesScreen, "PreferencesScreen");
|
|
291
|
+
export {
|
|
292
|
+
PreferencesScreen
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=preferences-screen-V3ZBWP7A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/preferences-screen.tsx","../src/react/web-push-setup.tsx"],"sourcesContent":["/**\n * The notification-preferences screen: the category × channel matrix over\n * `GET/PUT <mount>/notification-preferences`.\n *\n * Toggles auto-save (optimistic, per change); channels that cannot reach the\n * user right now (no phone on file / channel not declared) render disabled with\n * a hint. Web Push additionally carries the per-BROWSER enable step, since a\n * preference alone cannot reach a device that never subscribed.\n */\nimport { useCallback, useEffect, useState, type JSX } from 'react';\n\nimport { LoadingState } from '@12-apps/ui/data-display/LoadingState';\nimport { Switch } from '@12-apps/ui/form/Switch';\nimport { Box } from '@12-apps/ui/mui/Box';\nimport { Text } from '@12-apps/ui/typography/Text';\n\nimport type { NotificationMessages } from '../messages';\nimport { NOTIFICATION_CHANNELS, type NotificationChannel } from '../types';\n\nimport type { NotificationsApiClient, PreferencesPayload } from './api';\nimport { WebPushDeviceSetup, type WebPushSetupConfig } from './web-push-setup';\n\ntype Availability = Record<NotificationChannel, boolean>;\n\n/** One category's row of channel switches. */\nfunction CategoryCard({\n category,\n channels,\n availability,\n messages,\n onToggle,\n}: {\n category: string;\n channels: Record<NotificationChannel, boolean>;\n availability: Availability;\n messages: NotificationMessages;\n onToggle: (channel: NotificationChannel, enabled: boolean) => void;\n}): JSX.Element {\n const labels = messages.categoryLabels[category];\n return (\n <Box\n sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 2 }}\n data-testid={`prefs-${category}`}\n >\n <Text variant=\"body\" size=\"sm\" weight=\"semibold\" as=\"p\">\n {labels?.title ?? messages.categoryFallbackTitle(category)}\n </Text>\n {labels?.description ? (\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"p\">\n {labels.description}\n </Text>\n ) : null}\n <Box\n sx={{\n mt: 1.5,\n display: 'grid',\n gridTemplateColumns: { xs: '1fr 1fr', sm: 'repeat(4, 1fr)' },\n gap: 1,\n }}\n >\n {NOTIFICATION_CHANNELS.map((channel) => (\n <Switch\n key={channel}\n size=\"sm\"\n color=\"primary\"\n label={messages.channelLabels[channel] ?? channel}\n // An unavailable channel reads OFF regardless of the stored choice:\n // a toggle that says \"on\" for a channel that cannot reach you is a\n // promise the pipeline will not keep.\n checked={channels[channel] && availability[channel]}\n disabled={!availability[channel]}\n onChange={(_, checked) => onToggle(channel, checked)}\n // `dataTestId`, not `data-testid`: the UI Switch puts this one on\n // the INPUT (and derives `-container` / `-label` from it), which is\n // the element a click and a `disabled` assertion need.\n dataTestId={`prefs-${category}-${channel}`}\n />\n ))}\n </Box>\n </Box>\n );\n}\n\n/** Why disabled toggles are disabled, one line per unavailable channel. */\nfunction UnavailableHints({\n availability,\n messages,\n}: {\n availability: Availability;\n messages: NotificationMessages;\n}): JSX.Element | null {\n const unavailable = NOTIFICATION_CHANNELS.filter((channel) => !availability[channel]);\n if (unavailable.length === 0) return null;\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>\n {unavailable.map((channel) => (\n <Text\n key={channel}\n variant=\"caption\"\n size=\"xs\"\n color=\"secondary\"\n as=\"p\"\n data-testid={`prefs-hint-${channel}`}\n >\n {messages.channelLabels[channel] ?? channel}:{' '}\n {messages.channelUnavailableHints[channel] ?? ''}\n </Text>\n ))}\n </Box>\n );\n}\n\nexport interface PreferencesScreenProps {\n /** Rendered under the lead paragraph — a \"back to account\" link, typically. */\n footer?: JSX.Element;\n}\n\n/** Optimistic per-toggle auto-save; a failed PUT takes the server's answer. */\nfunction usePreferences(api: NotificationsApiClient): {\n payload: PreferencesPayload | null;\n toggle: (category: string, channel: NotificationChannel, enabled: boolean) => void;\n} {\n const [payload, setPayload] = useState<PreferencesPayload | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void api\n .getPreferences()\n .then((next) => {\n if (!cancelled) setPayload(next);\n })\n .catch(() => undefined);\n return () => {\n cancelled = true;\n };\n }, [api]);\n\n const toggle = useCallback(\n (category: string, channel: NotificationChannel, enabled: boolean) => {\n setPayload((current) => {\n const row = current?.preferences[category];\n if (!current || !row) return current;\n return {\n ...current,\n preferences: { ...current.preferences, [category]: { ...row, [channel]: enabled } },\n };\n });\n const reconcile = (): void => {\n void api.getPreferences().then(setPayload).catch(() => undefined);\n };\n void api\n .savePreference(category, channel, enabled)\n .then((result) => {\n // The PUT answers with the whole matrix, so a success REPLACES the\n // optimistic guess with the server's own row — which is what catches a\n // save the server merged differently from the way the screen assumed.\n if (result.ok) setPayload(result.data);\n else reconcile();\n })\n // The packaged transport never rejects (it folds a failure into\n // `ok: false`), but a HOST transport may — and an unhandled rejection\n // would leave the toggle showing a choice the server never took.\n .catch(reconcile);\n },\n [api],\n );\n\n return { payload, toggle };\n}\n\nexport function PreferencesScreen({\n footer,\n api,\n messages,\n webPush,\n}: PreferencesScreenProps & {\n api: NotificationsApiClient;\n messages: NotificationMessages;\n webPush: WebPushSetupConfig;\n}): JSX.Element {\n const { payload, toggle } = usePreferences(api);\n\n if (!payload) {\n return (\n <LoadingState\n variant=\"spinner\"\n message={messages.loadingMore}\n size=\"md\"\n dataTestId=\"notification-prefs-loading\"\n />\n );\n }\n\n const { preferences, availability, categories } = payload;\n return (\n <Box\n component=\"section\"\n data-testid=\"notification-prefs-view\"\n sx={{\n maxWidth: 640,\n mx: 'auto',\n width: '100%',\n px: 2,\n py: 4,\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n }}\n >\n <Box>\n <Text variant=\"heading\" size=\"lg\" as=\"h1\">\n {messages.preferencesTitle}\n </Text>\n <Text variant=\"caption\" size=\"sm\" color=\"secondary\" as=\"p\">\n {messages.preferencesLead} {footer}\n </Text>\n </Box>\n <WebPushDeviceSetup\n available={availability.WEB_PUSH}\n api={api}\n messages={messages}\n config={webPush}\n />\n {categories.map((category) => (\n <CategoryCard\n key={category}\n category={category}\n channels={preferences[category] ?? {\n EMAIL: false,\n SMS: false,\n WHATSAPP: false,\n WEB_PUSH: false,\n }}\n availability={availability}\n messages={messages}\n onToggle={(channel, enabled) => toggle(category, channel, enabled)}\n />\n ))}\n <UnavailableHints availability={availability} messages={messages} />\n </Box>\n );\n}\n","/**\n * The per-BROWSER Web Push enable step, which sits above the preference matrix\n * because a preference alone cannot reach a device that never subscribed.\n */\nimport { useEffect, useState, type JSX } from 'react';\n\nimport { Button } from '@12-apps/ui/form/Button';\nimport { Box } from '@12-apps/ui/mui/Box';\nimport { Text } from '@12-apps/ui/typography/Text';\n\nimport type { NotificationMessages } from '../messages';\n\nimport type { NotificationsApiClient } from './api';\nimport { enableWebPush, getExistingPushSubscription } from './web-push-client';\n\n/** The panel's copy for a host whose platform blocks browser-level push. */\nexport interface WebPushPlatformHint {\n title: string;\n body: string;\n}\n\nexport interface WebPushSetupConfig {\n /**\n * The host's service-worker path. Path-routed SPAs each control their own\n * scope, and the file itself is the host's.\n */\n swPath?: string;\n /**\n * Whether THIS platform must be installed to the home screen before a\n * subscription can exist at all.\n *\n * iOS is the case: Safari has no browser-level Web Push, so \"Ativar\" there\n * asks no permission, creates no subscription, and fails with nothing a user\n * could act on. The check is a config seam rather than a dependency because\n * \"is this an installable iOS browser\" is a question a host's PWA layer\n * already answers (the origin passes\n * `() => isIosInstallable() && !isStandalone()` from `@12-apps/pwa`).\n */\n needsInstallFirst?: () => boolean;\n /** What to say instead of the button when the check above is true. */\n installHint?: WebPushPlatformHint;\n}\n\nconst cardSx = {\n p: 2,\n border: '1px solid',\n borderColor: 'divider',\n borderRadius: 2,\n} as const;\n\nfunction InstallFirstHint({ hint }: { hint: WebPushPlatformHint }): JSX.Element {\n return (\n <Box sx={cardSx} data-testid=\"web-push-install-hint\">\n <Text variant=\"body\" size=\"sm\" weight=\"semibold\" as=\"p\">\n {hint.title}\n </Text>\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"p\">\n {hint.body}\n </Text>\n </Box>\n );\n}\n\ntype SetupState = 'idle' | 'checking' | 'on' | 'busy' | 'failed' | 'denied';\n\n/**\n * \"Is this browser receiving alerts?\" — asked of the browser AND of the server.\n *\n * The browser alone is not enough, and answering from it alone is how a real\n * person is silenced while being told they are fine. Two users share a counter\n * PC: Ana enables push, then Otávio signs in on the same browser and enables it\n * too. `PushManager` hands out the SAME endpoint for the same browser profile,\n * so the POST re-owns the row and Ana's is gone. Ana signs back in, her browser\n * still holds the subscription object, and a browser-only check renders \"Este\n * navegador está recebendo alertas.\" with no button to fix it — forever. The\n * 404/410 prune reaches the same state from the other direction.\n *\n * So the server is asked whether it still has THIS endpoint under THIS user. A\n * `false` (or an unreachable server) shows *Ativar* again, and one click\n * re-registers the row — which is the same request the happy path makes, so the\n * recovery costs nothing to maintain.\n */\nasync function resolveState(api: NotificationsApiClient): Promise<SetupState> {\n const subscription = await getExistingPushSubscription();\n if (!subscription) return 'idle';\n const registration = await api\n .getPushRegistration({ endpoint: subscription.endpoint })\n .catch(() => null);\n return registration?.registered ? 'on' : 'idle';\n}\n\nfunction statusText(state: SetupState, messages: NotificationMessages): string {\n if (state === 'on') return messages.devicePushOn;\n if (state === 'denied') return messages.devicePushDenied;\n if (state === 'failed') return messages.devicePushFailed;\n return messages.devicePushIdle;\n}\n\nexport function WebPushDeviceSetup({\n available,\n api,\n messages,\n config,\n}: {\n available: boolean;\n api: NotificationsApiClient;\n messages: NotificationMessages;\n config: WebPushSetupConfig;\n}): JSX.Element | null {\n const [state, setState] = useState<SetupState>('checking');\n // Read once, at mount: neither can change without a new document.\n const [needsInstall] = useState(() => config.needsInstallFirst?.() ?? false);\n\n useEffect(() => {\n let cancelled = false;\n void resolveState(api).then((next) => {\n if (!cancelled) setState(next);\n });\n return () => {\n cancelled = true;\n };\n }, [api]);\n\n if (!available) return null;\n if (needsInstall && config.installHint) return <InstallFirstHint hint={config.installHint} />;\n\n const enable = async (): Promise<void> => {\n setState('busy');\n const result = await enableWebPush(api, config.swPath);\n if (result.ok) setState('on');\n else setState(result.reason === 'permission-denied' ? 'denied' : 'failed');\n };\n\n return (\n <Box\n sx={{\n ...cardSx,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 2,\n }}\n data-testid=\"web-push-device-setup\"\n >\n <Box sx={{ minWidth: 0 }}>\n <Text variant=\"body\" size=\"sm\" weight=\"semibold\" as=\"p\">\n {messages.devicePushTitle}\n </Text>\n <Text variant=\"caption\" size=\"xs\" color=\"secondary\" as=\"p\">\n {statusText(state, messages)}\n </Text>\n </Box>\n {state !== 'on' ? (\n <Button\n variant=\"outline\"\n color=\"primary\"\n size=\"sm\"\n disabled={state === 'busy' || state === 'checking'}\n onClick={() => void enable()}\n dataTestId=\"web-push-enable\"\n >\n {state === 'busy' ? messages.devicePushEnabling : messages.devicePushEnable}\n </Button>\n ) : null}\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","Box","Text","jsx","jsxs","Box","Text","useState","useEffect"]}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { N as NotificationsApiClient } from '../create-web-notifications-
|
|
2
|
-
export { B as BADGE_POLL_MS, a as BADGE_RECONCILE_MS, b as BellButtonProps, I as InboxListStatus, d as InboxState, e as InboxStore, f as NotificationsHttpError, g as NotificationsPanelProps, h as NotificationsResult, i as NotificationsSignalHook, j as NotificationsSubscribe, k as NotificationsTransport, l as NotificationsWebConfig, P as PAGE_SIZE, m as PreferencesPayload, n as PreferencesScreenProps, o as PushRegistrationPayload, W as WebNotifications, p as WebPushPlatformHint, q as WebPushSetupConfig, r as createInboxStore, s as createNotificationsApiClient, c as createWebNotifications, t as httpNotificationsTransport, u as useInboxList, v as useInboxState, w as useUnreadCount } from '../create-web-notifications-
|
|
1
|
+
import { N as NotificationsApiClient } from '../create-web-notifications-DrgqtU4Q.js';
|
|
2
|
+
export { B as BADGE_POLL_MS, a as BADGE_RECONCILE_MS, b as BellButtonProps, I as InboxListStatus, d as InboxState, e as InboxStore, f as NotificationsHttpError, g as NotificationsPanelProps, h as NotificationsResult, i as NotificationsSignalHook, j as NotificationsSubscribe, k as NotificationsTransport, l as NotificationsWebConfig, P as PAGE_SIZE, m as PreferencesPayload, n as PreferencesScreenProps, o as PushRegistrationPayload, W as WebNotifications, p as WebPushPlatformHint, q as WebPushSetupConfig, r as createInboxStore, s as createNotificationsApiClient, c as createWebNotifications, t as httpNotificationsTransport, u as useInboxList, v as useInboxState, w as useUnreadCount } from '../create-web-notifications-DrgqtU4Q.js';
|
|
3
3
|
import { JSX } from 'react';
|
|
4
|
-
import { N as NotificationMessages } from '../wire-
|
|
5
|
-
import '../types-
|
|
4
|
+
import { N as NotificationMessages } from '../wire-CmRrB3_s.js';
|
|
5
|
+
import '../types-CYJN1xCM.js';
|
|
6
6
|
|
|
7
7
|
/** Inline SVG bell (no icon-library dependency in this package). */
|
|
8
8
|
|
package/dist/react/index.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
relativeTime
|
|
3
|
+
} from "../chunk-O5BVUXPO.js";
|
|
4
|
+
import {
|
|
5
|
+
disableWebPush,
|
|
6
|
+
enableWebPush,
|
|
7
|
+
getExistingPushSubscription,
|
|
8
|
+
pushSupported
|
|
9
|
+
} from "../chunk-CQZMTFPY.js";
|
|
10
|
+
import {
|
|
11
|
+
NotificationsHttpError,
|
|
12
|
+
createNotificationsApiClient,
|
|
13
|
+
createWebNotifications,
|
|
14
|
+
httpNotificationsTransport
|
|
15
|
+
} from "../chunk-M5XTROYM.js";
|
|
1
16
|
import {
|
|
2
17
|
BADGE_POLL_MS,
|
|
3
18
|
BADGE_RECONCILE_MS,
|
|
4
19
|
BellIcon,
|
|
5
|
-
NotificationsHttpError,
|
|
6
20
|
PAGE_SIZE,
|
|
7
21
|
createInboxStore,
|
|
8
|
-
createNotificationsApiClient,
|
|
9
|
-
createWebNotifications,
|
|
10
|
-
disableWebPush,
|
|
11
|
-
enableWebPush,
|
|
12
|
-
getExistingPushSubscription,
|
|
13
|
-
httpNotificationsTransport,
|
|
14
|
-
pushSupported,
|
|
15
|
-
relativeTime,
|
|
16
22
|
useInboxList,
|
|
17
23
|
useInboxState,
|
|
18
24
|
useUnreadCount
|
|
19
|
-
} from "../chunk-
|
|
20
|
-
import "../chunk-
|
|
25
|
+
} from "../chunk-BW723CX2.js";
|
|
26
|
+
import "../chunk-M2TVBVH2.js";
|
|
21
27
|
import "../chunk-7QVYU63E.js";
|
|
22
28
|
export {
|
|
23
29
|
BADGE_POLL_MS,
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { A as ApiNotifications } from '../create-api-notifications-
|
|
2
|
-
export { C as CommittedNotification, E as EMAIL_DRIVERS, c as EmailDriver, d as EmailDriverDeclaration, e as EmailMessage, f as ExtraDrivers, L as ListNotificationsInput, g as NotificationAudienceDirectory, h as NotificationChannelPolicy, i as NotificationCommittedListener, j as NotificationContactDirectory, k as NotificationCreateData, l as NotificationDelegate, m as NotificationDeliveryDelegate, n as NotificationDeliveryRow, o as NotificationDeliveryWhere, p as NotificationDispatchScheduler, q as NotificationInboxStore, r as NotificationPageAfter, s as NotificationPreferenceDelegate, t as NotificationPreferenceRow, u as NotificationPreferenceStore, v as NotificationRouter, w as NotificationWhere, a as NotificationsActor, x as NotificationsApiError, y as NotificationsDb, z as NotificationsDbClient, B as NotificationsDbProvider, D as NotificationsRequest, F as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, G as NotifyByPermission, H as NotifyOptions, I as NotifyResult, P as PermissionNotificationResult, J as PermissionNotificationSkip, K as PushSubscriptionDelegate, M as PushSubscriptionInput, O as PushSubscriptionRow, Q as PushSubscriptionStore, S as SMS_DRIVERS, R as SmsDriver, T as SmsDriverDeclaration, U as SmsMessage, V as TransportDeclaration, W as TransportRegistry, X as WHATSAPP_DRIVERS, Y as WhatsAppDriver, Z as WhatsAppDriverDeclaration, _ as WhatsAppMessage, $ as createApiNotifications, a0 as createTransportRegistry, a1 as emailTransport, a2 as foldApiError, a3 as formatEmail, a4 as formatSms, a5 as formatWhatsApp, a6 as ok, a7 as smsTransport, a8 as whatsAppTransport } from '../create-api-notifications-
|
|
3
|
-
export { D as DriverDeclarationBase, F as FetchImpl, N as NotificationProviderError, W as WEB_PUSH_DRIVERS, a as WebPushDriverDeclaration, b as WebPushMessage, c as WebPushSender, d as WebPushSubscription, e as WebPushSubscriptionSource, f as absoluteLink, g as formatWebPush, w as webPushTransport } from '../web-push-
|
|
1
|
+
import { A as ApiNotifications } from '../create-api-notifications-WaexrZnn.js';
|
|
2
|
+
export { C as CommittedNotification, E as EMAIL_DRIVERS, c as EmailDriver, d as EmailDriverDeclaration, e as EmailMessage, f as ExtraDrivers, L as ListNotificationsInput, g as NotificationAudienceDirectory, h as NotificationChannelPolicy, i as NotificationCommittedListener, j as NotificationContactDirectory, k as NotificationCreateData, l as NotificationDelegate, m as NotificationDeliveryDelegate, n as NotificationDeliveryRow, o as NotificationDeliveryWhere, p as NotificationDispatchScheduler, q as NotificationInboxStore, r as NotificationPageAfter, s as NotificationPreferenceDelegate, t as NotificationPreferenceRow, u as NotificationPreferenceStore, v as NotificationRouter, w as NotificationWhere, a as NotificationsActor, x as NotificationsApiError, y as NotificationsDb, z as NotificationsDbClient, B as NotificationsDbProvider, D as NotificationsRequest, F as NotificationsResponse, b as NotificationsRoute, N as NotificationsServerConfig, G as NotifyByPermission, H as NotifyOptions, I as NotifyResult, P as PermissionNotificationResult, J as PermissionNotificationSkip, K as PushSubscriptionDelegate, M as PushSubscriptionInput, O as PushSubscriptionRow, Q as PushSubscriptionStore, S as SMS_DRIVERS, R as SmsDriver, T as SmsDriverDeclaration, U as SmsMessage, V as TransportDeclaration, W as TransportRegistry, X as WHATSAPP_DRIVERS, Y as WhatsAppDriver, Z as WhatsAppDriverDeclaration, _ as WhatsAppMessage, $ as createApiNotifications, a0 as createTransportRegistry, a1 as emailTransport, a2 as foldApiError, a3 as formatEmail, a4 as formatSms, a5 as formatWhatsApp, a6 as ok, a7 as smsTransport, a8 as whatsAppTransport } from '../create-api-notifications-WaexrZnn.js';
|
|
3
|
+
export { D as DriverDeclarationBase, F as FetchImpl, N as NotificationProviderError, W as WEB_PUSH_DRIVERS, a as WebPushDriverDeclaration, b as WebPushMessage, c as WebPushSender, d as WebPushSubscription, e as WebPushSubscriptionSource, f as absoluteLink, g as formatWebPush, w as webPushTransport } from '../web-push-iER5StBm.js';
|
|
4
4
|
import { NotifyPort } from '@12-apps/wiring/ports';
|
|
5
|
-
export { a as NOTIFICATIONS_DRAIN_CRON, b as NOTIFICATIONS_DRAIN_LEASE_MS, c as NOTIFICATIONS_JOBS, d as NOTIFICATIONS_SWEEP_QUEUE, N as NotificationsJobDeps } from '../jobs-
|
|
6
|
-
import '../generators-
|
|
7
|
-
import '../types-
|
|
8
|
-
import '../wire-
|
|
5
|
+
export { a as NOTIFICATIONS_DRAIN_CRON, b as NOTIFICATIONS_DRAIN_LEASE_MS, c as NOTIFICATIONS_JOBS, d as NOTIFICATIONS_SWEEP_QUEUE, N as NotificationsJobDeps } from '../jobs-CcsSA0YR.js';
|
|
6
|
+
import '../generators-CH5m8Ucj.js';
|
|
7
|
+
import '../types-CYJN1xCM.js';
|
|
8
|
+
import '../wire-CmRrB3_s.js';
|
|
9
9
|
import '@12-apps/wiring';
|
|
10
10
|
|
|
11
11
|
/**
|
package/dist/server/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
NOTIFICATIONS_JOBS,
|
|
5
5
|
NOTIFICATIONS_SWEEP_QUEUE,
|
|
6
6
|
wireNotifyPort
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-KU4FVOFA.js";
|
|
8
8
|
import {
|
|
9
9
|
EMAIL_DRIVERS,
|
|
10
10
|
NotificationProviderError,
|
|
@@ -25,9 +25,10 @@ import {
|
|
|
25
25
|
smsTransport,
|
|
26
26
|
webPushTransport,
|
|
27
27
|
whatsAppTransport
|
|
28
|
-
} from "../chunk-
|
|
29
|
-
import "../chunk-
|
|
30
|
-
import "../chunk-
|
|
28
|
+
} from "../chunk-7234ROOP.js";
|
|
29
|
+
import "../chunk-GGAZOLNN.js";
|
|
30
|
+
import "../chunk-BGI4BQ66.js";
|
|
31
|
+
import "../chunk-M2TVBVH2.js";
|
|
31
32
|
import "../chunk-7QVYU63E.js";
|
|
32
33
|
export {
|
|
33
34
|
EMAIL_DRIVERS,
|
|
@@ -84,12 +84,44 @@ interface NotificationEvent<TPayload = unknown> {
|
|
|
84
84
|
* generator never touches existing generators, the router, or any transport
|
|
85
85
|
* (open/closed).
|
|
86
86
|
*/
|
|
87
|
+
/**
|
|
88
|
+
* Who the content is being rendered FOR — the reader, at the moment the
|
|
89
|
+
* generator is asked.
|
|
90
|
+
*
|
|
91
|
+
* A notification is stored as rendered TEXT: title and body are columns, so
|
|
92
|
+
* the language is chosen once, when the row is written, and never again. That
|
|
93
|
+
* makes this the only honest place to ask. A generator is registered at BOOT —
|
|
94
|
+
* a host that resolved its words there would pin every future reader to
|
|
95
|
+
* whichever language the process happened to start in, invisibly, because a
|
|
96
|
+
* single-locale host cannot tell the difference.
|
|
97
|
+
*
|
|
98
|
+
* The tag is the RECIPIENT's, never the request's. The person who triggers a
|
|
99
|
+
* notification is routinely not the person who reads it: an invite is sent
|
|
100
|
+
* because an administrator acted and is read by the invitee. Reading
|
|
101
|
+
* `Accept-Language` here would be a bug that only ever surfaces as somebody
|
|
102
|
+
* being told things in a language they do not speak.
|
|
103
|
+
*
|
|
104
|
+
* Absent means "nobody said" — a host with one audience, or one that stores no
|
|
105
|
+
* per-user language, populates nothing and every generator answers with its
|
|
106
|
+
* own default exactly as it did before this existed.
|
|
107
|
+
*/
|
|
108
|
+
interface NotificationGenerateContext {
|
|
109
|
+
readonly locale?: string | null;
|
|
110
|
+
}
|
|
87
111
|
interface NotificationGenerator<TPayload = unknown> {
|
|
88
112
|
/** The event key, dot-namespaced ("order.paid"). One generator per type. */
|
|
89
113
|
type: string;
|
|
90
114
|
/** The preference category the router gates this type's fan-out on. */
|
|
91
115
|
category: NotificationCategory;
|
|
92
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Render this event's content for ONE recipient.
|
|
118
|
+
*
|
|
119
|
+
* `context` is OPTIONAL, and that is what keeps every generator written
|
|
120
|
+
* before it working: a one-parameter `generate` is assignable to this
|
|
121
|
+
* signature unchanged. A host that passes nothing is stating a fact — it has
|
|
122
|
+
* no language for this reader — rather than asserting a default.
|
|
123
|
+
*/
|
|
124
|
+
generate: (payload: TPayload, context?: NotificationGenerateContext) => NotificationContent;
|
|
93
125
|
}
|
|
94
126
|
/**
|
|
95
127
|
* The recipient as a transport sees them: resolved destinations only. Built by
|
|
@@ -100,6 +132,11 @@ interface NotificationGenerator<TPayload = unknown> {
|
|
|
100
132
|
interface TransportRecipient {
|
|
101
133
|
userId: string;
|
|
102
134
|
email: string | null;
|
|
135
|
+
/**
|
|
136
|
+
* The recipient's own language, when the host's contact directory states
|
|
137
|
+
* one. Absent means "nobody said" — see {@link NotificationGenerateContext}.
|
|
138
|
+
*/
|
|
139
|
+
locale?: string | null;
|
|
103
140
|
/** Phone as the host stores it (transports normalize per provider rules). */
|
|
104
141
|
phone: string | null;
|
|
105
142
|
/** How many active browser push subscriptions the user holds. */
|
|
@@ -152,4 +189,4 @@ interface NotificationLogger {
|
|
|
152
189
|
error(message: string, ...meta: unknown[]): void;
|
|
153
190
|
}
|
|
154
191
|
|
|
155
|
-
export { type DeliveryStatus as D, NOTIFICATION_CHANNELS as N, type TransportRecipient as T, type NotificationCategory as a, type NotificationChannel as b, type NotificationContent as c, type NotificationEvent as d, type
|
|
192
|
+
export { type DeliveryStatus as D, NOTIFICATION_CHANNELS as N, type TransportRecipient as T, type NotificationCategory as a, type NotificationChannel as b, type NotificationContent as c, type NotificationEvent as d, type NotificationGenerateContext as e, type NotificationGenerator as f, type NotificationLogger as g, type NotificationRecipient as h, type NotificationTaxonomy as i, type NotificationTransport as j, taxonomyOf as t };
|
package/dist/web-push/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as WebPushSender } from '../web-push-
|
|
2
|
-
import '../types-
|
|
1
|
+
import { c as WebPushSender } from '../web-push-iER5StBm.js';
|
|
2
|
+
import '../types-CYJN1xCM.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `@12-apps/notifications/web-push` — the VAPID sender, behind its own subpath.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { g as NotificationLogger, c as NotificationContent, j as NotificationTransport } from './types-CYJN1xCM.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The DRIVER port — the reason a host adds a vendor with a config entry and no
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/notifications",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Plug-and-play notification system (12-15): an always-on in-app inbox, per-user × per-category channel preferences, and email / SMS / WhatsApp / web-push transports behind vendor DRIVERS so a second provider is a config entry. Framework-free core (.), host-mounted backend surface (./server: inbox / preferences / push-subscription endpoints, the channel router with delivery records + retry sweep, the permission fan-out, duck-typed Prisma seam), Hono adapter (./hono), React surface (./react: bell + badge, inbox drawer, preferences screen), VAPID sender (./web-push) and the package-owned Prisma partial + migrations. Standardized adoption contract in ADOPTING.md.",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"prisma:sync:check": "node scripts/sync-notifications-schema.mjs --check"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@12-apps/ui": "^6.11.
|
|
54
|
+
"@12-apps/ui": "^6.11.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@12-apps/wiring": ">=1.3.0",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@12-apps/eslint-config": "^1.22.0",
|
|
82
82
|
"@12-apps/typescript-config": "^1.21.0",
|
|
83
|
-
"@12-apps/wiring": "^1.
|
|
83
|
+
"@12-apps/wiring": "^1.16.0",
|
|
84
84
|
"@testing-library/react": "^16.1.0",
|
|
85
85
|
"@types/react": "19.2.2",
|
|
86
86
|
"@types/react-dom": "19.2.2",
|