strapi-plugin-notifier 1.0.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.
@@ -0,0 +1,310 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useRef, useCallback, useEffect } from "react";
3
+ import { Box, Flex, Typography, Button, IconButton, Loader } from "@strapi/design-system";
4
+ import "react-dom/client";
5
+ import { Cross } from "@strapi/icons";
6
+ import { u as useFetchClient, g as getConfig, s as setNotifications, a as getNotifications, m as markAsRead, b as markAllAsRead, c as clearNotification, d as clearAll, e as subscribeToNotifications, f as usePluginConfig } from "./index-CNYabBMJ.mjs";
7
+ import "@strapi/icons/symbols";
8
+ const toStoreItem = (n) => ({
9
+ id: String(n.id),
10
+ title: n.title,
11
+ message: n.message ?? "",
12
+ type: n.type ?? "info",
13
+ read: n.read,
14
+ createdAt: n.createdAt,
15
+ url: n.url
16
+ });
17
+ const useNotificationActions = () => {
18
+ const { get, put, del } = useFetchClient();
19
+ const [isLoading, setIsLoading] = useState(false);
20
+ const [pagination, setPagination] = useState(null);
21
+ const currentPage = useRef(1);
22
+ const fetchPage = useCallback(
23
+ async (page, append = false) => {
24
+ const { pageSize } = getConfig();
25
+ setIsLoading(true);
26
+ try {
27
+ const { data } = await get(
28
+ `/notifier/notifications?page=${page}&pageSize=${pageSize}`
29
+ );
30
+ if (Array.isArray(data?.data)) {
31
+ const incoming = data.data.map(toStoreItem);
32
+ if (append) {
33
+ setNotifications([...getNotifications(), ...incoming]);
34
+ } else {
35
+ setNotifications(incoming);
36
+ }
37
+ setPagination(data.pagination ?? null);
38
+ currentPage.current = page;
39
+ }
40
+ } finally {
41
+ setIsLoading(false);
42
+ }
43
+ },
44
+ [get]
45
+ );
46
+ const fetchAll = useCallback(() => fetchPage(1, false), [fetchPage]);
47
+ const loadMore = useCallback(() => {
48
+ if (!pagination || currentPage.current >= pagination.pageCount) return;
49
+ fetchPage(currentPage.current + 1, true);
50
+ }, [fetchPage, pagination]);
51
+ const handleMarkAsRead = useCallback(
52
+ async (id) => {
53
+ markAsRead(id);
54
+ try {
55
+ await put(`/notifier/notifications/${id}/read`);
56
+ } catch {
57
+ }
58
+ },
59
+ [put]
60
+ );
61
+ const handleMarkAllAsRead = useCallback(async () => {
62
+ markAllAsRead();
63
+ try {
64
+ await put("/notifier/notifications/read-all");
65
+ } catch {
66
+ }
67
+ }, [put]);
68
+ const handleClear = useCallback(
69
+ async (id) => {
70
+ clearNotification(id);
71
+ try {
72
+ await del(`/notifier/notifications/${id}`);
73
+ } catch {
74
+ }
75
+ },
76
+ [del]
77
+ );
78
+ const handleClearAll = useCallback(async () => {
79
+ clearAll();
80
+ try {
81
+ await del("/notifier/notifications");
82
+ } catch {
83
+ }
84
+ }, [del]);
85
+ const hasMore = pagination ? currentPage.current < pagination.pageCount : false;
86
+ return {
87
+ fetchAll,
88
+ loadMore,
89
+ handleMarkAsRead,
90
+ handleMarkAllAsRead,
91
+ handleClear,
92
+ handleClearAll,
93
+ isLoading,
94
+ hasMore
95
+ };
96
+ };
97
+ const FILTERS = ["all", "unread", "info", "success", "warning", "error"];
98
+ const FILTER_LABELS = {
99
+ all: "All",
100
+ unread: "Unread",
101
+ info: "Info",
102
+ success: "Success",
103
+ warning: "Warning",
104
+ error: "Error"
105
+ };
106
+ function InboxHeader({
107
+ filter,
108
+ onFilterChange,
109
+ onMarkAllAsRead,
110
+ onClearAll
111
+ }) {
112
+ return /* @__PURE__ */ jsxs(Box, { paddingBottom: 4, children: [
113
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
114
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", children: "Notifications" }),
115
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
116
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "S", onClick: onMarkAllAsRead, children: "Mark all as read" }),
117
+ /* @__PURE__ */ jsx(
118
+ Button,
119
+ {
120
+ variant: "danger-light",
121
+ size: "S",
122
+ onClick: onClearAll,
123
+ children: "Clear all"
124
+ }
125
+ )
126
+ ] })
127
+ ] }),
128
+ /* @__PURE__ */ jsx(Flex, { gap: 2, wrap: "wrap", children: FILTERS.map((f) => /* @__PURE__ */ jsx(
129
+ "button",
130
+ {
131
+ onClick: () => onFilterChange(f),
132
+ style: {
133
+ padding: "4px 12px",
134
+ borderRadius: "16px",
135
+ border: "1px solid",
136
+ borderColor: filter === f ? "#4945ff" : "#dcdce4",
137
+ backgroundColor: filter === f ? "#f0f0ff" : "transparent",
138
+ color: filter === f ? "#4945ff" : "#32324d",
139
+ cursor: "pointer",
140
+ fontSize: "12px",
141
+ fontWeight: filter === f ? 600 : 400,
142
+ transition: "all 0.15s"
143
+ },
144
+ children: FILTER_LABELS[f]
145
+ },
146
+ f
147
+ )) })
148
+ ] });
149
+ }
150
+ const useNotifications = () => {
151
+ const [notifications, setNotifications2] = useState(getNotifications);
152
+ useEffect(() => subscribeToNotifications(setNotifications2), []);
153
+ return notifications;
154
+ };
155
+ const UNREAD_BG = {
156
+ info: "#f0f0ff",
157
+ success: "#f0fff4",
158
+ warning: "#fffbf0",
159
+ error: "#fff5f5"
160
+ };
161
+ function NotificationItem({
162
+ notification,
163
+ onMarkAsRead,
164
+ onClear
165
+ }) {
166
+ const config = usePluginConfig();
167
+ const accent = config.theme.accent[notification.type];
168
+ const handleClick = () => {
169
+ if (!notification.read) onMarkAsRead(notification.id);
170
+ if (notification.url) {
171
+ if (notification.url.startsWith("http")) {
172
+ window.open(notification.url, "_blank", "noopener,noreferrer");
173
+ } else {
174
+ window.location.href = notification.url;
175
+ }
176
+ }
177
+ };
178
+ const handleDismiss = (e) => {
179
+ e.stopPropagation();
180
+ onClear(notification.id);
181
+ };
182
+ const bg = !notification.read ? UNREAD_BG[notification.type] : "transparent";
183
+ return /* @__PURE__ */ jsx(
184
+ Box,
185
+ {
186
+ onClick: handleClick,
187
+ style: {
188
+ borderLeft: `4px solid ${accent}`,
189
+ backgroundColor: bg,
190
+ cursor: notification.url ? "pointer" : "default",
191
+ transition: "background 0.15s"
192
+ },
193
+ padding: 4,
194
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "flex-start", gap: 2, children: [
195
+ /* @__PURE__ */ jsxs(Box, { style: { flex: 1, minWidth: 0 }, children: [
196
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, alignItems: "center", children: [
197
+ /* @__PURE__ */ jsx(
198
+ Typography,
199
+ {
200
+ variant: "omega",
201
+ fontWeight: notification.read ? "normal" : "bold",
202
+ style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
203
+ children: notification.title
204
+ }
205
+ ),
206
+ notification.url && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", children: "↗" })
207
+ ] }),
208
+ notification.message && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", style: { marginTop: 2 }, children: notification.message }),
209
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral400", style: { marginTop: 4 }, children: new Date(notification.createdAt).toLocaleString() })
210
+ ] }),
211
+ /* @__PURE__ */ jsx(
212
+ IconButton,
213
+ {
214
+ label: "Dismiss",
215
+ onClick: handleDismiss,
216
+ variant: "ghost",
217
+ size: "S",
218
+ children: /* @__PURE__ */ jsx(Cross, {})
219
+ }
220
+ )
221
+ ] })
222
+ }
223
+ );
224
+ }
225
+ const MESSAGES = {
226
+ all: "No notifications yet.",
227
+ unread: "No unread notifications.",
228
+ info: "No info notifications.",
229
+ success: "No success notifications.",
230
+ warning: "No warning notifications.",
231
+ error: "No error notifications."
232
+ };
233
+ function EmptyInbox({ filter = "all" }) {
234
+ return /* @__PURE__ */ jsx(Box, { padding: 8, style: { textAlign: "center" }, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: MESSAGES[filter] }) });
235
+ }
236
+ const applyFilter = (items, filter) => {
237
+ if (filter === "all") return items;
238
+ if (filter === "unread") return items.filter((n) => !n.read);
239
+ return items.filter((n) => n.type === filter);
240
+ };
241
+ function NotificationList({
242
+ filter,
243
+ onMarkAsRead,
244
+ onClear,
245
+ isLoading,
246
+ hasMore,
247
+ onLoadMore
248
+ }) {
249
+ const notifications = useNotifications();
250
+ const filtered = applyFilter(notifications, filter);
251
+ if (isLoading && notifications.length === 0) {
252
+ return /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Loader, { small: true, children: "Loading notifications…" }) });
253
+ }
254
+ if (filtered.length === 0) {
255
+ return /* @__PURE__ */ jsx(EmptyInbox, { filter });
256
+ }
257
+ return /* @__PURE__ */ jsxs(Box, { children: [
258
+ filtered.map((n) => /* @__PURE__ */ jsx(
259
+ NotificationItem,
260
+ {
261
+ notification: n,
262
+ onMarkAsRead,
263
+ onClear
264
+ },
265
+ n.id
266
+ )),
267
+ hasMore && /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: onLoadMore, loading: isLoading, children: isLoading ? "Loading…" : "Load more" }) })
268
+ ] });
269
+ }
270
+ function Index() {
271
+ const [filter, setFilter] = useState("all");
272
+ const {
273
+ fetchAll,
274
+ loadMore,
275
+ handleMarkAsRead,
276
+ handleMarkAllAsRead,
277
+ handleClear,
278
+ handleClearAll,
279
+ isLoading,
280
+ hasMore
281
+ } = useNotificationActions();
282
+ useEffect(() => {
283
+ fetchAll();
284
+ }, []);
285
+ return /* @__PURE__ */ jsxs(Box, { padding: 8, style: { height: "100%" }, children: [
286
+ /* @__PURE__ */ jsx(
287
+ InboxHeader,
288
+ {
289
+ filter,
290
+ onFilterChange: setFilter,
291
+ onMarkAllAsRead: handleMarkAllAsRead,
292
+ onClearAll: handleClearAll
293
+ }
294
+ ),
295
+ /* @__PURE__ */ jsx(
296
+ NotificationList,
297
+ {
298
+ filter,
299
+ onMarkAsRead: handleMarkAsRead,
300
+ onClear: handleClear,
301
+ isLoading,
302
+ hasMore,
303
+ onLoadMore: loadMore
304
+ }
305
+ )
306
+ ] });
307
+ }
308
+ export {
309
+ Index as default
310
+ };
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const React = require("react");
5
+ const designSystem = require("@strapi/design-system");
6
+ require("react-dom/client");
7
+ require("@strapi/icons");
8
+ const index = require("./index-7WJGsVEY.js");
9
+ require("@strapi/icons/symbols");
10
+ const DEFAULT = {
11
+ retention: { maxDays: 90, maxPerUser: 500 },
12
+ delivery: { pollIntervalMs: 3e4, pageSize: 20 },
13
+ ui: { theme: { accent: { info: "#4945ff", success: "#5cb85c", warning: "#f0ad4e", error: "#ee5e52" } } }
14
+ };
15
+ function SettingsPage() {
16
+ const { get, put, del } = index.useFetchClient();
17
+ const [settings, setSettings] = React.useState(DEFAULT);
18
+ const [isSaving, setIsSaving] = React.useState(false);
19
+ const [isResetting, setIsResetting] = React.useState(false);
20
+ const [saveError, setSaveError] = React.useState(null);
21
+ React.useEffect(() => {
22
+ get("/notifier/settings").then(({ data }) => {
23
+ if (data) setSettings(data);
24
+ }).catch(() => {
25
+ });
26
+ }, [get]);
27
+ const handleSave = async () => {
28
+ setSaveError(null);
29
+ setIsSaving(true);
30
+ try {
31
+ const { data } = await put("/notifier/settings", { data: settings });
32
+ if (data) setSettings(data);
33
+ } catch (e) {
34
+ setSaveError(e?.message ?? "Failed to save settings.");
35
+ } finally {
36
+ setIsSaving(false);
37
+ }
38
+ };
39
+ const handleReset = async () => {
40
+ setIsResetting(true);
41
+ try {
42
+ await del("/notifier/settings");
43
+ const { data } = await get("/notifier/settings");
44
+ if (data) setSettings(data);
45
+ } finally {
46
+ setIsResetting(false);
47
+ }
48
+ };
49
+ const set = (path, value) => {
50
+ setSettings((prev) => {
51
+ const next = JSON.parse(JSON.stringify(prev));
52
+ const keys = path.split(".");
53
+ let obj = next;
54
+ for (let i = 0; i < keys.length - 1; i++) obj = obj[keys[i]];
55
+ obj[keys[keys.length - 1]] = value;
56
+ return next;
57
+ });
58
+ };
59
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 8, children: [
60
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 6, children: [
61
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", children: "Notifier Settings" }),
62
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
63
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: handleReset, loading: isResetting, children: "Reset to defaults" }),
64
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: handleSave, loading: isSaving, children: "Save" })
65
+ ] })
66
+ ] }),
67
+ saveError && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger600", children: saveError }) }),
68
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 6, hasRadius: true, children: [
69
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", paddingBottom: 4, as: "h2", children: "Retention" }),
70
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", children: [
71
+ /* @__PURE__ */ jsxRuntime.jsx(
72
+ designSystem.NumberInput,
73
+ {
74
+ label: "Max age (days)",
75
+ hint: "Notifications older than this will be deleted by the nightly cron.",
76
+ value: settings.retention.maxDays,
77
+ onValueChange: (v) => set("retention.maxDays", v),
78
+ style: { flex: 1 }
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsxRuntime.jsx(
82
+ designSystem.NumberInput,
83
+ {
84
+ label: "Max per user",
85
+ hint: "Cap on notifications stored per user. Oldest are removed first.",
86
+ value: settings.retention.maxPerUser,
87
+ onValueChange: (v) => set("retention.maxPerUser", v),
88
+ style: { flex: 1 }
89
+ }
90
+ )
91
+ ] })
92
+ ] }),
93
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 6, hasRadius: true, marginTop: 4, children: [
94
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", paddingBottom: 4, as: "h2", children: "Delivery" }),
95
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", children: [
96
+ /* @__PURE__ */ jsxRuntime.jsx(
97
+ designSystem.NumberInput,
98
+ {
99
+ label: "Poll interval (ms)",
100
+ hint: "How often the Bell polls the server for new notifications.",
101
+ value: settings.delivery.pollIntervalMs,
102
+ onValueChange: (v) => set("delivery.pollIntervalMs", v),
103
+ style: { flex: 1 }
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsxRuntime.jsx(
107
+ designSystem.NumberInput,
108
+ {
109
+ label: "Page size",
110
+ hint: "Number of notifications loaded per page in the inbox.",
111
+ value: settings.delivery.pageSize,
112
+ onValueChange: (v) => set("delivery.pageSize", v),
113
+ style: { flex: 1 }
114
+ }
115
+ )
116
+ ] })
117
+ ] }),
118
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 6, hasRadius: true, marginTop: 4, children: [
119
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", paddingBottom: 4, as: "h2", children: "Theme" }),
120
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
121
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 4, children: [
122
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", paddingBottom: 3, children: "Accent colours (hex)" }),
123
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 4, wrap: "wrap", children: ["info", "success", "warning", "error"].map((type) => /* @__PURE__ */ jsxRuntime.jsx(
124
+ designSystem.TextInput,
125
+ {
126
+ label: type.charAt(0).toUpperCase() + type.slice(1),
127
+ value: settings.ui.theme.accent[type],
128
+ onChange: (e) => set(`ui.theme.accent.${type}`, e.target.value),
129
+ style: { flex: 1 }
130
+ },
131
+ type
132
+ )) })
133
+ ] })
134
+ ] })
135
+ ] });
136
+ }
137
+ exports.default = SettingsPage;
@@ -0,0 +1,137 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Box, Flex, Typography, Button, NumberInput, Divider, TextInput } from "@strapi/design-system";
4
+ import "react-dom/client";
5
+ import "@strapi/icons";
6
+ import { u as useFetchClient } from "./index-CNYabBMJ.mjs";
7
+ import "@strapi/icons/symbols";
8
+ const DEFAULT = {
9
+ retention: { maxDays: 90, maxPerUser: 500 },
10
+ delivery: { pollIntervalMs: 3e4, pageSize: 20 },
11
+ ui: { theme: { accent: { info: "#4945ff", success: "#5cb85c", warning: "#f0ad4e", error: "#ee5e52" } } }
12
+ };
13
+ function SettingsPage() {
14
+ const { get, put, del } = useFetchClient();
15
+ const [settings, setSettings] = useState(DEFAULT);
16
+ const [isSaving, setIsSaving] = useState(false);
17
+ const [isResetting, setIsResetting] = useState(false);
18
+ const [saveError, setSaveError] = useState(null);
19
+ useEffect(() => {
20
+ get("/notifier/settings").then(({ data }) => {
21
+ if (data) setSettings(data);
22
+ }).catch(() => {
23
+ });
24
+ }, [get]);
25
+ const handleSave = async () => {
26
+ setSaveError(null);
27
+ setIsSaving(true);
28
+ try {
29
+ const { data } = await put("/notifier/settings", { data: settings });
30
+ if (data) setSettings(data);
31
+ } catch (e) {
32
+ setSaveError(e?.message ?? "Failed to save settings.");
33
+ } finally {
34
+ setIsSaving(false);
35
+ }
36
+ };
37
+ const handleReset = async () => {
38
+ setIsResetting(true);
39
+ try {
40
+ await del("/notifier/settings");
41
+ const { data } = await get("/notifier/settings");
42
+ if (data) setSettings(data);
43
+ } finally {
44
+ setIsResetting(false);
45
+ }
46
+ };
47
+ const set = (path, value) => {
48
+ setSettings((prev) => {
49
+ const next = JSON.parse(JSON.stringify(prev));
50
+ const keys = path.split(".");
51
+ let obj = next;
52
+ for (let i = 0; i < keys.length - 1; i++) obj = obj[keys[i]];
53
+ obj[keys[keys.length - 1]] = value;
54
+ return next;
55
+ });
56
+ };
57
+ return /* @__PURE__ */ jsxs(Box, { padding: 8, children: [
58
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 6, children: [
59
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", children: "Notifier Settings" }),
60
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
61
+ /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: handleReset, loading: isResetting, children: "Reset to defaults" }),
62
+ /* @__PURE__ */ jsx(Button, { onClick: handleSave, loading: isSaving, children: "Save" })
63
+ ] })
64
+ ] }),
65
+ saveError && /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "danger600", children: saveError }) }),
66
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", padding: 6, hasRadius: true, children: [
67
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", paddingBottom: 4, as: "h2", children: "Retention" }),
68
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", children: [
69
+ /* @__PURE__ */ jsx(
70
+ NumberInput,
71
+ {
72
+ label: "Max age (days)",
73
+ hint: "Notifications older than this will be deleted by the nightly cron.",
74
+ value: settings.retention.maxDays,
75
+ onValueChange: (v) => set("retention.maxDays", v),
76
+ style: { flex: 1 }
77
+ }
78
+ ),
79
+ /* @__PURE__ */ jsx(
80
+ NumberInput,
81
+ {
82
+ label: "Max per user",
83
+ hint: "Cap on notifications stored per user. Oldest are removed first.",
84
+ value: settings.retention.maxPerUser,
85
+ onValueChange: (v) => set("retention.maxPerUser", v),
86
+ style: { flex: 1 }
87
+ }
88
+ )
89
+ ] })
90
+ ] }),
91
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", padding: 6, hasRadius: true, marginTop: 4, children: [
92
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", paddingBottom: 4, as: "h2", children: "Delivery" }),
93
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, wrap: "wrap", children: [
94
+ /* @__PURE__ */ jsx(
95
+ NumberInput,
96
+ {
97
+ label: "Poll interval (ms)",
98
+ hint: "How often the Bell polls the server for new notifications.",
99
+ value: settings.delivery.pollIntervalMs,
100
+ onValueChange: (v) => set("delivery.pollIntervalMs", v),
101
+ style: { flex: 1 }
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsx(
105
+ NumberInput,
106
+ {
107
+ label: "Page size",
108
+ hint: "Number of notifications loaded per page in the inbox.",
109
+ value: settings.delivery.pageSize,
110
+ onValueChange: (v) => set("delivery.pageSize", v),
111
+ style: { flex: 1 }
112
+ }
113
+ )
114
+ ] })
115
+ ] }),
116
+ /* @__PURE__ */ jsxs(Box, { background: "neutral0", padding: 6, hasRadius: true, marginTop: 4, children: [
117
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", paddingBottom: 4, as: "h2", children: "Theme" }),
118
+ /* @__PURE__ */ jsx(Divider, {}),
119
+ /* @__PURE__ */ jsxs(Box, { paddingTop: 4, children: [
120
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", paddingBottom: 3, children: "Accent colours (hex)" }),
121
+ /* @__PURE__ */ jsx(Flex, { gap: 4, wrap: "wrap", children: ["info", "success", "warning", "error"].map((type) => /* @__PURE__ */ jsx(
122
+ TextInput,
123
+ {
124
+ label: type.charAt(0).toUpperCase() + type.slice(1),
125
+ value: settings.ui.theme.accent[type],
126
+ onChange: (e) => set(`ui.theme.accent.${type}`, e.target.value),
127
+ style: { flex: 1 }
128
+ },
129
+ type
130
+ )) })
131
+ ] })
132
+ ] })
133
+ ] });
134
+ }
135
+ export {
136
+ SettingsPage as default
137
+ };