@acms-subir/core 0.3.38 → 0.3.40
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/CHANGELOG.md +18 -0
- package/docs/mobile.md +1 -1
- package/integration.mjs +1 -0
- package/mobile-template/recipes/events/files/components/RsvpPanel.tsx +11 -7
- package/mobile-template/recipes/rsvp/files/components/RsvpPanel.tsx +11 -7
- package/package.json +1 -1
- package/src/actions/index.ts +2 -1
- package/src/components/EventPage.astro +14 -5
- package/src/components/MobileApp.tsx +44 -23
- package/src/components/editor/EventPanel.tsx +3 -1
- package/src/components/editor/PostEditor.tsx +1 -1
- package/src/components/editor/event/RegistrationCard.tsx +55 -1
- package/src/components/editor/event/registration.ts +18 -1
- package/src/lib/booking/emails.ts +37 -11
- package/src/lib/booking/service.ts +96 -19
- package/src/lib/calendar/api.ts +3 -2
- package/src/lib/calendar/events.ts +11 -0
- package/src/lib/calendar/members-config.ts +5 -0
- package/src/lib/calendar/requests.ts +4 -4
- package/src/lib/calendar/rsvp-words.ts +31 -2
- package/src/lib/content-service.ts +29 -14
- package/src/lib/drafts.ts +2 -0
- package/src/lib/editor/documents.ts +4 -1
- package/src/lib/editor/schemas.ts +1 -1
- package/src/lib/email/booking-words.ts +66 -1
- package/src/lib/email/service.ts +1 -1
- package/src/lib/email/store.ts +17 -1
- package/src/lib/email/types.ts +3 -1
- package/src/lib/legal/text-en.ts +1 -1
- package/src/lib/legal/text-es.ts +1 -1
- package/src/lib/mobile/bookings.ts +1 -1
- package/src/lib/mobile/calendar.ts +23 -1
- package/src/lib/mobile/http.ts +1 -1
- package/src/lib/mobile/manage.ts +1 -1
- package/src/lib/mobile/openapi.ts +3 -2
- package/src/lib/mobile/rsvp.ts +14 -8
- package/src/lib/mobile/schemas.ts +7 -3
- package/src/lib/push/api.ts +7 -8
- package/src/lib/push/service.ts +21 -16
- package/src/lib/push/types.ts +18 -8
- package/src/lib/repo/sql.ts +7 -3
- package/src/lib/repo/types.ts +8 -4
- package/src/lib/schema.ts +8 -1
- package/src/pages/api/v1/events/[slug]/index.ts +8 -0
- package/src/pages/events/[slug].astro +10 -3
- package/src/styles/editor.css +4 -0
- package/src/styles/mobile-app.css +7 -0
- package/src/styles/site.css +3 -0
- package/src/theme-sdk/index.ts +1 -1
package/src/lib/push/types.ts
CHANGED
|
@@ -2,6 +2,22 @@ import { z } from 'zod';
|
|
|
2
2
|
|
|
3
3
|
// '*' in a calendars list = every public event
|
|
4
4
|
const keys = z.array(z.string().min(1).max(200)).max(100);
|
|
5
|
+
/** One "when an event changes" rule: the events it covers and who hears. */
|
|
6
|
+
export const ChangeRule = z.object({
|
|
7
|
+
calendars: keys.default([]),
|
|
8
|
+
/** only events with one of these tags (any case); [] = every event in the calendars */
|
|
9
|
+
tags: z.array(z.string().trim().min(1).max(100)).max(20).default([]),
|
|
10
|
+
/** sent to these topics instead of the event's own (`events` and its calendars) */
|
|
11
|
+
topics: z.array(z.string().min(1).max(200)).max(10).default([]),
|
|
12
|
+
});
|
|
13
|
+
export type ChangeRule = z.infer<typeof ChangeRule>;
|
|
14
|
+
export const MAX_CHANGE_RULES = 10;
|
|
15
|
+
// one rule, as saved before there could be several: a list of one, or none while it had no calendars
|
|
16
|
+
const oneRule = (v: unknown) => {
|
|
17
|
+
if (!v || typeof v !== 'object' || Array.isArray(v)) return v;
|
|
18
|
+
const calendars = (v as { calendars?: unknown }).calendars;
|
|
19
|
+
return Array.isArray(calendars) && calendars.length ? [v] : [];
|
|
20
|
+
};
|
|
5
21
|
export const PushAuto = z.object({
|
|
6
22
|
/** collection keys whose first publish sends a notification */
|
|
7
23
|
newPost: z.object({ collections: keys.default([]) }).default({}),
|
|
@@ -11,14 +27,8 @@ export const PushAuto = z.object({
|
|
|
11
27
|
startingSoon: z.object({ calendars: keys.default([]), minutes: z.number().int().min(5).max(1440).default(30) }).default({}),
|
|
12
28
|
/** "We're live": once per broadcast that live video detects, to devices on the `live` topic */
|
|
13
29
|
live: z.object({ enabled: z.boolean().default(false) }).default({}),
|
|
14
|
-
/** a published event's new date, time or place, or a cancellation, once its edits settle */
|
|
15
|
-
eventChanges: z.
|
|
16
|
-
calendars: keys.default([]),
|
|
17
|
-
/** only events with one of these tags (any case); [] = every event in the calendars */
|
|
18
|
-
tags: z.array(z.string().trim().min(1).max(100)).max(20).default([]),
|
|
19
|
-
/** sent to these topics instead of the event's own (`events` and its calendars) */
|
|
20
|
-
topics: z.array(z.string().min(1).max(200)).max(10).default([]),
|
|
21
|
-
}).default({}),
|
|
30
|
+
/** a published event's new date, time or place, or a cancellation, once its edits settle: one notice when any rule covers it */
|
|
31
|
+
eventChanges: z.preprocess(oneRule, z.array(ChangeRule).max(MAX_CHANGE_RULES)).default([]),
|
|
22
32
|
});
|
|
23
33
|
export type PushAuto = z.infer<typeof PushAuto>;
|
|
24
34
|
export const PushSettings = z.object({ auto: PushAuto.default({}) });
|
package/src/lib/repo/sql.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { PermissionStore } from '../permissions/store';
|
|
|
11
11
|
import { BackupStore } from '../backup/store';
|
|
12
12
|
import { LiveConfig } from '../live/types';
|
|
13
13
|
import { ApiError } from '../api';
|
|
14
|
-
import { EmailStore, bookingJobs } from '../email/store';
|
|
14
|
+
import { EmailStore, bookingJobs, rsvpStaffJobs } from '../email/store';
|
|
15
15
|
import type { ContentRepository, DesignVersion, EventUpdate, ImportRecord, ImportRecordKind, ImportRun, SearchResults, Stats } from './types';
|
|
16
16
|
import { DocTranslations, Page, Post, Link, Media, NavItem, Redirect, Settings, Snippet, Tag, User, Calendar, CalEvent, ApiKey, Webhook, WebhookDelivery, Resource, BookingLink, Booking, Place, EventLocation, EventRequest, isInternalTag, normalizeRedirectPath, publicTags, slugify, type WidgetInstance } from '../schema';
|
|
17
17
|
import { designSnapshot, draftNavItems, pickDesignDraft, pickEventDraft, pickPageDraft, pickPostDraft } from '../drafts';
|
|
@@ -1225,6 +1225,9 @@ export class SqlRepository implements ContentRepository {
|
|
|
1225
1225
|
write = { sql: `INSERT INTO bookings ${cols} SELECT ${ins.args!.map(() => '?').join(', ')}
|
|
1226
1226
|
WHERE (? = 0 OR (SELECT COALESCE(SUM(seats),0) FROM bookings WHERE kind='rsvp' AND event_id=? AND start_at=? AND status IN ('pending','confirmed')) + ? <= ?)`,
|
|
1227
1227
|
args: [...ins.args!,cap,b.eventId,b.start,b.seats,cap] };
|
|
1228
|
+
// same statement, so two at once from one address can't both get in
|
|
1229
|
+
if (guard.oncePerEmail && holds && b.email) write = { sql: write.sql + ` AND NOT EXISTS (SELECT 1 FROM bookings WHERE kind='rsvp' AND event_id=? AND start_at=? AND email=? AND status IN ('pending','confirmed'))`,
|
|
1230
|
+
args: [...write.args!,b.eventId,b.start,b.email] };
|
|
1228
1231
|
} else if (holds && b.resourceId) {
|
|
1229
1232
|
write = { sql: `INSERT INTO bookings ${cols} SELECT ${ins.args!.map(() => '?').join(', ')} WHERE NOT EXISTS (
|
|
1230
1233
|
SELECT 1 FROM bookings WHERE resource_id=? AND kind IN ('booking','block') AND status IN ('pending','confirmed') AND hold_start < ? AND hold_end > ?)`,
|
|
@@ -1238,7 +1241,7 @@ export class SqlRepository implements ContentRepository {
|
|
|
1238
1241
|
SELECT ?,?,id,? FROM bookings WHERE id=?`, args: [keyHash, requestHash, now, b.id] });
|
|
1239
1242
|
}
|
|
1240
1243
|
// Booking, retry ledger and email outbox commit together on D1 and libSQL.
|
|
1241
|
-
await this.driver.batch([write, ...ledger, ...bookingJobs(b.id, b.status === 'confirmed' ? 'confirmed' : 'received', 0, true)]);
|
|
1244
|
+
await this.driver.batch([write, ...ledger, ...bookingJobs(b.id, b.status === 'confirmed' ? 'confirmed' : 'received', 0, true), ...rsvpStaffJobs(b.id, 'staff-rsvp', guard.staff, 0)]);
|
|
1242
1245
|
return this.getBooking(b.id);
|
|
1243
1246
|
}
|
|
1244
1247
|
async moveBooking(id: string, t: { start: number; end: number; holdStart: number; holdEnd: number }) {
|
|
@@ -1275,7 +1278,8 @@ export class SqlRepository implements ContentRepository {
|
|
|
1275
1278
|
args.push(id);
|
|
1276
1279
|
const kind = data.status === 'confirmed' ? 'confirmed' : data.status === 'declined' ? 'declined' : data.status === 'pending' ? 'received' : 'cancelled';
|
|
1277
1280
|
await this.driver.batch([{sql:`UPDATE bookings SET ${sets.join(', ')} WHERE id = ? AND notification_version = ?`,args:[...args,current.notificationVersion]},
|
|
1278
|
-
...(changed ? bookingJobs(id,kind,current.notificationVersion+1,false,mutation,data.notificationMessage) : [])
|
|
1281
|
+
...(changed ? bookingJobs(id,kind,current.notificationVersion+1,false,mutation,data.notificationMessage) : []),
|
|
1282
|
+
...(changed && data.status === 'cancelled' ? rsvpStaffJobs(id,'staff-rsvp-cancelled',data.staff,current.notificationVersion+1,mutation) : [])]);
|
|
1279
1283
|
}
|
|
1280
1284
|
async deleteBooking(id: string) {
|
|
1281
1285
|
await this.ready;
|
package/src/lib/repo/types.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { SavedForm, FormSubmission } from '../forms/schema';
|
|
|
7
7
|
import type { LiveStore } from '../live/store';
|
|
8
8
|
import type { BookingRequest } from '../backup/content';
|
|
9
9
|
import type { EmailStore } from '../email/store';
|
|
10
|
+
import type { RsvpStaffNotice } from '../email/types';
|
|
10
11
|
import type { PushStore } from '../push/store';
|
|
11
12
|
import type { AnnouncementStore } from '../announcements/store';
|
|
12
13
|
import type { Announcement } from '../announcements/types';
|
|
@@ -187,8 +188,10 @@ export interface ContentRepository {
|
|
|
187
188
|
|
|
188
189
|
// bookings (+ staff blocks and event RSVPs). createBooking is the only
|
|
189
190
|
// write that can fail on a race: it inserts atomically only if the held
|
|
190
|
-
// range is still free (resource bookings) or seats remain (RSVPs
|
|
191
|
-
//
|
|
191
|
+
// range is still free (resource bookings) or seats remain (RSVPs; with
|
|
192
|
+
// oncePerEmail, and the address has no active RSVP for that date), and
|
|
193
|
+
// returns null when it lost. staff: who hears about a new RSVP (queued with
|
|
194
|
+
// it, so a lost race or a replay emails no one).
|
|
192
195
|
/** from/to: overlaps the window; endedBy: ended at or before; default order start ascending, at most 5000 rows */
|
|
193
196
|
listBookings(filter?: { resourceId?: string; eventId?: string; email?: string; from?: number; to?: number; endedBy?: number; statuses?: BookingStatus[]; kinds?: Booking['kind'][]; newestFirst?: boolean; limit?: number; offset?: number }): Promise<Booking[]>;
|
|
194
197
|
getBooking(id: string): Promise<Booking | null>;
|
|
@@ -198,11 +201,12 @@ export interface ContentRepository {
|
|
|
198
201
|
// locale: the booker's language; '' (the main language) when left out
|
|
199
202
|
createBooking(
|
|
200
203
|
data: Omit<Booking, 'id' | 'createdAt' | 'updatedAt' | 'reminderSentAt' | 'notificationVersion' | 'locale'> & { locale?: string },
|
|
201
|
-
guard?: { capacity?: number; idempotency?: { keyHash: string; requestHash: string } }
|
|
204
|
+
guard?: { capacity?: number; oncePerEmail?: boolean; idempotency?: { keyHash: string; requestHash: string }; staff?: RsvpStaffNotice }
|
|
202
205
|
): Promise<Booking | null>;
|
|
203
206
|
// moves a booking only if the new held range is free (reschedule)
|
|
204
207
|
moveBooking(id: string, times: { start: number; end: number; holdStart: number; holdEnd: number }): Promise<boolean>;
|
|
205
|
-
|
|
208
|
+
// staff: who hears that the person cancelled their RSVP (status → cancelled only)
|
|
209
|
+
updateBooking(id: string, data: Partial<Pick<Booking, 'status' | 'notes' | 'name' | 'email' | 'phone' | 'answers' | 'reminderSentAt'>> & { notificationMessage?: string; staff?: RsvpStaffNotice }): Promise<void>;
|
|
206
210
|
deleteBooking(id: string): Promise<void>;
|
|
207
211
|
// confirmed bookings starting in [from, to) that haven't had a reminder
|
|
208
212
|
listDueReminders(from: number, to: number): Promise<Booking[]>;
|
package/src/lib/schema.ts
CHANGED
|
@@ -415,12 +415,19 @@ export const CalEvent = z.object({
|
|
|
415
415
|
// UID of the feed event this was imported from ('' = authored here)
|
|
416
416
|
sourceUid: z.string().default(''),
|
|
417
417
|
// RSVP: seats per occurrence (0 = unlimited) and whether RSVPs confirm at
|
|
418
|
-
// once; RSVPs are stored as bookings of kind 'rsvp'
|
|
418
|
+
// once; RSVPs are stored as bookings of kind 'rsvp'. They close at closesAt
|
|
419
|
+
// (wall time in the event's zone, like start; every date), else closeBefore
|
|
420
|
+
// minutes before each date's start, else when the date ends. notifyStaff:
|
|
421
|
+
// the hosts (else the church's contact email) hear about each new RSVP and
|
|
422
|
+
// each one the person cancels (lib/booking/service.ts rsvpStaff).
|
|
419
423
|
rsvp: z.object({
|
|
420
424
|
enabled: z.boolean().default(false),
|
|
421
425
|
capacity: z.number().int().default(0),
|
|
422
426
|
autoConfirm: z.boolean().default(true),
|
|
423
427
|
maxSeats: z.number().int().default(4),
|
|
428
|
+
closesAt: z.string().nullable().default(null),
|
|
429
|
+
closeBefore: z.number().int().default(0),
|
|
430
|
+
notifyStaff: z.boolean().default(true),
|
|
424
431
|
form: FormDefinition.optional(),
|
|
425
432
|
}).default({}),
|
|
426
433
|
// rooms/people this event uses: they can't be booked while it happens (or
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getEventDetail } from '../../../../../lib/mobile/calendar';
|
|
2
|
+
import { readRoute } from '../../../../../lib/mobile/route';
|
|
3
|
+
import { methodNotAllowed } from '../../../../../lib/mobile/http';
|
|
4
|
+
export { preflight as OPTIONS } from '../../../../../lib/mobile/http';
|
|
5
|
+
// the next occurrence moves with the clock: not timeless
|
|
6
|
+
export const GET = readRoute(getEventDetail, true);
|
|
7
|
+
export const HEAD = GET;
|
|
8
|
+
export const ALL = () => methodNotAllowed();
|
|
@@ -19,7 +19,7 @@ import { effectiveSettings } from '../../lib/designPreview';
|
|
|
19
19
|
import { resolveDesign } from '../../lib/theme';
|
|
20
20
|
import { siteOrigin } from '../../lib/seo';
|
|
21
21
|
import { CALENDAR, eventUrl, withoutPrivateFields } from '../../lib/calendar/config';
|
|
22
|
-
import { eventIsPublic, featuredOccurrence } from '../../lib/calendar/events';
|
|
22
|
+
import { eventIsPublic, featuredOccurrence, rsvpDeadline } from '../../lib/calendar/events';
|
|
23
23
|
import { eventJsonLd } from '../../lib/calendar/seo';
|
|
24
24
|
import { upcomingEvents } from '../../lib/calendar/cards';
|
|
25
25
|
import { seatsTaken, BookingError } from '../../lib/booking/service';
|
|
@@ -50,6 +50,7 @@ const origin = siteOrigin(settings, Astro.url);
|
|
|
50
50
|
// RSVP: "Save my seat" posts back here and lands on the manage page, in the
|
|
51
51
|
// visitor's language (its emails speak it too)
|
|
52
52
|
let rsvpError = '';
|
|
53
|
+
let rsvpDuplicate = false;
|
|
53
54
|
let formValues: Record<string,string> = {};
|
|
54
55
|
let coreValues = { name: '', email: '', seats: '1' };
|
|
55
56
|
let requestId: string = crypto.randomUUID();
|
|
@@ -66,13 +67,19 @@ if (Astro.request.method === 'POST' && event.rsvp.enabled) {
|
|
|
66
67
|
const booking = await submitEventForm(repo, event, occurrence, form, requestId, { origin, locals: Astro.locals, waitUntil: waitUntilFrom(Astro.locals) }, locale);
|
|
67
68
|
return Astro.redirect(`${localePath(manageUrl(booking.manageToken), locale)}?new=1`, 303);
|
|
68
69
|
} catch (err) {
|
|
69
|
-
|
|
70
|
+
const w = rsvpStrings((I18N.enabled ? locale : '') || siteLang(settings));
|
|
71
|
+
// the address already has one: a notice in the page's words (templates that don't know it show it as the error)
|
|
72
|
+
rsvpDuplicate = err instanceof BookingError && err.code === 'duplicate';
|
|
73
|
+
rsvpError = rsvpDuplicate ? `${w.duplicate} ${w.duplicateNote}` : err instanceof BookingError || err instanceof ApiError ? err.message : w.saveError;
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
const formHtml = hiddenFields(await formVersion(definition), requestId) + `<input type="hidden" name="occurrence" value="${occurrence.start}">`;
|
|
73
77
|
const taken = event.rsvp.enabled ? await seatsTaken(repo, event.id, occurrence.start) : 0;
|
|
78
|
+
// the event's RSVP deadline for this date (null: open until it ends)
|
|
79
|
+
const closesAt = event.rsvp.enabled ? rsvpDeadline(occurrence, settings) : null;
|
|
74
80
|
const rsvp = event.rsvp.enabled
|
|
75
|
-
? { going: taken, left: event.rsvp.capacity ? Math.max(0, event.rsvp.capacity - taken) : null, maxSeats: event.rsvp.maxSeats, error: rsvpError, formHtml, fieldsHtml: renderFields(definition, formValues, 'event-registration'), values: coreValues,
|
|
81
|
+
? { going: taken, left: event.rsvp.capacity ? Math.max(0, event.rsvp.capacity - taken) : null, maxSeats: event.rsvp.maxSeats, error: rsvpError, duplicate: rsvpDuplicate, formHtml, fieldsHtml: renderFields(definition, formValues, 'event-registration'), values: coreValues,
|
|
82
|
+
open: occurrence.end > now && !occurrence.cancelled && !(closesAt !== null && closesAt <= now), closesAt }
|
|
76
83
|
: null;
|
|
77
84
|
const calendars = event.calendarIds.map((id) => calMap.get(id)).filter((c): c is NonNullable<typeof c> => Boolean(c) && c!.visibility !== 'private');
|
|
78
85
|
const html = highlightCodeHtml(await expandDynamicBlocks(event.bodyHtml, repo));
|
package/src/styles/editor.css
CHANGED
|
@@ -1168,6 +1168,10 @@
|
|
|
1168
1168
|
.pe-evr-modes + .pe-evs-fields { margin-top: 22px; }
|
|
1169
1169
|
.pe-evr-link { grid-template-columns: minmax(0, 1fr) 150px; }
|
|
1170
1170
|
.pe-evr-seats { grid-template-columns: repeat(2, minmax(0, 200px)); }
|
|
1171
|
+
.pe-evr-when { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
|
|
1172
|
+
.pe-evr-when > span { font-size: 13px; color: var(--muted); }
|
|
1173
|
+
.pe-field .pe-evr-when input, .pe-field .pe-evr-when select { width: auto; }
|
|
1174
|
+
.pe-field .pe-evr-when input[type='number'] { width: 84px; }
|
|
1171
1175
|
.pe-evr-form { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--panel); }
|
|
1172
1176
|
.pe-evr-form-t { flex: 1 1 240px; min-width: 0; }
|
|
1173
1177
|
.pe-evr-form-t b { display: block; font-size: 13.5px; font-weight: 600; color: var(--text); }
|
|
@@ -88,6 +88,13 @@
|
|
|
88
88
|
.ma-tag { display: inline-flex; align-items: center; gap: 5px; background: var(--sel); color: var(--text-2); font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: var(--r-full); }
|
|
89
89
|
.ma-tag button { background: none; border: 0; color: inherit; cursor: pointer; font-size: 10px; padding: 0; }
|
|
90
90
|
.ma-tags input { flex: 1; min-width: 70px; background: none !important; border: 0 !important; box-shadow: none !important; padding: 2px !important; }
|
|
91
|
+
/* "when an event changes": a card per rule */
|
|
92
|
+
.ma-subrules { display: grid; gap: 8px; margin-top: 10px; }
|
|
93
|
+
.ma-subrule { padding: 6px 12px 12px; border: 1px solid var(--line); border-radius: var(--r-md); min-width: 0; }
|
|
94
|
+
.ma-subrule-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 30px; }
|
|
95
|
+
.ma-subrule-head b { font-size: 12.5px; color: var(--text-2); }
|
|
96
|
+
.ma-subrule .ma-choices { margin-top: 2px; }
|
|
97
|
+
.ma-add { margin-top: 10px; }
|
|
91
98
|
.ma .form-actions { margin-top: 6px; }
|
|
92
99
|
.ma-saved { color: var(--green); font-size: 12.5px; font-weight: 500; }
|
|
93
100
|
|
package/src/styles/site.css
CHANGED
|
@@ -1147,6 +1147,9 @@ li::marker { color: var(--muted); }
|
|
|
1147
1147
|
.pc-ev-rsvpform[open] > summary { display: none; }
|
|
1148
1148
|
.pc-ev-rsvpform form { margin-top: 4px; }
|
|
1149
1149
|
.pc-ev-full { font-size: 14px; font-weight: 600; color: var(--muted); }
|
|
1150
|
+
.pc-ev-closes { font-size: 13.5px; color: var(--muted); margin: 0 0 10px; }
|
|
1151
|
+
.pc-ev-full + .pc-ev-closes { margin-top: 4px; }
|
|
1152
|
+
.pc-ev-notice { font-size: 14px; line-height: 1.5; background: var(--soft); border-radius: var(--r-sm); padding: 9px 12px; margin: 0 0 14px; }
|
|
1150
1153
|
|
|
1151
1154
|
@media (max-width: 860px) {
|
|
1152
1155
|
.pc-bk { grid-template-columns: 1fr; }
|
package/src/theme-sdk/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface HomeProps { settings: Settings; page: Page | null; posts: Post[
|
|
|
11
11
|
export interface PageProps { settings: Settings; page: Page; html: string }
|
|
12
12
|
export interface PostProps { settings: Settings; post: Post; html: string; authors: User[]; related: Post[] }
|
|
13
13
|
import type { Occurrence } from '../lib/calendar/events';
|
|
14
|
-
export interface EventProps { settings: Settings; event: CalEvent; html: string; calendars: Calendar[]; origin: string; occurrence: Occurrence; upcoming: Pick<Occurrence, 'event' | 'start' | 'end' | 'allDay' | 'days'>[]; preview?: boolean; rsvp?: {going:number;left:number|null;maxSeats:number;error:string;open:boolean}|null }
|
|
14
|
+
export interface EventProps { settings: Settings; event: CalEvent; html: string; calendars: Calendar[]; origin: string; occurrence: Occurrence; upcoming: Pick<Occurrence, 'event' | 'start' | 'end' | 'allDay' | 'days'>[]; preview?: boolean; rsvp?: {going:number;left:number|null;maxSeats:number;error:string;open:boolean;closesAt?:number|null;duplicate?:boolean}|null }
|
|
15
15
|
/**
|
|
16
16
|
* Props for a coded section. `data` holds resolved values: link fields arrive
|
|
17
17
|
* as LinkValue, lists as row arrays, source fields as fresh item arrays
|