@aglyn/plugins-events-calendar 1.0.0-beta.221 → 1.0.0-beta.223

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-events-calendar",
3
- "version": "1.0.0-beta.221",
3
+ "version": "1.0.0-beta.223",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,14 +25,14 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.221",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.221",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.221",
31
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.221",
32
- "@aglyn/shared-util-timestamp": "1.0.0-beta.221",
33
- "@aglyn/tenant-data-admin": "1.0.0-beta.221",
34
- "@aglyn/tenant-feature-instance": "1.0.0-beta.221",
35
- "@aglyn/tenant-runtime": "1.0.0-beta.221",
28
+ "@aglyn/aglyn": "1.0.0-beta.223",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.223",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.223",
31
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.223",
32
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.223",
33
+ "@aglyn/tenant-data-admin": "1.0.0-beta.223",
34
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.223",
35
+ "@aglyn/tenant-runtime": "1.0.0-beta.223",
36
36
  "@swc/helpers": "0.5.23"
37
37
  },
38
38
  "peerDependencies": {
@@ -16,7 +16,8 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- import { createResourceUid, MEDIA_ALT_MAX_LENGTH, pluginDocsHelp } from "@aglyn/aglyn";
19
+ import { TransferResumeImport } from "@aglyn/aglyn/app-utils/transfer-resume-import";
20
+ import { createResourceUid, MEDIA_ALT_MAX_LENGTH, pluginDocsHelp, useTransferLauncher } from "@aglyn/aglyn";
20
21
  import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
21
22
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
23
  import { Timestamp } from "@aglyn/shared-util-timestamp";
@@ -26,6 +27,8 @@ import { collection, deleteField, doc, limit, orderBy, query, setDoc, updateDoc
26
27
  import { useCallback, useMemo, useState } from "react";
27
28
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
28
29
  import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
30
+ import { eventWrite } from "../model/event-write.js";
31
+ import { EVENTS_TRANSFER_RESOURCE } from "../transfer/events-transfer-key.js";
29
32
  /**
30
33
  * How many event documents this page reads.
31
34
  *
@@ -63,11 +66,12 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
63
66
  * ones whose ids sorted late, including the next one happening.
64
67
  *
65
68
  * `orderBy` DROPS documents missing the field, so it is only safe
66
- * once every writer is known to set it. All three checks pass for
67
- * `startsAtMs`: the only writer refuses a save without it (the
68
- * guard a few lines below), the server feed already orders by it,
69
- * and `events` is not in `IMPORTABLE_FIELDS`, so nothing else
70
- * writes a row here.
69
+ * once every writer is known to set it, and every one does for
70
+ * `startsAtMs`. The editor refuses a save without it (the guard
71
+ * in `handleSave`); the events import writes through the same
72
+ * rule (`model/event-write.ts`), which refuses a row without one;
73
+ * a site package restore writes the stored events it carries,
74
+ * start included. The server feed already orders by it.
71
75
  */ orderBy('startsAtMs', 'desc'), /*
72
76
  * One document more than the ceiling, so "there is more than
73
77
  * this" is a fact rather than a comparison against the cap —
@@ -139,6 +143,38 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
139
143
  pageSize
140
144
  ]);
141
145
  const [draft, setDraft] = useState(null);
146
+ /*
147
+ * Import and Export open the console's wizard and dialog on this site's
148
+ * events. Outside the console shell there is no launcher, and no buttons.
149
+ * The list needs no refresh after an import: it is a live listener.
150
+ */ const transfer = useTransferLauncher();
151
+ const transferTarget = {
152
+ resource: EVENTS_TRANSFER_RESOURCE,
153
+ scope: 'host',
154
+ hostId
155
+ };
156
+ // Import for those who may write the records, Export for those who may read them.
157
+ const canImport = Boolean(transfer == null ? void 0 : transfer.can('import', transferTarget));
158
+ const canExport = Boolean(transfer == null ? void 0 : transfer.can('export', transferTarget));
159
+ const transferActions = transfer && (canImport || canExport) ? /*#__PURE__*/ _jsxs(Stack, {
160
+ direction: "row",
161
+ spacing: 1,
162
+ children: [
163
+ /*#__PURE__*/ _jsx(TransferResumeImport, {
164
+ target: transferTarget
165
+ }),
166
+ canImport && /*#__PURE__*/ _jsx(Button, {
167
+ size: "small",
168
+ onClick: ()=>transfer.openImport(transferTarget),
169
+ children: 'Import'
170
+ }),
171
+ canExport && /*#__PURE__*/ _jsx(Button, {
172
+ size: "small",
173
+ onClick: ()=>transfer.openExport(transferTarget),
174
+ children: 'Export'
175
+ })
176
+ ]
177
+ }) : null;
142
178
  const handleSave = useCallback(async ()=>{
143
179
  if (!draft || !draft.title.trim()) return;
144
180
  const startsAtMs = draft.startsAt ? new Date(draft.startsAt).getTime() : 0;
@@ -170,27 +206,26 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
170
206
  unreadable: Boolean(draft.id) && eventsUnreadable,
171
207
  fromCache: Boolean(draft.id) && eventsFromCache
172
208
  }, async ()=>{
173
- await setDoc(doc(firestore, 'hosts', hostId, 'events', id), _extends({
174
- title: draft.title.trim().slice(0, 150),
175
- startsAtMs
176
- }, endsAtMs > startsAtMs ? {
177
- endsAtMs
178
- } : {
179
- endsAtMs: startsAtMs + 60 * 60 * 1000
180
- }, draft.location.trim() && {
181
- location: draft.location.trim().slice(0, 200)
182
- }, draft.organizer.trim() && {
183
- organizer: draft.organizer.trim().slice(0, 100)
184
- }, draft.description.trim() && {
185
- description: draft.description.trim().slice(0, 2000)
186
- }, draft.coverImage.trim() && {
187
- coverImage: draft.coverImage.trim()
188
- }, draft.coverImage.trim() && draft.coverImageAlt.trim() ? {
189
- coverImageAlt: draft.coverImageAlt.trim().slice(0, MEDIA_ALT_MAX_LENGTH)
190
- } : {
191
- coverImageAlt: deleteField()
192
- }, {
193
- status: draft.status,
209
+ // The stored form is the shared rule's (`model/event-write.ts`),
210
+ // which the events import writes through too. This is a `merge`
211
+ // write, so whatever the rule removes has to be DELETED here —
212
+ // leaving the key out would keep it (AGL-2418: a cover
213
+ // description outliving its cover).
214
+ const write = eventWrite({
215
+ title: draft.title,
216
+ startsAtMs,
217
+ endsAtMs,
218
+ location: draft.location,
219
+ organizer: draft.organizer,
220
+ description: draft.description,
221
+ coverImage: draft.coverImage,
222
+ coverImageAlt: draft.coverImageAlt,
223
+ status: draft.status
224
+ });
225
+ await setDoc(doc(firestore, 'hosts', hostId, 'events', id), _extends({}, write.fields, Object.fromEntries(write.remove.map((key)=>[
226
+ key,
227
+ deleteField()
228
+ ])), {
194
229
  updatedAt: Timestamp.now()
195
230
  }, draft.id ? {} : {
196
231
  createdAt: Timestamp.now()
@@ -253,6 +288,9 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
253
288
  help: pluginDocsHelp('events', {
254
289
  anchor: '#manage-events'
255
290
  }),
291
+ HeaderProps: transferActions ? {
292
+ action: transferActions
293
+ } : undefined,
256
294
  contentGutterX: true,
257
295
  contentGutterY: true,
258
296
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/events-calendar/src/lib/components/events-console-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n createResourceUid,\n MEDIA_ALT_MAX_LENGTH,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { type ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n ceilingedWindow,\n useFirestore,\n useFirestoreCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteField,\n doc,\n limit,\n orderBy,\n query,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\n\n/**\n * How many event documents this page reads.\n *\n * A CEILING, not a page size: the rows are filtered for soft deletes and\n * re-sorted after reading, so a server page would arrive holding anywhere\n * from zero to ten events and the count under it would be about candidates\n * rather than about events.\n */\nconst EVENT_CEILING = 200\n\ninterface EventDraft {\n id: string | null\n title: string\n startsAt: string\n endsAt: string\n location: string\n organizer: string\n description: string\n coverImage: string\n coverImageAlt: string\n status: 'draft' | 'published'\n}\n\ninterface EventRecord {\n $id: string\n title?: string\n startsAtMs?: number\n endsAtMs?: number\n location?: string\n organizer?: string\n description?: string\n coverImage?: string\n coverImageAlt?: string\n /** `deleted` is written by the delete, beside `deletedAt`. */\n status?: 'draft' | 'published' | 'deleted'\n deletedAt?: unknown\n}\n\n/**\n * A host's events.\n *\n * This was a hand-rolled `onSnapshot` with its own retry, and it carried a\n * FOURTH copy of the AGL-1066 defect: `attempt = 0` in the success handler.\n * Under `persistentLocalCache` the cached emission that precedes every\n * refusal handed the budget back, so `unreadable` — the whole point of the\n * counter — could not fire in the one state that needed it, a populated\n * editor over a read that had stopped working.\n *\n * Rather than repair a fourth counter, the copy is gone. `useFirestoreCollection`\n * is the same `onSnapshot`-plus-retry that absorbs the AGL-216/217\n * post-sign-in denial, and it carries the signals this page's save needs\n * (`fromCache`, and `serverDenied` for the refusal the budget cannot yet\n * express) plus denial reporting into `session-health` this page never had.\n * There was never a reason it could not: the file has imported from\n * `@aglyn/tenant-feature-instance` all along.\n */\nfunction useHostEvents(hostId: string): {\n events: EventRecord[]\n truncated: boolean\n fromCache: boolean\n unreadable: boolean\n} {\n const firestore = useFirestore()\n const { data, status, fromCache } = useFirestoreCollection<EventRecord>(\n () =>\n hostId\n ? query(\n collection(firestore, 'hosts', hostId, 'events'),\n /*\n * ORDERED, so the window is the newest 200 rather than a sample.\n *\n * A `limit()` with no `orderBy` returns documents in ID order, so\n * this was reading an arbitrary 200 of the collection and then\n * sorting them by date in the browser — which looks newest-first\n * and is not: a site past 200 events would simply never see the\n * ones whose ids sorted late, including the next one happening.\n *\n * `orderBy` DROPS documents missing the field, so it is only safe\n * once every writer is known to set it. All three checks pass for\n * `startsAtMs`: the only writer refuses a save without it (the\n * guard a few lines below), the server feed already orders by it,\n * and `events` is not in `IMPORTABLE_FIELDS`, so nothing else\n * writes a row here.\n */\n orderBy('startsAtMs', 'desc'),\n /*\n * One document more than the ceiling, so \"there is more than\n * this\" is a fact rather than a comparison against the cap —\n * which is wrong in exactly the case it matters, a site holding\n * precisely 200 events. The probe row is dropped below and is\n * never rendered or counted.\n */\n limit(EVENT_CEILING + 1),\n )\n : null,\n [firestore, hostId],\n { idField: '$id' },\n )\n\n const { rows, truncated } = ceilingedWindow<EventRecord>(data, EVENT_CEILING)\n return { events: rows, truncated, fromCache, unreadable: status === 'error' }\n}\n\n/** datetime-local ↔ epoch-ms without timezone surprises. */\nfunction toLocalInput(ms: number | null | undefined): string {\n if (!ms) return ''\n const date = new Date(ms)\n const pad = (value: number) => String(value).padStart(2, '0')\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-` +\n `${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`\n )\n}\n\n/**\n * Events manager (AGL-145 → AGL-394): the Event Calendar add-on's console\n * surface, now owned by the plugin so the console shell renders it through\n * the ConsoleExtension registry rather than a hardcoded page. Events carry\n * schedule/location/organizer/cover and a draft/published status; visitors\n * see published events through the Event List canvas element.\n *\n * Reaching this component means the org holds `eventCalendar`. The shell\n * resolves that entitlement and renders its own refusal instead of mounting\n * anything an extension registered (AGL-2484), so there is no unentitled\n * state to render here; the sentence a refused org reads is registered as\n * this plugin's `upgradeNotice` in `plugin.ts`.\n */\nexport function EventsConsolePage(props: ConsolePluginPageProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n\n const {\n events: eventDocs,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing copies a whole stored event into `draft` and one\n * `setDoc` writes all of it back, so `merge: true` protects nothing.\n * `status` is the field that costs the most: a cached seed can\n * UNPUBLISH a live, indexed event — or republish one that was pulled\n * down — from the Save button of an unrelated edit, and `endsAtMs` is\n * rewritten on every save whether or not anyone touched the schedule.\n */\n fromCache: eventsFromCache,\n truncated: eventsTruncated,\n unreadable: eventsUnreadable,\n } = useHostEvents(hostId)\n const events = eventDocs\n .filter((event) => !event.deletedAt)\n .sort((a, b) => (b.startsAtMs ?? 0) - (a.startsAtMs ?? 0))\n\n /*\n * The page is a SLICE of the ceiling this page already holds. Sorting is\n * allowed here for the reason it is not allowed on a server-paged list: the\n * whole window is in hand, so newest-first is the order of the window and\n * not of one arbitrary page inside it.\n */\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleEvents = useMemo(\n () => events.slice(page * pageSize, page * pageSize + pageSize),\n [events, page, pageSize],\n )\n\n const [draft, setDraft] = useState<EventDraft | null>(null)\n\n const handleSave = useCallback(async () => {\n if (!draft || !draft.title.trim()) return\n const startsAtMs = draft.startsAt ? new Date(draft.startsAt).getTime() : 0\n const endsAtMs = draft.endsAt ? new Date(draft.endsAt).getTime() : 0\n if (!startsAtMs) {\n return void enqueueSnackbar('Set a start time', {\n variant: 'warning',\n persist: false,\n })\n }\n try {\n const id = draft.id ?? createResourceUid()\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * One `setDoc` serves create and edit here, so the guard has to be\n * conditioned on `draft.id` rather than sitting on a branch of its\n * own. A NEW event is built from blanks at a fresh uid and can\n * overwrite nothing, while the first snapshot of any listener is\n * `fromCache: true` — an unconditional guard would refuse every\n * create, which is the common case, not a corner.\n *\n * The guard WRAPS the write. An early return is a shape you can keep\n * while losing the protection; here the write is only reachable\n * through the verdict.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'event',\n unreadable: Boolean(draft.id) && eventsUnreadable,\n fromCache: Boolean(draft.id) && eventsFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'events', id),\n {\n title: draft.title.trim().slice(0, 150),\n startsAtMs,\n ...(endsAtMs > startsAtMs\n ? { endsAtMs }\n : { endsAtMs: startsAtMs + 60 * 60 * 1000 }),\n ...(draft.location.trim() && {\n location: draft.location.trim().slice(0, 200),\n }),\n ...(draft.organizer.trim() && {\n organizer: draft.organizer.trim().slice(0, 100),\n }),\n ...(draft.description.trim() && {\n description: draft.description.trim().slice(0, 2000),\n }),\n ...(draft.coverImage.trim() && {\n coverImage: draft.coverImage.trim(),\n }),\n // AGL-2418. This is a `merge` write, so clearing the box has\n // to DELETE the key — leaving it out would silently keep the\n // old sentence describing a picture that changed, which is\n // worse than never having offered the field. Tied to the\n // cover: a description with no image describes nothing.\n ...(draft.coverImage.trim() && draft.coverImageAlt.trim()\n ? {\n coverImageAlt: draft.coverImageAlt\n .trim()\n .slice(0, MEDIA_ALT_MAX_LENGTH),\n }\n : { coverImageAlt: deleteField() }),\n status: draft.status,\n updatedAt: Timestamp.now(),\n ...(draft.id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n )\n },\n )\n // Before `setDraft(null)`, so a refusal keeps the dialog open with\n // what was typed — in the same warning vocabulary as the start-time\n // refusal above it.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Event saved', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n draft,\n firestore,\n hostId,\n enqueueSnackbar,\n eventsFromCache,\n eventsUnreadable,\n ])\n\n const handleDelete = useCallback(\n (event: EventRecord) => async () => {\n const confirmed = await confirm({\n title: 'Delete this event?',\n description: `\"${event.title}\" disappears from your site.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n // `status` too: the public listing asks for `status == 'published'`\n // on its query, and cannot ask for a missing `deletedAt`.\n await updateDoc(doc(firestore, 'hosts', hostId, 'events', event.$id), {\n deletedAt: Timestamp.now(),\n status: 'deleted',\n })\n },\n [confirm, firestore, hostId],\n )\n\n return (\n <CardDisplay\n header={'Events'}\n help={pluginDocsHelp('events', { anchor: '#manage-events' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {events.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Create events here, then drop an Event List element on any ' +\n 'page — published events render with SEO Event markup.'}\n </Typography>\n ) : (\n visibleEvents.map((event) => (\n <Stack\n key={event.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n label={event.status ?? 'draft'}\n color={event.status === 'published' ? 'success' : 'default'}\n />\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {event.title}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {new Date(event.startsAtMs ?? 0).toLocaleString()}\n {event.location ? ` · ${event.location}` : ''}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n onClick={() =>\n setDraft({\n id: event.$id,\n title: event.title ?? '',\n startsAt: toLocalInput(event.startsAtMs),\n endsAt: toLocalInput(event.endsAtMs),\n location: event.location ?? '',\n organizer: event.organizer ?? '',\n description: event.description ?? '',\n coverImage: event.coverImage ?? '',\n coverImageAlt: event.coverImageAlt ?? '',\n status: event.status === 'published' ? 'published' : 'draft',\n })\n }\n >\n {'Edit'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(event)}>\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n {events.length === 0 ? null : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleEvents.length}\n // The events this page HOLDS, after the soft-deleted ones are\n // dropped — a slice of rows already read, so the total is exact\n // for the window. The alert below says when the window is short\n // of the site.\n count={events.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {eventsTruncated ? (\n <Alert severity=\"info\">\n {`Showing the newest ${EVENT_CEILING} events. This site has ` +\n 'more, and the older ones are not reachable from here.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n setDraft({\n id: null,\n title: '',\n startsAt: '',\n endsAt: '',\n location: '',\n organizer: '',\n description: '',\n coverImage: '',\n coverImageAlt: '',\n status: 'draft',\n })\n }\n >\n {'Add event'}\n </Button>\n </Stack>\n\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{draft?.id ? 'Edit event' : 'Add event'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n label=\"Title\"\n value={draft?.title ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, title: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Starts\"\n type=\"datetime-local\"\n value={draft?.startsAt ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, startsAt: event.target.value } : prev,\n )\n }\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Ends\"\n type=\"datetime-local\"\n value={draft?.endsAt ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, endsAt: event.target.value } : prev,\n )\n }\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ flex: 1 }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Location\"\n value={draft?.location ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, location: event.target.value } : prev,\n )\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Organizer\"\n value={draft?.organizer ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, organizer: event.target.value } : prev,\n )\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n label=\"Cover image URL\"\n value={draft?.coverImage ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, coverImage: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n {/*\n AGL-2418, matching the collection entry editor's wording. Shown\n only beside a cover, because a description with nothing to\n describe is a field nobody can answer. Left empty the thumbnail\n renders `alt=\"\"`, which is right here — the title sits next to\n it — so this is for the cover that carries something the title\n does not: a face, a venue, a poster with the lineup on it.\n */}\n {draft?.coverImage?.trim() ? (\n <TextField\n label=\"Cover image description\"\n placeholder=\"What the picture shows\"\n value={draft?.coverImageAlt ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n coverImageAlt: event.target.value.slice(\n 0,\n MEDIA_ALT_MAX_LENGTH,\n ),\n }\n : prev,\n )\n }\n size=\"small\"\n helperText={\n 'Read aloud by screen readers. Leave empty if the picture is decorative.'\n }\n />\n ) : null}\n <TextField\n label=\"Description\"\n value={draft?.description ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, description: event.target.value } : prev,\n )\n }\n size=\"small\"\n multiline\n minRows={3}\n />\n </DialogContent>\n <DialogActions sx={{ justifyContent: 'space-between' }}>\n <Button\n onClick={() =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n status:\n prev.status === 'published' ? 'draft' : 'published',\n }\n : prev,\n )\n }\n >\n {draft?.status === 'published' ? 'Set to draft' : 'Set published'}\n </Button>\n <Stack direction=\"row\" spacing={1}>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!draft?.title.trim()}\n onClick={handleSave}\n >\n {'Save event'}\n </Button>\n </Stack>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nEventsConsolePage.displayName = 'EventsConsolePage'\n\nexport default EventsConsolePage\n"],"names":["createResourceUid","MEDIA_ALT_MAX_LENGTH","pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Timestamp","ceilingedWindow","useFirestore","useFirestoreCollection","writeGuardedBySeed","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","collection","deleteField","doc","limit","orderBy","query","setDoc","updateDoc","useCallback","useMemo","useState","ListPagination","TABLE_PAGE_SIZE_DEFAULT","EVENT_CEILING","useHostEvents","hostId","firestore","data","status","fromCache","idField","rows","truncated","events","unreadable","toLocalInput","ms","date","Date","pad","value","String","padStart","getFullYear","getMonth","getDate","getHours","getMinutes","EventsConsolePage","props","draft","enqueueSnackbar","confirm","eventDocs","eventsFromCache","eventsTruncated","eventsUnreadable","filter","event","deletedAt","sort","a","b","startsAtMs","page","setPage","pageSize","setPageSize","visibleEvents","slice","setDraft","handleSave","title","trim","startsAt","getTime","endsAtMs","endsAt","variant","persist","id","verdict","subject","Boolean","location","organizer","description","coverImage","coverImageAlt","updatedAt","now","createdAt","merge","ok","message","error","console","allowDuplicate","handleDelete","confirmed","confirmationText","confirmationButtonProps","color","then","catch","$id","header","help","anchor","contentGutterX","contentGutterY","spacing","length","map","direction","sx","alignItems","size","label","flex","minWidth","noWrap","toLocaleString","onClick","rowCount","count","onPageChange","onPageSizeChange","severity","alignSelf","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","onChange","prev","target","autoFocus","mt","type","slotProps","inputLabel","shrink","placeholder","helperText","multiline","minRows","justifyContent","disabled","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/events-calendar/src/lib/components/events-console-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { TransferResumeImport } from '@aglyn/aglyn/app-utils/transfer-resume-import'\nimport {\n createResourceUid,\n MEDIA_ALT_MAX_LENGTH,\n pluginDocsHelp,\n useTransferLauncher,\n} from '@aglyn/aglyn'\nimport { type ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n ceilingedWindow,\n useFirestore,\n useFirestoreCollection,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n deleteField,\n doc,\n limit,\n orderBy,\n query,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useState } from 'react'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { eventWrite } from '../model/event-write'\nimport { EVENTS_TRANSFER_RESOURCE } from '../transfer/events-transfer-key'\n\n/**\n * How many event documents this page reads.\n *\n * A CEILING, not a page size: the rows are filtered for soft deletes and\n * re-sorted after reading, so a server page would arrive holding anywhere\n * from zero to ten events and the count under it would be about candidates\n * rather than about events.\n */\nconst EVENT_CEILING = 200\n\ninterface EventDraft {\n id: string | null\n title: string\n startsAt: string\n endsAt: string\n location: string\n organizer: string\n description: string\n coverImage: string\n coverImageAlt: string\n status: 'draft' | 'published'\n}\n\ninterface EventRecord {\n $id: string\n title?: string\n startsAtMs?: number\n endsAtMs?: number\n location?: string\n organizer?: string\n description?: string\n coverImage?: string\n coverImageAlt?: string\n /** `deleted` is written by the delete, beside `deletedAt`. */\n status?: 'draft' | 'published' | 'deleted'\n deletedAt?: unknown\n}\n\n/**\n * A host's events.\n *\n * This was a hand-rolled `onSnapshot` with its own retry, and it carried a\n * FOURTH copy of the AGL-1066 defect: `attempt = 0` in the success handler.\n * Under `persistentLocalCache` the cached emission that precedes every\n * refusal handed the budget back, so `unreadable` — the whole point of the\n * counter — could not fire in the one state that needed it, a populated\n * editor over a read that had stopped working.\n *\n * Rather than repair a fourth counter, the copy is gone. `useFirestoreCollection`\n * is the same `onSnapshot`-plus-retry that absorbs the AGL-216/217\n * post-sign-in denial, and it carries the signals this page's save needs\n * (`fromCache`, and `serverDenied` for the refusal the budget cannot yet\n * express) plus denial reporting into `session-health` this page never had.\n * There was never a reason it could not: the file has imported from\n * `@aglyn/tenant-feature-instance` all along.\n */\nfunction useHostEvents(hostId: string): {\n events: EventRecord[]\n truncated: boolean\n fromCache: boolean\n unreadable: boolean\n} {\n const firestore = useFirestore()\n const { data, status, fromCache } = useFirestoreCollection<EventRecord>(\n () =>\n hostId\n ? query(\n collection(firestore, 'hosts', hostId, 'events'),\n /*\n * ORDERED, so the window is the newest 200 rather than a sample.\n *\n * A `limit()` with no `orderBy` returns documents in ID order, so\n * this was reading an arbitrary 200 of the collection and then\n * sorting them by date in the browser — which looks newest-first\n * and is not: a site past 200 events would simply never see the\n * ones whose ids sorted late, including the next one happening.\n *\n * `orderBy` DROPS documents missing the field, so it is only safe\n * once every writer is known to set it, and every one does for\n * `startsAtMs`. The editor refuses a save without it (the guard\n * in `handleSave`); the events import writes through the same\n * rule (`model/event-write.ts`), which refuses a row without one;\n * a site package restore writes the stored events it carries,\n * start included. The server feed already orders by it.\n */\n orderBy('startsAtMs', 'desc'),\n /*\n * One document more than the ceiling, so \"there is more than\n * this\" is a fact rather than a comparison against the cap —\n * which is wrong in exactly the case it matters, a site holding\n * precisely 200 events. The probe row is dropped below and is\n * never rendered or counted.\n */\n limit(EVENT_CEILING + 1),\n )\n : null,\n [firestore, hostId],\n { idField: '$id' },\n )\n\n const { rows, truncated } = ceilingedWindow<EventRecord>(data, EVENT_CEILING)\n return { events: rows, truncated, fromCache, unreadable: status === 'error' }\n}\n\n/** datetime-local ↔ epoch-ms without timezone surprises. */\nfunction toLocalInput(ms: number | null | undefined): string {\n if (!ms) return ''\n const date = new Date(ms)\n const pad = (value: number) => String(value).padStart(2, '0')\n return (\n `${date.getFullYear()}-${pad(date.getMonth() + 1)}-` +\n `${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`\n )\n}\n\n/**\n * Events manager (AGL-145 → AGL-394): the Event Calendar add-on's console\n * surface, now owned by the plugin so the console shell renders it through\n * the ConsoleExtension registry rather than a hardcoded page. Events carry\n * schedule/location/organizer/cover and a draft/published status; visitors\n * see published events through the Event List canvas element.\n *\n * Reaching this component means the org holds `eventCalendar`. The shell\n * resolves that entitlement and renders its own refusal instead of mounting\n * anything an extension registered (AGL-2484), so there is no unentitled\n * state to render here; the sentence a refused org reads is registered as\n * this plugin's `upgradeNotice` in `plugin.ts`.\n */\nexport function EventsConsolePage(props: ConsolePluginPageProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n\n const {\n events: eventDocs,\n /**\n * The rows this editor is seeded from are unconfirmed by the server\n * (AGL-1358). Editing copies a whole stored event into `draft` and one\n * `setDoc` writes all of it back, so `merge: true` protects nothing.\n * `status` is the field that costs the most: a cached seed can\n * UNPUBLISH a live, indexed event — or republish one that was pulled\n * down — from the Save button of an unrelated edit, and `endsAtMs` is\n * rewritten on every save whether or not anyone touched the schedule.\n */\n fromCache: eventsFromCache,\n truncated: eventsTruncated,\n unreadable: eventsUnreadable,\n } = useHostEvents(hostId)\n const events = eventDocs\n .filter((event) => !event.deletedAt)\n .sort((a, b) => (b.startsAtMs ?? 0) - (a.startsAtMs ?? 0))\n\n /*\n * The page is a SLICE of the ceiling this page already holds. Sorting is\n * allowed here for the reason it is not allowed on a server-paged list: the\n * whole window is in hand, so newest-first is the order of the window and\n * not of one arbitrary page inside it.\n */\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleEvents = useMemo(\n () => events.slice(page * pageSize, page * pageSize + pageSize),\n [events, page, pageSize],\n )\n\n const [draft, setDraft] = useState<EventDraft | null>(null)\n\n /*\n * Import and Export open the console's wizard and dialog on this site's\n * events. Outside the console shell there is no launcher, and no buttons.\n * The list needs no refresh after an import: it is a live listener.\n */\n const transfer = useTransferLauncher()\n const transferTarget = { resource: EVENTS_TRANSFER_RESOURCE, scope: 'host' as const, hostId }\n // Import for those who may write the records, Export for those who may read them.\n const canImport = Boolean(transfer?.can('import', transferTarget))\n const canExport = Boolean(transfer?.can('export', transferTarget))\n const transferActions =\n transfer && (canImport || canExport) ? (\n <Stack direction=\"row\" spacing={1}>\n {/* An import left unfinished, reopened where it stopped (AGL-3549). */}\n <TransferResumeImport target={transferTarget} />\n {canImport && (\n <Button size=\"small\" onClick={() => transfer.openImport(transferTarget)}>\n {'Import'}\n </Button>\n )}\n {canExport && (\n <Button\n size=\"small\"\n onClick={() =>\n transfer.openExport(transferTarget)\n }\n >\n {'Export'}\n </Button>\n )}\n </Stack>\n ) : null\n\n const handleSave = useCallback(async () => {\n if (!draft || !draft.title.trim()) return\n const startsAtMs = draft.startsAt ? new Date(draft.startsAt).getTime() : 0\n const endsAtMs = draft.endsAt ? new Date(draft.endsAt).getTime() : 0\n if (!startsAtMs) {\n return void enqueueSnackbar('Set a start time', {\n variant: 'warning',\n persist: false,\n })\n }\n try {\n const id = draft.id ?? createResourceUid()\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * One `setDoc` serves create and edit here, so the guard has to be\n * conditioned on `draft.id` rather than sitting on a branch of its\n * own. A NEW event is built from blanks at a fresh uid and can\n * overwrite nothing, while the first snapshot of any listener is\n * `fromCache: true` — an unconditional guard would refuse every\n * create, which is the common case, not a corner.\n *\n * The guard WRAPS the write. An early return is a shape you can keep\n * while losing the protection; here the write is only reachable\n * through the verdict.\n */\n const verdict = await writeGuardedBySeed(\n {\n subject: 'event',\n unreadable: Boolean(draft.id) && eventsUnreadable,\n fromCache: Boolean(draft.id) && eventsFromCache,\n },\n async () => {\n // The stored form is the shared rule's (`model/event-write.ts`),\n // which the events import writes through too. This is a `merge`\n // write, so whatever the rule removes has to be DELETED here —\n // leaving the key out would keep it (AGL-2418: a cover\n // description outliving its cover).\n const write = eventWrite({\n title: draft.title,\n startsAtMs,\n endsAtMs,\n location: draft.location,\n organizer: draft.organizer,\n description: draft.description,\n coverImage: draft.coverImage,\n coverImageAlt: draft.coverImageAlt,\n status: draft.status,\n })\n await setDoc(\n doc(firestore, 'hosts', hostId, 'events', id),\n {\n ...write.fields,\n ...Object.fromEntries(\n write.remove.map((key) => [key, deleteField()]),\n ),\n updatedAt: Timestamp.now(),\n ...(draft.id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n )\n },\n )\n // Before `setDraft(null)`, so a refusal keeps the dialog open with\n // what was typed — in the same warning vocabulary as the start-time\n // refusal above it.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Event saved', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n draft,\n firestore,\n hostId,\n enqueueSnackbar,\n eventsFromCache,\n eventsUnreadable,\n ])\n\n const handleDelete = useCallback(\n (event: EventRecord) => async () => {\n const confirmed = await confirm({\n title: 'Delete this event?',\n description: `\"${event.title}\" disappears from your site.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n // `status` too: the public listing asks for `status == 'published'`\n // on its query, and cannot ask for a missing `deletedAt`.\n await updateDoc(doc(firestore, 'hosts', hostId, 'events', event.$id), {\n deletedAt: Timestamp.now(),\n status: 'deleted',\n })\n },\n [confirm, firestore, hostId],\n )\n\n return (\n <CardDisplay\n header={'Events'}\n help={pluginDocsHelp('events', { anchor: '#manage-events' })}\n HeaderProps={transferActions ? { action: transferActions } : undefined}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n {events.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Create events here, then drop an Event List element on any ' +\n 'page — published events render with SEO Event markup.'}\n </Typography>\n ) : (\n visibleEvents.map((event) => (\n <Stack\n key={event.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n label={event.status ?? 'draft'}\n color={event.status === 'published' ? 'success' : 'default'}\n />\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {event.title}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {new Date(event.startsAtMs ?? 0).toLocaleString()}\n {event.location ? ` · ${event.location}` : ''}\n </Typography>\n </Stack>\n <Button\n size=\"small\"\n onClick={() =>\n setDraft({\n id: event.$id,\n title: event.title ?? '',\n startsAt: toLocalInput(event.startsAtMs),\n endsAt: toLocalInput(event.endsAtMs),\n location: event.location ?? '',\n organizer: event.organizer ?? '',\n description: event.description ?? '',\n coverImage: event.coverImage ?? '',\n coverImageAlt: event.coverImageAlt ?? '',\n status: event.status === 'published' ? 'published' : 'draft',\n })\n }\n >\n {'Edit'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(event)}>\n {'Delete'}\n </Button>\n </Stack>\n ))\n )}\n {events.length === 0 ? null : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleEvents.length}\n // The events this page HOLDS, after the soft-deleted ones are\n // dropped — a slice of rows already read, so the total is exact\n // for the window. The alert below says when the window is short\n // of the site.\n count={events.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {eventsTruncated ? (\n <Alert severity=\"info\">\n {`Showing the newest ${EVENT_CEILING} events. This site has ` +\n 'more, and the older ones are not reachable from here.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() =>\n setDraft({\n id: null,\n title: '',\n startsAt: '',\n endsAt: '',\n location: '',\n organizer: '',\n description: '',\n coverImage: '',\n coverImageAlt: '',\n status: 'draft',\n })\n }\n >\n {'Add event'}\n </Button>\n </Stack>\n\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{draft?.id ? 'Edit event' : 'Add event'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n label=\"Title\"\n value={draft?.title ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, title: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Starts\"\n type=\"datetime-local\"\n value={draft?.startsAt ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, startsAt: event.target.value } : prev,\n )\n }\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Ends\"\n type=\"datetime-local\"\n value={draft?.endsAt ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, endsAt: event.target.value } : prev,\n )\n }\n size=\"small\"\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ flex: 1 }}\n />\n </Stack>\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Location\"\n value={draft?.location ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, location: event.target.value } : prev,\n )\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n label=\"Organizer\"\n value={draft?.organizer ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, organizer: event.target.value } : prev,\n )\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n </Stack>\n <TextField\n label=\"Cover image URL\"\n value={draft?.coverImage ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, coverImage: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n {/*\n AGL-2418, matching the collection entry editor's wording. Shown\n only beside a cover, because a description with nothing to\n describe is a field nobody can answer. Left empty the thumbnail\n renders `alt=\"\"`, which is right here — the title sits next to\n it — so this is for the cover that carries something the title\n does not: a face, a venue, a poster with the lineup on it.\n */}\n {draft?.coverImage?.trim() ? (\n <TextField\n label=\"Cover image description\"\n placeholder=\"What the picture shows\"\n value={draft?.coverImageAlt ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n coverImageAlt: event.target.value.slice(\n 0,\n MEDIA_ALT_MAX_LENGTH,\n ),\n }\n : prev,\n )\n }\n size=\"small\"\n helperText={\n 'Read aloud by screen readers. Leave empty if the picture is decorative.'\n }\n />\n ) : null}\n <TextField\n label=\"Description\"\n value={draft?.description ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, description: event.target.value } : prev,\n )\n }\n size=\"small\"\n multiline\n minRows={3}\n />\n </DialogContent>\n <DialogActions sx={{ justifyContent: 'space-between' }}>\n <Button\n onClick={() =>\n setDraft((prev) =>\n prev\n ? {\n ...prev,\n status:\n prev.status === 'published' ? 'draft' : 'published',\n }\n : prev,\n )\n }\n >\n {draft?.status === 'published' ? 'Set to draft' : 'Set published'}\n </Button>\n <Stack direction=\"row\" spacing={1}>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!draft?.title.trim()}\n onClick={handleSave}\n >\n {'Save event'}\n </Button>\n </Stack>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nEventsConsolePage.displayName = 'EventsConsolePage'\n\nexport default EventsConsolePage\n"],"names":["TransferResumeImport","createResourceUid","MEDIA_ALT_MAX_LENGTH","pluginDocsHelp","useTransferLauncher","CardDisplay","useConfirmationContext","useSnackbar","Timestamp","ceilingedWindow","useFirestore","useFirestoreCollection","writeGuardedBySeed","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","collection","deleteField","doc","limit","orderBy","query","setDoc","updateDoc","useCallback","useMemo","useState","ListPagination","TABLE_PAGE_SIZE_DEFAULT","eventWrite","EVENTS_TRANSFER_RESOURCE","EVENT_CEILING","useHostEvents","hostId","firestore","data","status","fromCache","idField","rows","truncated","events","unreadable","toLocalInput","ms","date","Date","pad","value","String","padStart","getFullYear","getMonth","getDate","getHours","getMinutes","EventsConsolePage","props","draft","enqueueSnackbar","confirm","eventDocs","eventsFromCache","eventsTruncated","eventsUnreadable","filter","event","deletedAt","sort","a","b","startsAtMs","page","setPage","pageSize","setPageSize","visibleEvents","slice","setDraft","transfer","transferTarget","resource","scope","canImport","Boolean","can","canExport","transferActions","direction","spacing","target","size","onClick","openImport","openExport","handleSave","title","trim","startsAt","getTime","endsAtMs","endsAt","variant","persist","id","verdict","subject","write","location","organizer","description","coverImage","coverImageAlt","fields","Object","fromEntries","remove","map","key","updatedAt","now","createdAt","merge","ok","message","error","console","allowDuplicate","handleDelete","confirmed","confirmationText","confirmationButtonProps","color","then","catch","$id","header","help","anchor","HeaderProps","action","undefined","contentGutterX","contentGutterY","length","sx","alignItems","label","flex","minWidth","noWrap","toLocaleString","rowCount","count","onPageChange","onPageSizeChange","severity","alignSelf","open","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","onChange","prev","autoFocus","mt","type","slotProps","inputLabel","shrink","placeholder","helperText","multiline","minRows","justifyContent","disabled","displayName"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The events-calendar plugin's CONSOLE-ONLY server declarations, named under
19
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
20
+ * console's boot. The tenant runtime registers none of it.
21
+ *
22
+ * The server half of the `events` transfer resource
23
+ * (`transfer/events-transfer.ts`): the catalog, the match keys and the
24
+ * aliases are plain data registered as they are; every hook that reads or
25
+ * writes loads the resource on first use. Registering again replaces this
26
+ * plugin's own.
27
+ */
28
+ export declare function registerEventsCalendarConsoleServerDeclarations(): void;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // The registry's own module, not the `@aglyn/aglyn/server` barrel: boot needs
17
+ // one registry, not the whole server surface.
18
+ import { registerPluginTransferResource } from "@aglyn/aglyn/plugin-manager/plugin-transfer-resources";
19
+ import { BUNDLE_ID } from "./constants/bundle-common.js";
20
+ import { EVENTS_ALIASES, EVENTS_CATALOG, EVENTS_MATCH_KEYS, eventsValuesEqual } from "./transfer/events-transfer-catalog.js";
21
+ import { EVENTS_TRANSFER_RESOURCE } from "./transfer/events-transfer-key.js";
22
+ let loading = null;
23
+ /**
24
+ * The resource over the Admin SDK, built on the first hook a transfer calls:
25
+ * the transfer core, the Admin SDK and the plan table load then, not at boot.
26
+ * A failed load is forgotten, so the next call tries again.
27
+ */ function eventsResource() {
28
+ loading != null ? loading : loading = import("./transfer/events-transfer.server.js").then(({ adminEventsTransferResource })=>adminEventsTransferResource()).catch((error)=>{
29
+ loading = null;
30
+ throw error;
31
+ });
32
+ return loading;
33
+ }
34
+ /**
35
+ * The events-calendar plugin's CONSOLE-ONLY server declarations, named under
36
+ * `consoleServerDeclarations` in `plugins.config.json` and run at the
37
+ * console's boot. The tenant runtime registers none of it.
38
+ *
39
+ * The server half of the `events` transfer resource
40
+ * (`transfer/events-transfer.ts`): the catalog, the match keys and the
41
+ * aliases are plain data registered as they are; every hook that reads or
42
+ * writes loads the resource on first use. Registering again replaces this
43
+ * plugin's own.
44
+ */ export function registerEventsCalendarConsoleServerDeclarations() {
45
+ registerPluginTransferResource(EVENTS_TRANSFER_RESOURCE, {
46
+ fields: ()=>EVENTS_CATALOG,
47
+ matchKeys: EVENTS_MATCH_KEYS,
48
+ aliases: EVENTS_ALIASES,
49
+ // A status in any case is the stored one, in the review as in the plan.
50
+ valuesEqual: eventsValuesEqual,
51
+ count: async (ctx, options)=>(await eventsResource()).count(ctx, options),
52
+ readPage: async (ctx, cursor, fieldIds, options)=>(await eventsResource()).readPage(ctx, cursor, fieldIds, options),
53
+ lookup: async (ctx, requests)=>(await eventsResource()).lookup(ctx, requests),
54
+ plan: async (ctx, input)=>(await eventsResource()).plan(ctx, input),
55
+ apply: async (ctx, chunk, writer)=>(await eventsResource()).apply(ctx, chunk, writer),
56
+ revert: async (ctx, snapshot, decisions)=>(await eventsResource()).revert(ctx, snapshot, decisions)
57
+ }, {
58
+ pluginId: BUNDLE_ID
59
+ });
60
+ }
61
+
62
+ //# sourceMappingURL=declarations.console-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/events-calendar/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry's own module, not the `@aglyn/aglyn/server` barrel: boot needs\n// one registry, not the whole server surface.\nimport { registerPluginTransferResource } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport type { EventsTransferResource } from './transfer/events-transfer'\nimport {\n EVENTS_ALIASES,\n EVENTS_CATALOG,\n EVENTS_MATCH_KEYS,\n eventsValuesEqual,\n} from './transfer/events-transfer-catalog'\nimport { EVENTS_TRANSFER_RESOURCE } from './transfer/events-transfer-key'\n\nlet loading: Promise<EventsTransferResource> | null = null\n\n/**\n * The resource over the Admin SDK, built on the first hook a transfer calls:\n * the transfer core, the Admin SDK and the plan table load then, not at boot.\n * A failed load is forgotten, so the next call tries again.\n */\nfunction eventsResource(): Promise<EventsTransferResource> {\n loading ??= import('./transfer/events-transfer.server')\n .then(({ adminEventsTransferResource }) => adminEventsTransferResource())\n .catch((error: unknown) => {\n loading = null\n throw error\n })\n return loading\n}\n\n/**\n * The events-calendar plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * The server half of the `events` transfer resource\n * (`transfer/events-transfer.ts`): the catalog, the match keys and the\n * aliases are plain data registered as they are; every hook that reads or\n * writes loads the resource on first use. Registering again replaces this\n * plugin's own.\n */\nexport function registerEventsCalendarConsoleServerDeclarations(): void {\n registerPluginTransferResource(\n EVENTS_TRANSFER_RESOURCE,\n {\n fields: () => EVENTS_CATALOG,\n matchKeys: EVENTS_MATCH_KEYS,\n aliases: EVENTS_ALIASES,\n // A status in any case is the stored one, in the review as in the plan.\n valuesEqual: eventsValuesEqual,\n count: async (ctx, options) =>\n (await eventsResource()).count(ctx, options),\n readPage: async (ctx, cursor, fieldIds, options) =>\n (await eventsResource()).readPage(ctx, cursor, fieldIds, options),\n lookup: async (ctx, requests) =>\n (await eventsResource()).lookup(ctx, requests),\n plan: async (ctx, input) => (await eventsResource()).plan(ctx, input),\n apply: async (ctx, chunk, writer) =>\n (await eventsResource()).apply(ctx, chunk, writer),\n revert: async (ctx, snapshot, decisions) =>\n (await eventsResource()).revert(ctx, snapshot, decisions),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerPluginTransferResource","BUNDLE_ID","EVENTS_ALIASES","EVENTS_CATALOG","EVENTS_MATCH_KEYS","eventsValuesEqual","EVENTS_TRANSFER_RESOURCE","loading","eventsResource","then","adminEventsTransferResource","catch","error","registerEventsCalendarConsoleServerDeclarations","fields","matchKeys","aliases","valuesEqual","count","ctx","options","readPage","cursor","fieldIds","lookup","requests","plan","input","apply","chunk","writer","revert","snapshot","decisions","pluginId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,8EAA8E;AAC9E,8CAA8C;AAC9C,SAASA,8BAA8B,QAAQ,wDAAuD;AACtG,SAASC,SAAS,QAAQ,+BAA2B;AAErD,SACEC,cAAc,EACdC,cAAc,EACdC,iBAAiB,EACjBC,iBAAiB,QACZ,wCAAoC;AAC3C,SAASC,wBAAwB,QAAQ,oCAAgC;AAEzE,IAAIC,UAAkD;AAEtD;;;;CAIC,GACD,SAASC;IACPD,kBAAAA,UAAAA,UAAY,MAAM,CAAC,wCAChBE,IAAI,CAAC,CAAC,EAAEC,2BAA2B,EAAE,GAAKA,+BAC1CC,KAAK,CAAC,CAACC;QACNL,UAAU;QACV,MAAMK;IACR;IACF,OAAOL;AACT;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASM;IACdb,+BACEM,0BACA;QACEQ,QAAQ,IAAMX;QACdY,WAAWX;QACXY,SAASd;QACT,wEAAwE;QACxEe,aAAaZ;QACba,OAAO,OAAOC,KAAKC,UACjB,AAAC,CAAA,MAAMZ,gBAAe,EAAGU,KAAK,CAACC,KAAKC;QACtCC,UAAU,OAAOF,KAAKG,QAAQC,UAAUH,UACtC,AAAC,CAAA,MAAMZ,gBAAe,EAAGa,QAAQ,CAACF,KAAKG,QAAQC,UAAUH;QAC3DI,QAAQ,OAAOL,KAAKM,WAClB,AAAC,CAAA,MAAMjB,gBAAe,EAAGgB,MAAM,CAACL,KAAKM;QACvCC,MAAM,OAAOP,KAAKQ,QAAU,AAAC,CAAA,MAAMnB,gBAAe,EAAGkB,IAAI,CAACP,KAAKQ;QAC/DC,OAAO,OAAOT,KAAKU,OAAOC,SACxB,AAAC,CAAA,MAAMtB,gBAAe,EAAGoB,KAAK,CAACT,KAAKU,OAAOC;QAC7CC,QAAQ,OAAOZ,KAAKa,UAAUC,YAC5B,AAAC,CAAA,MAAMzB,gBAAe,EAAGuB,MAAM,CAACZ,KAAKa,UAAUC;IACnD,GACA;QAAEC,UAAUjC;IAAU;AAE1B"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * THE ONE RULE FOR WHAT AN EVENT STORES.
19
+ *
20
+ * `hosts/{hostId}/events/{id}` has two writers that must agree: the Events
21
+ * page's editor and the events import. Both hand what they were given to
22
+ * {@link eventWrite} and store exactly what it answers, so a title is cut,
23
+ * an end time is repaired and a cover description is dropped the same way
24
+ * whichever of them wrote the event.
25
+ *
26
+ * Pure: no Firestore, no clock. Each writer adds its own `updatedAt` /
27
+ * `createdAt` and turns {@link EventWrite.remove} into its SDK's delete
28
+ * sentinel.
29
+ */
30
+ /** Longest stored title. */
31
+ export declare const EVENT_TITLE_MAX_LENGTH = 150;
32
+ /** Longest stored location. */
33
+ export declare const EVENT_LOCATION_MAX_LENGTH = 200;
34
+ /** Longest stored organizer. */
35
+ export declare const EVENT_ORGANIZER_MAX_LENGTH = 100;
36
+ /** Longest stored description. */
37
+ export declare const EVENT_DESCRIPTION_MAX_LENGTH = 2000;
38
+ /** Longest stored cover description: the platform's cap for any `alt`. */
39
+ export declare const EVENT_COVER_ALT_MAX_LENGTH = 300;
40
+ /** How long an event without an end after its start lasts. */
41
+ export declare const EVENT_DEFAULT_DURATION_MS: number;
42
+ /** The statuses an editor or a file may set. `deleted` is only ever written by a delete. */
43
+ export declare const EVENT_STATUSES: readonly ["draft", "published"];
44
+ export type EventStatus = (typeof EVENT_STATUSES)[number];
45
+ /** A status as typed, folded to one an event may hold, or `null` for anything else. */
46
+ export declare function eventStatusOf(value: unknown): EventStatus | null;
47
+ /** What a writer was given for one event. Text may be untrimmed; blank means "none". */
48
+ export interface EventWriteInput {
49
+ title: string;
50
+ /** Epoch milliseconds; required. */
51
+ startsAtMs: number;
52
+ /** Epoch milliseconds; absent, zero or not after the start gives the default duration. */
53
+ endsAtMs?: number | null;
54
+ location?: string | null;
55
+ organizer?: string | null;
56
+ description?: string | null;
57
+ coverImage?: string | null;
58
+ coverImageAlt?: string | null;
59
+ status: EventStatus;
60
+ }
61
+ /** The content fields an event document stores. */
62
+ export interface EventStoredFields {
63
+ title: string;
64
+ startsAtMs: number;
65
+ endsAtMs: number;
66
+ location?: string;
67
+ organizer?: string;
68
+ description?: string;
69
+ coverImage?: string;
70
+ coverImageAlt?: string;
71
+ status: EventStatus;
72
+ }
73
+ /** The optional fields a write can take off a stored event. */
74
+ export type EventRemovableField = 'location' | 'organizer' | 'description' | 'coverImage' | 'coverImageAlt';
75
+ /** What to store: the fields to write and the stored fields to delete. */
76
+ export interface EventWrite {
77
+ fields: EventStoredFields;
78
+ remove: EventRemovableField[];
79
+ }
80
+ export interface EventWriteOptions {
81
+ /**
82
+ * Whether a blank optional field deletes the stored one. The editor leaves
83
+ * it off: it writes a whole event over a `merge`, and a blank box there
84
+ * keeps what was stored. The import turns it on, because it hands over the
85
+ * record as it will be — a blank there is a value it chose to clear.
86
+ */
87
+ clearBlank?: boolean;
88
+ }
89
+ /** Why an event cannot be written as given, or `null` when it can. */
90
+ export declare function eventWriteProblem(input: Pick<EventWriteInput, 'title' | 'startsAtMs'>): string | null;
91
+ /** The end an event stores: the one given when it is after the start, else the default duration. */
92
+ export declare function eventEndsAtMs(startsAtMs: number, endsAtMs: number | null | undefined): number;
93
+ /**
94
+ * The stored form of an event. Throws for input {@link eventWriteProblem}
95
+ * refuses; both writers check it first and say why in their own words.
96
+ *
97
+ * The cover description is tied to the cover: with no cover, or an empty
98
+ * description, it is removed rather than left describing a picture that
99
+ * changed.
100
+ */
101
+ export declare function eventWrite(input: EventWriteInput, options?: EventWriteOptions): EventWrite;