@aglyn/plugins-events-calendar 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +47 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +19 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/event-list.d.ts +60 -0
- package/src/lib/components/event-list.js +298 -0
- package/src/lib/components/event-list.js.map +1 -0
- package/src/lib/components/events-console-page.d.ts +19 -0
- package/src/lib/components/events-console-page.js +530 -0
- package/src/lib/components/events-console-page.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +26 -0
- package/src/lib/constants/bundle-common.js +26 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/plugin.d.ts +25 -0
- package/src/lib/plugin.js +75 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server.d.ts +17 -0
- package/src/lib/server.js +166 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +28 -0
- package/src/lib/site.js +51 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/events-calendar/src/lib/components/event-list.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\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiCalendarStar } from '@aglyn/shared-data-mdi'\nimport Box from '@mui/material/Box'\nimport CircularProgress from '@mui/material/CircularProgress'\nimport Stack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useEffect, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'eventList'\n\nexport interface EventListProps {\n /** 'upcoming' (default) or 'past'. */\n mode?: 'upcoming' | 'past'\n /** Heading above the list; empty hides it. */\n heading?: string\n /** Max events shown (default 10). */\n maxItems?: number\n}\n\nexport interface EventItem {\n $id: string\n title: string\n startsAtMs: number\n endsAtMs: number\n location?: string | null\n organizer?: string | null\n description?: string | null\n coverImage?: string | null\n /**\n * The author's description of the cover (AGL-2418). Absent or blank\n * renders `alt=\"\"` — correct for this layout, where the thumbnail sits\n * immediately beside the title that already names it.\n */\n coverImageAlt?: string | null\n}\n\n/**\n * One event as its `schema.org/Event` JSON-LD string (AGL-143/145).\n *\n * Lifted out of the JSX so the structured data can be asserted on its own,\n * which is what the surface is FOR — it is markup no human ever sees, so a\n * render test that only checks the visible list proves nothing about it.\n */\nexport function eventJsonLd(event: EventItem): string {\n // The cover, and only if a crawler could actually fetch it (AGL-1351).\n //\n // Calling the shared resolver with NO host is how this surface says \"accept\n // what is already absolute, and nothing else\": with no origin to resolve\n // against it returns undefined for a site-relative path and for a `media:`\n // reference alike, so neither can reach the markup. The origin lives on the\n // host record, which only the server holds — `/api/events/list` resolves\n // each cover there, so in practice an already-absolute URL passes straight\n // through and this stands as the guarantee that the node is never emitted\n // with an address a crawler cannot follow, whatever the payload contains.\n //\n // Deliberately NOT `window.location.origin`: on the console's Preview\n // surface that is the console, and the markup would then name this site's\n // image on a URL belonging to a different one.\n const image = Aglyn.resolveSocialImage({\n sources: [{ image: event.coverImage }],\n host: undefined,\n })\n return Aglyn.safeJsonLd({\n '@context': 'https://schema.org',\n '@type': 'Event',\n name: event.title,\n startDate: new Date(event.startsAtMs).toISOString(),\n ...(event.endsAtMs && {\n endDate: new Date(event.endsAtMs).toISOString(),\n }),\n ...(event.location && {\n location: { '@type': 'Place', name: event.location },\n }),\n ...(event.organizer && {\n organizer: {\n '@type': 'Organization',\n name: event.organizer,\n },\n }),\n ...(event.description && {\n description: event.description,\n }),\n // Absent rather than `\"image\": [null]` — `strictNullChecks` is off\n // repo-wide, so this guard is what keeps an unresolvable cover out.\n ...(image && { image: [image.url] }),\n })\n}\n\n/**\n * Event List (AGL-145, Event Calendar add-on): published events from the\n * public tenant API with schema.org Event JSON-LD per item (ties into the\n * AGL-143 structured-data work). Editor placeholder without a site\n * context; empty add-on hosts render nothing live.\n */\nconst EventList = forwardRef<HTMLDivElement, EventListProps>((props, ref) => {\n const { mode, heading, maxItems, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { hostId } = Aglyn.useSite()\n const [events, setEvents] = useState<EventItem[] | null>(null)\n\n useEffect(() => {\n if (!hostId) return\n let active = true\n void fetch(\n `/api/events/list?hostId=${encodeURIComponent(hostId)}` +\n `&mode=${mode === 'past' ? 'past' : 'upcoming'}`,\n )\n .then((response) => response.json())\n .then((payload) => {\n if (active) setEvents(payload?.events ?? [])\n })\n .catch(() => {\n if (active) setEvents([])\n })\n return () => {\n active = false\n }\n }, [hostId, mode])\n\n if (!hostId) {\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 3,\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Event list — published events render here (Event Calendar add-on)'}\n </Box>\n )\n }\n if (events === null) {\n return (\n <Box ref={ref} {...rest} sx={[{ p: 2 }, ...nodeSx]}>\n <CircularProgress size={24} />\n </Box>\n )\n }\n const visible = events.slice(0, maxItems && maxItems > 0 ? maxItems : 10)\n if (!visible.length) return <Box ref={ref} {...rest} />\n\n return (\n <Stack ref={ref} spacing={2} {...rest}>\n {heading ? <Typography variant=\"h5\">{heading}</Typography> : null}\n {visible.map((event) => (\n <Stack\n key={event.$id}\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'flex-start' }}\n >\n {/* The cover is a free-text console field rendered verbatim — one\n of AGL-1725's raw author sinks. Scheme rule only, never a host\n check: `http:` (an insecure egress no browser defends) and\n unknown schemes render nothing; https/relative/data render as\n stored. */}\n {event.coverImage &&\n !Aglyn.isRefusedAuthorImageSrc(event.coverImage) ? (\n <Box\n component=\"img\"\n src={event.coverImage}\n // The author's own description when they wrote one (AGL-2418),\n // and `alt=\"\"` otherwise. No fallback to the title: this is a\n // 96×96 thumbnail sitting immediately beside that title, so\n // borrowing it would announce the same words twice.\n alt={Aglyn.renderedMediaAlt(event.coverImageAlt)}\n sx={{\n width: 96,\n height: 96,\n objectFit: 'cover',\n borderRadius: 1,\n flexShrink: 0,\n }}\n // Deferred (AGL-2486). A 96x96 thumbnail, one per event, in a\n // list that is as long as the calendar is busy — previously\n // unhinted, so a twenty-event list opened twenty eager\n // default-priority fetches.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n ) : null}\n <Stack spacing={0.25} sx={{ minWidth: 0 }}>\n <Typography variant=\"h6\">{event.title}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {new Date(event.startsAtMs).toLocaleString([], {\n dateStyle: 'medium',\n timeStyle: 'short',\n })}\n {event.location ? ` · ${event.location}` : ''}\n {event.organizer ? ` · ${event.organizer}` : ''}\n </Typography>\n {event.description ? (\n <Typography variant=\"body2\">{event.description}</Typography>\n ) : null}\n </Stack>\n {/* schema.org Event (AGL-143/145), built above. */}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: eventJsonLd(event) }}\n />\n </Stack>\n ))}\n </Stack>\n )\n})\nEventList.displayName = 'EventList'\n\nexport const schema: Aglyn.ComponentSchema<EventListProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Event List',\n description:\n 'Your published events, each marked up so search engines can show it.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: {\n path: mdiCalendarStar.path,\n sx: { color: '#7b1fa2' },\n },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'heading',\n description: 'Heading above the list; empty hides it.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Heading',\n },\n {\n name: 'mode',\n description: 'Which events to show.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Show',\n options: [\n { value: '', label: 'Upcoming (default)' },\n { value: 'past', label: 'Past events' },\n ],\n },\n {\n name: 'maxItems',\n description: 'Maximum events shown (default 10).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Max items',\n type: 'number',\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Event List',\n pluginId: BUNDLE_ID,\n description: 'Published events with SEO Event markup (add-on)',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: {\n path: mdiCalendarStar.path,\n sx: { color: '#7b1fa2' },\n },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default EventList\n"],"names":["Aglyn","mdiCalendarStar","Box","CircularProgress","Stack","Typography","forwardRef","useEffect","useState","BUNDLE_ID","generatePresetId","ID","eventJsonLd","event","image","resolveSocialImage","sources","coverImage","host","undefined","safeJsonLd","name","title","startDate","Date","startsAtMs","toISOString","endsAtMs","endDate","location","organizer","description","url","EventList","props","ref","mode","heading","maxItems","rest","nodeSx","Array","isArray","hostId","useSite","events","setEvents","active","fetch","encodeURIComponent","then","response","json","payload","catch","sx","p","border","borderColor","borderRadius","color","fontSize","fontFamily","size","visible","slice","length","spacing","variant","map","direction","alignItems","isRefusedAuthorImageSrc","component","src","alt","renderedMediaAlt","coverImageAlt","width","height","objectFit","flexShrink","DEFERRED_IMAGE_ATTRIBUTES","minWidth","toLocaleString","dateStyle","timeStyle","script","type","dangerouslySetInnerHTML","__html","$id","displayName","schema","pluginId","category","ComponentCategory","DATA_DISPLAY","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","FieldComponentType","TEXT_FIELD","label","SELECT","options","value","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ConsolePluginPageProps } from '@aglyn/aglyn';
|
|
2
|
+
/**
|
|
3
|
+
* Events manager (AGL-145 → AGL-394): the Event Calendar add-on's console
|
|
4
|
+
* surface, now owned by the plugin so the console shell renders it through
|
|
5
|
+
* the ConsoleExtension registry rather than a hardcoded page. Events carry
|
|
6
|
+
* schedule/location/organizer/cover and a draft/published status; visitors
|
|
7
|
+
* see published events through the Event List canvas element.
|
|
8
|
+
*
|
|
9
|
+
* Reaching this component means the org holds `eventCalendar`. The shell
|
|
10
|
+
* resolves that entitlement and renders its own refusal instead of mounting
|
|
11
|
+
* anything an extension registered (AGL-2484), so there is no unentitled
|
|
12
|
+
* state to render here; the sentence a refused org reads is registered as
|
|
13
|
+
* this plugin's `upgradeNotice` in `plugin.ts`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function EventsConsolePage(props: ConsolePluginPageProps): import("react").JSX.Element;
|
|
16
|
+
export declare namespace EventsConsolePage {
|
|
17
|
+
var displayName: string;
|
|
18
|
+
}
|
|
19
|
+
export default EventsConsolePage;
|
|
@@ -0,0 +1,530 @@
|
|
|
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
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { createResourceUid, MEDIA_ALT_MAX_LENGTH, pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
|
+
import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
22
|
+
import { Timestamp } from "@aglyn/shared-util-timestamp";
|
|
23
|
+
import { ceilingedWindow, useFirestore, useFirestoreCollection, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
|
|
24
|
+
import { Alert, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
|
|
25
|
+
import { collection, deleteField, doc, limit, orderBy, query, setDoc, updateDoc } from "firebase/firestore";
|
|
26
|
+
import { useCallback, useMemo, useState } from "react";
|
|
27
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
28
|
+
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
29
|
+
/**
|
|
30
|
+
* How many event documents this page reads.
|
|
31
|
+
*
|
|
32
|
+
* A CEILING, not a page size: the rows are filtered for soft deletes and
|
|
33
|
+
* re-sorted after reading, so a server page would arrive holding anywhere
|
|
34
|
+
* from zero to ten events and the count under it would be about candidates
|
|
35
|
+
* rather than about events.
|
|
36
|
+
*/ const EVENT_CEILING = 200;
|
|
37
|
+
/**
|
|
38
|
+
* A host's events.
|
|
39
|
+
*
|
|
40
|
+
* This was a hand-rolled `onSnapshot` with its own retry, and it carried a
|
|
41
|
+
* FOURTH copy of the AGL-1066 defect: `attempt = 0` in the success handler.
|
|
42
|
+
* Under `persistentLocalCache` the cached emission that precedes every
|
|
43
|
+
* refusal handed the budget back, so `unreadable` — the whole point of the
|
|
44
|
+
* counter — could not fire in the one state that needed it, a populated
|
|
45
|
+
* editor over a read that had stopped working.
|
|
46
|
+
*
|
|
47
|
+
* Rather than repair a fourth counter, the copy is gone. `useFirestoreCollection`
|
|
48
|
+
* is the same `onSnapshot`-plus-retry that absorbs the AGL-216/217
|
|
49
|
+
* post-sign-in denial, and it carries the signals this page's save needs
|
|
50
|
+
* (`fromCache`, and `serverDenied` for the refusal the budget cannot yet
|
|
51
|
+
* express) plus denial reporting into `session-health` this page never had.
|
|
52
|
+
* There was never a reason it could not: the file has imported from
|
|
53
|
+
* `@aglyn/tenant-feature-instance` all along.
|
|
54
|
+
*/ function useHostEvents(hostId) {
|
|
55
|
+
const firestore = useFirestore();
|
|
56
|
+
const { data, status, fromCache } = useFirestoreCollection(()=>hostId ? query(collection(firestore, 'hosts', hostId, 'events'), /*
|
|
57
|
+
* ORDERED, so the window is the newest 200 rather than a sample.
|
|
58
|
+
*
|
|
59
|
+
* A `limit()` with no `orderBy` returns documents in ID order, so
|
|
60
|
+
* this was reading an arbitrary 200 of the collection and then
|
|
61
|
+
* sorting them by date in the browser — which looks newest-first
|
|
62
|
+
* and is not: a site past 200 events would simply never see the
|
|
63
|
+
* ones whose ids sorted late, including the next one happening.
|
|
64
|
+
*
|
|
65
|
+
* `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.
|
|
71
|
+
*/ orderBy('startsAtMs', 'desc'), /*
|
|
72
|
+
* One document more than the ceiling, so "there is more than
|
|
73
|
+
* this" is a fact rather than a comparison against the cap —
|
|
74
|
+
* which is wrong in exactly the case it matters, a site holding
|
|
75
|
+
* precisely 200 events. The probe row is dropped below and is
|
|
76
|
+
* never rendered or counted.
|
|
77
|
+
*/ limit(EVENT_CEILING + 1)) : null, [
|
|
78
|
+
firestore,
|
|
79
|
+
hostId
|
|
80
|
+
], {
|
|
81
|
+
idField: '$id'
|
|
82
|
+
});
|
|
83
|
+
const { rows, truncated } = ceilingedWindow(data, EVENT_CEILING);
|
|
84
|
+
return {
|
|
85
|
+
events: rows,
|
|
86
|
+
truncated,
|
|
87
|
+
fromCache,
|
|
88
|
+
unreadable: status === 'error'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** datetime-local ↔ epoch-ms without timezone surprises. */ function toLocalInput(ms) {
|
|
92
|
+
if (!ms) return '';
|
|
93
|
+
const date = new Date(ms);
|
|
94
|
+
const pad = (value)=>String(value).padStart(2, '0');
|
|
95
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-` + `${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Events manager (AGL-145 → AGL-394): the Event Calendar add-on's console
|
|
99
|
+
* surface, now owned by the plugin so the console shell renders it through
|
|
100
|
+
* the ConsoleExtension registry rather than a hardcoded page. Events carry
|
|
101
|
+
* schedule/location/organizer/cover and a draft/published status; visitors
|
|
102
|
+
* see published events through the Event List canvas element.
|
|
103
|
+
*
|
|
104
|
+
* Reaching this component means the org holds `eventCalendar`. The shell
|
|
105
|
+
* resolves that entitlement and renders its own refusal instead of mounting
|
|
106
|
+
* anything an extension registered (AGL-2484), so there is no unentitled
|
|
107
|
+
* state to render here; the sentence a refused org reads is registered as
|
|
108
|
+
* this plugin's `upgradeNotice` in `plugin.ts`.
|
|
109
|
+
*/ export function EventsConsolePage(props) {
|
|
110
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
|
|
111
|
+
var _draft_coverImage;
|
|
112
|
+
const { hostId } = props;
|
|
113
|
+
const firestore = useFirestore();
|
|
114
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
115
|
+
const { confirm } = useConfirmationContext();
|
|
116
|
+
const { events: eventDocs, /**
|
|
117
|
+
* The rows this editor is seeded from are unconfirmed by the server
|
|
118
|
+
* (AGL-1358). Editing copies a whole stored event into `draft` and one
|
|
119
|
+
* `setDoc` writes all of it back, so `merge: true` protects nothing.
|
|
120
|
+
* `status` is the field that costs the most: a cached seed can
|
|
121
|
+
* UNPUBLISH a live, indexed event — or republish one that was pulled
|
|
122
|
+
* down — from the Save button of an unrelated edit, and `endsAtMs` is
|
|
123
|
+
* rewritten on every save whether or not anyone touched the schedule.
|
|
124
|
+
*/ fromCache: eventsFromCache, truncated: eventsTruncated, unreadable: eventsUnreadable } = useHostEvents(hostId);
|
|
125
|
+
const events = eventDocs.filter((event)=>!event.deletedAt).sort((a, b)=>{
|
|
126
|
+
var _b_startsAtMs, _a_startsAtMs;
|
|
127
|
+
return ((_b_startsAtMs = b.startsAtMs) != null ? _b_startsAtMs : 0) - ((_a_startsAtMs = a.startsAtMs) != null ? _a_startsAtMs : 0);
|
|
128
|
+
});
|
|
129
|
+
/*
|
|
130
|
+
* The page is a SLICE of the ceiling this page already holds. Sorting is
|
|
131
|
+
* allowed here for the reason it is not allowed on a server-paged list: the
|
|
132
|
+
* whole window is in hand, so newest-first is the order of the window and
|
|
133
|
+
* not of one arbitrary page inside it.
|
|
134
|
+
*/ const [page, setPage] = useState(0);
|
|
135
|
+
const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
136
|
+
const visibleEvents = useMemo(()=>events.slice(page * pageSize, page * pageSize + pageSize), [
|
|
137
|
+
events,
|
|
138
|
+
page,
|
|
139
|
+
pageSize
|
|
140
|
+
]);
|
|
141
|
+
const [draft, setDraft] = useState(null);
|
|
142
|
+
const handleSave = useCallback(async ()=>{
|
|
143
|
+
if (!draft || !draft.title.trim()) return;
|
|
144
|
+
const startsAtMs = draft.startsAt ? new Date(draft.startsAt).getTime() : 0;
|
|
145
|
+
const endsAtMs = draft.endsAt ? new Date(draft.endsAt).getTime() : 0;
|
|
146
|
+
if (!startsAtMs) {
|
|
147
|
+
return void enqueueSnackbar('Set a start time', {
|
|
148
|
+
variant: 'warning',
|
|
149
|
+
persist: false
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
var _draft_id;
|
|
154
|
+
const id = (_draft_id = draft.id) != null ? _draft_id : createResourceUid();
|
|
155
|
+
/**
|
|
156
|
+
* Refuse an EDIT whose seed the server never confirmed (AGL-1358).
|
|
157
|
+
*
|
|
158
|
+
* One `setDoc` serves create and edit here, so the guard has to be
|
|
159
|
+
* conditioned on `draft.id` rather than sitting on a branch of its
|
|
160
|
+
* own. A NEW event is built from blanks at a fresh uid and can
|
|
161
|
+
* overwrite nothing, while the first snapshot of any listener is
|
|
162
|
+
* `fromCache: true` — an unconditional guard would refuse every
|
|
163
|
+
* create, which is the common case, not a corner.
|
|
164
|
+
*
|
|
165
|
+
* The guard WRAPS the write. An early return is a shape you can keep
|
|
166
|
+
* while losing the protection; here the write is only reachable
|
|
167
|
+
* through the verdict.
|
|
168
|
+
*/ const verdict = await writeGuardedBySeed({
|
|
169
|
+
subject: 'event',
|
|
170
|
+
unreadable: Boolean(draft.id) && eventsUnreadable,
|
|
171
|
+
fromCache: Boolean(draft.id) && eventsFromCache
|
|
172
|
+
}, 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,
|
|
194
|
+
updatedAt: Timestamp.now()
|
|
195
|
+
}, draft.id ? {} : {
|
|
196
|
+
createdAt: Timestamp.now()
|
|
197
|
+
}), {
|
|
198
|
+
merge: true
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
// Before `setDraft(null)`, so a refusal keeps the dialog open with
|
|
202
|
+
// what was typed — in the same warning vocabulary as the start-time
|
|
203
|
+
// refusal above it.
|
|
204
|
+
if (!verdict.ok) {
|
|
205
|
+
return void enqueueSnackbar(verdict.message, {
|
|
206
|
+
variant: 'warning',
|
|
207
|
+
persist: false
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
setDraft(null);
|
|
211
|
+
enqueueSnackbar('Event saved', {
|
|
212
|
+
variant: 'success',
|
|
213
|
+
persist: false
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(error);
|
|
217
|
+
enqueueSnackbar('An error has occurred', {
|
|
218
|
+
variant: 'error',
|
|
219
|
+
allowDuplicate: true
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}, [
|
|
223
|
+
draft,
|
|
224
|
+
firestore,
|
|
225
|
+
hostId,
|
|
226
|
+
enqueueSnackbar,
|
|
227
|
+
eventsFromCache,
|
|
228
|
+
eventsUnreadable
|
|
229
|
+
]);
|
|
230
|
+
const handleDelete = useCallback((event)=>async ()=>{
|
|
231
|
+
const confirmed = await confirm({
|
|
232
|
+
title: 'Delete this event?',
|
|
233
|
+
description: `"${event.title}" disappears from your site.`,
|
|
234
|
+
confirmationText: 'Delete',
|
|
235
|
+
confirmationButtonProps: {
|
|
236
|
+
color: 'error'
|
|
237
|
+
}
|
|
238
|
+
}).then(()=>true).catch(()=>false);
|
|
239
|
+
if (!confirmed) return;
|
|
240
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'events', event.$id), {
|
|
241
|
+
deletedAt: Timestamp.now()
|
|
242
|
+
});
|
|
243
|
+
}, [
|
|
244
|
+
confirm,
|
|
245
|
+
firestore,
|
|
246
|
+
hostId
|
|
247
|
+
]);
|
|
248
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
249
|
+
header: 'Events',
|
|
250
|
+
help: pluginDocsHelp('events', {
|
|
251
|
+
anchor: '#manage-events'
|
|
252
|
+
}),
|
|
253
|
+
contentGutterX: true,
|
|
254
|
+
contentGutterY: true,
|
|
255
|
+
children: [
|
|
256
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
257
|
+
spacing: 1,
|
|
258
|
+
children: [
|
|
259
|
+
events.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
260
|
+
variant: "body2",
|
|
261
|
+
color: "text.secondary",
|
|
262
|
+
children: 'Create events here, then drop an Event List element on any ' + 'screen — published events render with SEO Event markup.'
|
|
263
|
+
}) : visibleEvents.map((event)=>{
|
|
264
|
+
var _event_status, _event_startsAtMs;
|
|
265
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
266
|
+
direction: "row",
|
|
267
|
+
spacing: 1,
|
|
268
|
+
sx: {
|
|
269
|
+
alignItems: 'center'
|
|
270
|
+
},
|
|
271
|
+
children: [
|
|
272
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
273
|
+
size: "small",
|
|
274
|
+
label: (_event_status = event.status) != null ? _event_status : 'draft',
|
|
275
|
+
color: event.status === 'published' ? 'success' : 'default'
|
|
276
|
+
}),
|
|
277
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
278
|
+
sx: {
|
|
279
|
+
flex: 1,
|
|
280
|
+
minWidth: 0
|
|
281
|
+
},
|
|
282
|
+
children: [
|
|
283
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
284
|
+
variant: "body2",
|
|
285
|
+
noWrap: true,
|
|
286
|
+
children: event.title
|
|
287
|
+
}),
|
|
288
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
289
|
+
variant: "caption",
|
|
290
|
+
color: "text.secondary",
|
|
291
|
+
noWrap: true,
|
|
292
|
+
children: [
|
|
293
|
+
new Date((_event_startsAtMs = event.startsAtMs) != null ? _event_startsAtMs : 0).toLocaleString(),
|
|
294
|
+
event.location ? ` · ${event.location}` : ''
|
|
295
|
+
]
|
|
296
|
+
})
|
|
297
|
+
]
|
|
298
|
+
}),
|
|
299
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
300
|
+
size: "small",
|
|
301
|
+
onClick: ()=>{
|
|
302
|
+
var _event_title, _event_location, _event_organizer, _event_description, _event_coverImage, _event_coverImageAlt, _event_status;
|
|
303
|
+
return setDraft({
|
|
304
|
+
id: event.$id,
|
|
305
|
+
title: (_event_title = event.title) != null ? _event_title : '',
|
|
306
|
+
startsAt: toLocalInput(event.startsAtMs),
|
|
307
|
+
endsAt: toLocalInput(event.endsAtMs),
|
|
308
|
+
location: (_event_location = event.location) != null ? _event_location : '',
|
|
309
|
+
organizer: (_event_organizer = event.organizer) != null ? _event_organizer : '',
|
|
310
|
+
description: (_event_description = event.description) != null ? _event_description : '',
|
|
311
|
+
coverImage: (_event_coverImage = event.coverImage) != null ? _event_coverImage : '',
|
|
312
|
+
coverImageAlt: (_event_coverImageAlt = event.coverImageAlt) != null ? _event_coverImageAlt : '',
|
|
313
|
+
status: (_event_status = event.status) != null ? _event_status : 'draft'
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
children: 'Edit'
|
|
317
|
+
}),
|
|
318
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
319
|
+
size: "small",
|
|
320
|
+
color: "error",
|
|
321
|
+
onClick: handleDelete(event),
|
|
322
|
+
children: 'Delete'
|
|
323
|
+
})
|
|
324
|
+
]
|
|
325
|
+
}, event.$id);
|
|
326
|
+
}),
|
|
327
|
+
events.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
|
|
328
|
+
page: page,
|
|
329
|
+
pageSize: pageSize,
|
|
330
|
+
rowCount: visibleEvents.length,
|
|
331
|
+
// The events this page HOLDS, after the soft-deleted ones are
|
|
332
|
+
// dropped — a slice of rows already read, so the total is exact
|
|
333
|
+
// for the window. The alert below says when the window is short
|
|
334
|
+
// of the site.
|
|
335
|
+
count: events.length,
|
|
336
|
+
onPageChange: setPage,
|
|
337
|
+
onPageSizeChange: setPageSize
|
|
338
|
+
}),
|
|
339
|
+
eventsTruncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
340
|
+
severity: "info",
|
|
341
|
+
children: `Showing the newest ${EVENT_CEILING} events. This site has ` + 'more, and the older ones are not reachable from here.'
|
|
342
|
+
}) : null,
|
|
343
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
344
|
+
size: "small",
|
|
345
|
+
color: "primary",
|
|
346
|
+
sx: {
|
|
347
|
+
alignSelf: 'flex-start'
|
|
348
|
+
},
|
|
349
|
+
onClick: ()=>setDraft({
|
|
350
|
+
id: null,
|
|
351
|
+
title: '',
|
|
352
|
+
startsAt: '',
|
|
353
|
+
endsAt: '',
|
|
354
|
+
location: '',
|
|
355
|
+
organizer: '',
|
|
356
|
+
description: '',
|
|
357
|
+
coverImage: '',
|
|
358
|
+
coverImageAlt: '',
|
|
359
|
+
status: 'draft'
|
|
360
|
+
}),
|
|
361
|
+
children: 'Add event'
|
|
362
|
+
})
|
|
363
|
+
]
|
|
364
|
+
}),
|
|
365
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
366
|
+
open: Boolean(draft),
|
|
367
|
+
onClose: ()=>setDraft(null),
|
|
368
|
+
maxWidth: "sm",
|
|
369
|
+
fullWidth: true,
|
|
370
|
+
children: [
|
|
371
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
372
|
+
children: (draft == null ? void 0 : draft.id) ? 'Edit event' : 'Add event'
|
|
373
|
+
}),
|
|
374
|
+
/*#__PURE__*/ _jsxs(DialogContent, {
|
|
375
|
+
sx: {
|
|
376
|
+
display: 'flex',
|
|
377
|
+
flexDirection: 'column',
|
|
378
|
+
gap: 1.5,
|
|
379
|
+
pt: 1
|
|
380
|
+
},
|
|
381
|
+
children: [
|
|
382
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
383
|
+
label: "Title",
|
|
384
|
+
value: (_ref = draft == null ? void 0 : draft.title) != null ? _ref : '',
|
|
385
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
386
|
+
title: event.target.value
|
|
387
|
+
}) : prev),
|
|
388
|
+
size: "small",
|
|
389
|
+
autoFocus: true,
|
|
390
|
+
sx: {
|
|
391
|
+
mt: 1
|
|
392
|
+
}
|
|
393
|
+
}),
|
|
394
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
395
|
+
direction: "row",
|
|
396
|
+
spacing: 1,
|
|
397
|
+
children: [
|
|
398
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
399
|
+
label: "Starts",
|
|
400
|
+
type: "datetime-local",
|
|
401
|
+
value: (_ref1 = draft == null ? void 0 : draft.startsAt) != null ? _ref1 : '',
|
|
402
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
403
|
+
startsAt: event.target.value
|
|
404
|
+
}) : prev),
|
|
405
|
+
size: "small",
|
|
406
|
+
slotProps: {
|
|
407
|
+
inputLabel: {
|
|
408
|
+
shrink: true
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
sx: {
|
|
412
|
+
flex: 1
|
|
413
|
+
}
|
|
414
|
+
}),
|
|
415
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
416
|
+
label: "Ends",
|
|
417
|
+
type: "datetime-local",
|
|
418
|
+
value: (_ref2 = draft == null ? void 0 : draft.endsAt) != null ? _ref2 : '',
|
|
419
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
420
|
+
endsAt: event.target.value
|
|
421
|
+
}) : prev),
|
|
422
|
+
size: "small",
|
|
423
|
+
slotProps: {
|
|
424
|
+
inputLabel: {
|
|
425
|
+
shrink: true
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
sx: {
|
|
429
|
+
flex: 1
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
]
|
|
433
|
+
}),
|
|
434
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
435
|
+
direction: "row",
|
|
436
|
+
spacing: 1,
|
|
437
|
+
children: [
|
|
438
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
439
|
+
label: "Location",
|
|
440
|
+
value: (_ref3 = draft == null ? void 0 : draft.location) != null ? _ref3 : '',
|
|
441
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
442
|
+
location: event.target.value
|
|
443
|
+
}) : prev),
|
|
444
|
+
size: "small",
|
|
445
|
+
sx: {
|
|
446
|
+
flex: 1
|
|
447
|
+
}
|
|
448
|
+
}),
|
|
449
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
450
|
+
label: "Organizer",
|
|
451
|
+
value: (_ref4 = draft == null ? void 0 : draft.organizer) != null ? _ref4 : '',
|
|
452
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
453
|
+
organizer: event.target.value
|
|
454
|
+
}) : prev),
|
|
455
|
+
size: "small",
|
|
456
|
+
sx: {
|
|
457
|
+
flex: 1
|
|
458
|
+
}
|
|
459
|
+
})
|
|
460
|
+
]
|
|
461
|
+
}),
|
|
462
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
463
|
+
label: "Cover image URL",
|
|
464
|
+
value: (_ref5 = draft == null ? void 0 : draft.coverImage) != null ? _ref5 : '',
|
|
465
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
466
|
+
coverImage: event.target.value
|
|
467
|
+
}) : prev),
|
|
468
|
+
size: "small"
|
|
469
|
+
}),
|
|
470
|
+
(draft == null ? void 0 : (_draft_coverImage = draft.coverImage) == null ? void 0 : _draft_coverImage.trim()) ? /*#__PURE__*/ _jsx(TextField, {
|
|
471
|
+
label: "Cover image description",
|
|
472
|
+
placeholder: "What the picture shows",
|
|
473
|
+
value: (_ref6 = draft == null ? void 0 : draft.coverImageAlt) != null ? _ref6 : '',
|
|
474
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
475
|
+
coverImageAlt: event.target.value.slice(0, MEDIA_ALT_MAX_LENGTH)
|
|
476
|
+
}) : prev),
|
|
477
|
+
size: "small",
|
|
478
|
+
helperText: 'Read aloud by screen readers. Leave empty if the picture is decorative.'
|
|
479
|
+
}) : null,
|
|
480
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
481
|
+
label: "Description",
|
|
482
|
+
value: (_ref7 = draft == null ? void 0 : draft.description) != null ? _ref7 : '',
|
|
483
|
+
onChange: (event)=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
484
|
+
description: event.target.value
|
|
485
|
+
}) : prev),
|
|
486
|
+
size: "small",
|
|
487
|
+
multiline: true,
|
|
488
|
+
minRows: 3
|
|
489
|
+
})
|
|
490
|
+
]
|
|
491
|
+
}),
|
|
492
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
493
|
+
sx: {
|
|
494
|
+
justifyContent: 'space-between'
|
|
495
|
+
},
|
|
496
|
+
children: [
|
|
497
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
498
|
+
onClick: ()=>setDraft((prev)=>prev ? _extends({}, prev, {
|
|
499
|
+
status: prev.status === 'published' ? 'draft' : 'published'
|
|
500
|
+
}) : prev),
|
|
501
|
+
children: (draft == null ? void 0 : draft.status) === 'published' ? 'Set to draft' : 'Set published'
|
|
502
|
+
}),
|
|
503
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
504
|
+
direction: "row",
|
|
505
|
+
spacing: 1,
|
|
506
|
+
children: [
|
|
507
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
508
|
+
onClick: ()=>setDraft(null),
|
|
509
|
+
children: 'Cancel'
|
|
510
|
+
}),
|
|
511
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
512
|
+
variant: "contained",
|
|
513
|
+
color: "primary",
|
|
514
|
+
disabled: !(draft == null ? void 0 : draft.title.trim()),
|
|
515
|
+
onClick: handleSave,
|
|
516
|
+
children: 'Save event'
|
|
517
|
+
})
|
|
518
|
+
]
|
|
519
|
+
})
|
|
520
|
+
]
|
|
521
|
+
})
|
|
522
|
+
]
|
|
523
|
+
})
|
|
524
|
+
]
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
EventsConsolePage.displayName = 'EventsConsolePage';
|
|
528
|
+
export default EventsConsolePage;
|
|
529
|
+
|
|
530
|
+
//# sourceMappingURL=events-console-page.js.map
|