@acms-subir/core 0.3.3-next.20260929113209 → 0.3.4
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 +105 -0
- package/cli/common.mjs +2 -2
- package/cli/legacy.mjs +1 -1
- package/cli/main.mjs +82 -8
- package/cli/mobile-doctor.mjs +430 -0
- package/cli/mobile.mjs +315 -0
- package/cli/native-sections.mjs +168 -0
- package/cli/preview.mjs +8 -4
- package/cli/sections.mjs +188 -0
- package/cli/themes.mjs +52 -8
- package/docs/mobile.md +527 -0
- package/docs/sections.md +203 -0
- package/docs/theme-authoring.md +19 -3
- package/integration.mjs +76 -5
- package/mobile-template/client/AGENTS.md +97 -0
- package/mobile-template/client/README.md +65 -0
- package/mobile-template/client/app/_layout.tsx +56 -0
- package/mobile-template/client/app/index.tsx +21 -0
- package/mobile-template/client/app.json +21 -0
- package/mobile-template/client/components/LanguageSwitch.tsx +31 -0
- package/mobile-template/client/eas.json +21 -0
- package/mobile-template/client/env.example +3 -0
- package/mobile-template/client/gitignore +42 -0
- package/mobile-template/client/package.json +38 -0
- package/mobile-template/client/sections/index.ts +11 -0
- package/mobile-template/client/tsconfig.json +7 -0
- package/mobile-template/editor/AGENTS.md +66 -0
- package/mobile-template/editor/README.md +73 -0
- package/mobile-template/editor/app/_layout.tsx +76 -0
- package/mobile-template/editor/app/auth.tsx +13 -0
- package/mobile-template/editor/app/index.tsx +55 -0
- package/mobile-template/editor/app.json +21 -0
- package/mobile-template/editor/components/Screen.tsx +51 -0
- package/mobile-template/editor/components/SignIn.tsx +36 -0
- package/mobile-template/editor/components/TextTabs.tsx +27 -0
- package/mobile-template/editor/eas.json +21 -0
- package/mobile-template/editor/env.example +5 -0
- package/mobile-template/editor/gitignore +42 -0
- package/mobile-template/editor/lib/appearance.tsx +12 -0
- package/mobile-template/editor/lib/config.ts +17 -0
- package/mobile-template/editor/lib/format.ts +24 -0
- package/mobile-template/editor/lib/tools.ts +16 -0
- package/mobile-template/editor/package.json +40 -0
- package/mobile-template/editor/tools/README.md +2 -0
- package/mobile-template/editor/tsconfig.json +7 -0
- package/mobile-template/recipes/booking/files/app/book/[resource].tsx +57 -0
- package/mobile-template/recipes/booking/files/app/book/details.tsx +105 -0
- package/mobile-template/recipes/booking/files/app/book/index.tsx +38 -0
- package/mobile-template/recipes/booking/recipe.json +17 -0
- package/mobile-template/recipes/editor-bookings/files/app/bookings/index.tsx +113 -0
- package/mobile-template/recipes/editor-bookings/files/tools/bookings.ts +1 -0
- package/mobile-template/recipes/editor-bookings/recipe.json +8 -0
- package/mobile-template/recipes/editor-content/files/app/content/[kind]/[id].tsx +13 -0
- package/mobile-template/recipes/editor-content/files/app/content/index.tsx +76 -0
- package/mobile-template/recipes/editor-content/files/components/DocumentDetail.tsx +55 -0
- package/mobile-template/recipes/editor-content/files/tools/content.ts +1 -0
- package/mobile-template/recipes/editor-content/recipe.json +8 -0
- package/mobile-template/recipes/editor-media/files/app/media/index.tsx +73 -0
- package/mobile-template/recipes/editor-media/files/tools/media.ts +1 -0
- package/mobile-template/recipes/editor-media/recipe.json +8 -0
- package/mobile-template/recipes/editor-sections/files/app/sections/[pageId]/[sectionId].tsx +12 -0
- package/mobile-template/recipes/editor-sections/files/app/sections/index.tsx +48 -0
- package/mobile-template/recipes/editor-sections/files/components/SectionEditorPane.tsx +77 -0
- package/mobile-template/recipes/editor-sections/files/tools/sections.ts +1 -0
- package/mobile-template/recipes/editor-sections/recipe.json +8 -0
- package/mobile-template/recipes/editor-translations/files/app/translations/index.tsx +63 -0
- package/mobile-template/recipes/editor-translations/files/app/translations/review.tsx +14 -0
- package/mobile-template/recipes/editor-translations/files/components/ReviewPane.tsx +72 -0
- package/mobile-template/recipes/editor-translations/files/tools/translations.ts +1 -0
- package/mobile-template/recipes/editor-translations/recipe.json +8 -0
- package/mobile-template/recipes/editor-writer/files/app/write/[kind]/[id].tsx +132 -0
- package/mobile-template/recipes/editor-writer/files/app/write/new.tsx +61 -0
- package/mobile-template/recipes/editor-writer/files/tools/writer.ts +1 -0
- package/mobile-template/recipes/editor-writer/recipe.json +8 -0
- package/mobile-template/recipes/events/files/app/events/[slug].tsx +41 -0
- package/mobile-template/recipes/events/files/app/events/index.tsx +50 -0
- package/mobile-template/recipes/events/files/lib/addToCalendar.ts +27 -0
- package/mobile-template/recipes/events/recipe.json +15 -0
- package/mobile-template/recipes/news/files/app/news/[slug].tsx +25 -0
- package/mobile-template/recipes/news/files/app/news/index.tsx +50 -0
- package/mobile-template/recipes/news/recipe.json +13 -0
- package/mobile-template/recipes/push/files/app/notifications.tsx +64 -0
- package/mobile-template/recipes/push/files/components/PushSetup.tsx +15 -0
- package/mobile-template/recipes/push/files/lib/push.ts +15 -0
- package/mobile-template/recipes/push/recipe.json +20 -0
- package/package.json +17 -13
- package/section-library/README.md +16 -0
- package/section-library/contact/Contact.astro +49 -0
- package/section-library/contact/section.json +106 -0
- package/section-library/cta-band/CtaBand.astro +28 -0
- package/section-library/cta-band/section.json +51 -0
- package/section-library/feature-grid/FeatureGrid.astro +54 -0
- package/section-library/feature-grid/section.json +107 -0
- package/section-library/gallery/Gallery.astro +29 -0
- package/section-library/gallery/section.json +51 -0
- package/section-library/hero-banner/HeroBanner.astro +42 -0
- package/section-library/hero-banner/section.json +81 -0
- package/section-library/latest-posts/LatestPosts.astro +22 -0
- package/section-library/latest-posts/section.json +37 -0
- package/section-library/pricing/Pricing.astro +40 -0
- package/section-library/pricing/section.json +117 -0
- package/section-library/questions/Questions.astro +38 -0
- package/section-library/questions/section.json +69 -0
- package/section-library/service-times/ServiceTimes.astro +42 -0
- package/section-library/service-times/section.json +127 -0
- package/section-library/team/Team.astro +45 -0
- package/section-library/team/section.json +88 -0
- package/section-library/upcoming-events/UpcomingEvents.astro +54 -0
- package/section-library/upcoming-events/section.json +51 -0
- package/src/actions/index.ts +205 -174
- package/src/components/AiSettings.tsx +45 -5
- package/src/components/DesignManager.tsx +112 -16
- package/src/components/MobileApp.tsx +392 -0
- package/src/components/ThemeSections.astro +40 -21
- package/src/components/ThemeSectionsEditor.tsx +757 -229
- package/src/components/booking/BookingFlow.astro +6 -3
- package/src/components/calendar/BookingsHead.astro +51 -0
- package/src/components/calendar/CalendarBoard.tsx +618 -0
- package/src/components/calendar/MiniMonth.tsx +54 -0
- package/src/components/calendar/NewEventDialog.tsx +44 -0
- package/src/components/calendar/QuickAdd.tsx +263 -0
- package/src/components/calendar/RoomHolds.tsx +203 -0
- package/src/components/calendar/WhereItLands.tsx +51 -0
- package/src/components/editor/EventPanel.tsx +23 -2
- package/src/components/editor/PostEditor.tsx +206 -14
- package/src/components/import/ImportExport.tsx +4 -1
- package/src/components/import/runner.ts +1 -0
- package/src/components/sections/SectionFields.tsx +560 -0
- package/src/components/sections/SectionPicker.tsx +120 -0
- package/src/components/sections/TranslationPanel.tsx +143 -0
- package/src/components/sections/checks.ts +64 -0
- package/src/layouts/Admin.astro +11 -7
- package/src/layouts/Site.astro +136 -21
- package/src/lib/agent-discovery.ts +11 -7
- package/src/lib/agent.ts +35 -2
- package/src/lib/ai-schema.ts +5 -0
- package/src/lib/ai.ts +5 -0
- package/src/lib/api.ts +12 -0
- package/src/lib/backup/content.ts +6 -1
- package/src/lib/booking/agent.ts +3 -2
- package/src/lib/booking/approval.ts +70 -0
- package/src/lib/booking/holds.ts +120 -0
- package/src/lib/booking/service.ts +37 -3
- package/src/lib/calendar/agent-schema.ts +9 -1
- package/src/lib/calendar/agent.ts +16 -1
- package/src/lib/calendar/board.ts +171 -0
- package/src/lib/calendar/config.ts +3 -0
- package/src/lib/calendar/grid.ts +169 -0
- package/src/lib/calendar/migration.ts +16 -0
- package/src/lib/calendar/render.ts +9 -7
- package/src/lib/calendar/schedule.ts +166 -0
- package/src/lib/content-service.ts +203 -0
- package/src/lib/design-service.ts +153 -5
- package/src/lib/drafts.ts +3 -3
- package/src/lib/editor/auth.ts +264 -0
- package/src/lib/editor/bookings.ts +60 -0
- package/src/lib/editor/config.ts +25 -0
- package/src/lib/editor/documents.ts +227 -0
- package/src/lib/editor/http.ts +48 -0
- package/src/lib/editor/me.ts +30 -0
- package/src/lib/editor/migration.ts +57 -0
- package/src/lib/editor/next.ts +11 -0
- package/src/lib/editor/openapi.ts +124 -0
- package/src/lib/editor/schemas.ts +91 -0
- package/src/lib/editor/sections.ts +96 -0
- package/src/lib/editor/store.ts +184 -0
- package/src/lib/editor/token-route.ts +74 -0
- package/src/lib/editor/translations.ts +163 -0
- package/src/lib/email/accounts.ts +2 -1
- package/src/lib/email/types.ts +2 -1
- package/src/lib/home-source.ts +34 -0
- package/src/lib/i18n.ts +34 -0
- package/src/lib/import/apply.ts +31 -2
- package/src/lib/import/bundle.ts +4 -0
- package/src/lib/import/export.ts +1 -0
- package/src/lib/import/ghost.ts +1 -0
- package/src/lib/media/upload.ts +83 -0
- package/src/lib/mobile/associations.ts +25 -0
- package/src/lib/mobile/calendar.ts +26 -14
- package/src/lib/mobile/config.mjs +59 -0
- package/src/lib/mobile/content.ts +18 -13
- package/src/lib/mobile/embed.ts +83 -0
- package/src/lib/mobile/http.ts +14 -6
- package/src/lib/mobile/locale.ts +18 -0
- package/src/lib/mobile/openapi.ts +111 -0
- package/src/lib/mobile/pages.ts +71 -0
- package/src/lib/mobile/route.ts +3 -3
- package/src/lib/mobile/schemas.ts +60 -0
- package/src/lib/mobile/site.ts +41 -0
- package/src/lib/push/api.ts +158 -0
- package/src/lib/push/config.ts +20 -0
- package/src/lib/push/expo.ts +76 -0
- package/src/lib/push/migration.ts +60 -0
- package/src/lib/push/service.ts +302 -0
- package/src/lib/push/store.ts +144 -0
- package/src/lib/push/types.ts +43 -0
- package/src/lib/repo/migrations.ts +14 -0
- package/src/lib/repo/sql.ts +65 -20
- package/src/lib/repo/types.ts +10 -4
- package/src/lib/roles.ts +5 -0
- package/src/lib/scheduler.ts +6 -1
- package/src/lib/schema.ts +76 -1
- package/src/lib/section-ai.ts +55 -0
- package/src/lib/section-targets.ts +62 -0
- package/src/lib/sections-agent.ts +146 -0
- package/src/lib/sections.ts +242 -0
- package/src/lib/site-chrome.ts +34 -0
- package/src/lib/theme-assets.ts +16 -0
- package/src/lib/theme.ts +33 -4
- package/src/lib/translate.ts +97 -0
- package/src/lib/translations.ts +39 -0
- package/src/middleware.ts +38 -2
- package/src/pages/.well-known/apple-app-site-association.ts +6 -0
- package/src/pages/.well-known/assetlinks.json.ts +6 -0
- package/src/pages/[...slug].astro +8 -3
- package/src/pages/admin/app-auth.astro +159 -0
- package/src/pages/admin/c/[collection]/[id].astro +9 -0
- package/src/pages/admin/calendar/bookings.astro +32 -26
- package/src/pages/admin/calendar/calendars.astro +4 -263
- package/src/pages/admin/calendar/events/[id].astro +9 -0
- package/src/pages/admin/calendar/events/index.astro +41 -83
- package/src/pages/admin/calendar/index.astro +43 -0
- package/src/pages/admin/calendar/resources/[id].astro +27 -5
- package/src/pages/admin/calendar/resources/index.astro +16 -17
- package/src/pages/admin/calendar/resources/link.astro +52 -12
- package/src/pages/admin/calendar/settings.astro +458 -0
- package/src/pages/admin/integrations.astro +5 -11
- package/src/pages/admin/login.astro +6 -2
- package/src/pages/admin/logout.ts +5 -2
- package/src/pages/admin/mobile.astro +18 -0
- package/src/pages/admin/pages/[id].astro +62 -0
- package/src/pages/admin/section-preview.astro +61 -0
- package/src/pages/admin/users.astro +107 -11
- package/src/pages/api/agent/docs.ts +6 -2
- package/src/pages/api/agent/index.ts +1 -1
- package/src/pages/api/ai/settings.ts +7 -4
- package/src/pages/api/design/ai.ts +15 -0
- package/src/pages/api/design/index.ts +12 -42
- package/src/pages/api/design/translate.ts +15 -0
- package/src/pages/api/live/thumb.ts +1 -1
- package/src/pages/api/mcp.ts +2 -2
- package/src/pages/api/media/upload.ts +10 -78
- package/src/pages/api/push/admin.ts +27 -0
- package/src/pages/api/v1/admin/bookings/[id]/[action].ts +14 -0
- package/src/pages/api/v1/admin/bookings/index.ts +6 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/[action].ts +17 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/index.ts +20 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/translations/[locale]/[action].ts +26 -0
- package/src/pages/api/v1/admin/documents/[kind]/[id]/translations/[locale]/index.ts +18 -0
- package/src/pages/api/v1/admin/documents/index.ts +13 -0
- package/src/pages/api/v1/admin/me.ts +6 -0
- package/src/pages/api/v1/admin/media/index.ts +32 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/index.ts +14 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/publish.ts +14 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/translations/[locale]/[action].ts +22 -0
- package/src/pages/api/v1/admin/sections/[pageId]/[sectionId]/translations/[locale]/index.ts +14 -0
- package/src/pages/api/v1/admin/sections/index.ts +6 -0
- package/src/pages/api/v1/auth/revoke.ts +5 -0
- package/src/pages/api/v1/auth/token.ts +5 -0
- package/src/pages/api/v1/devices/[token].ts +20 -0
- package/src/pages/api/v1/devices/index.ts +17 -0
- package/src/pages/api/v1/openapi.json.ts +7 -0
- package/src/pages/api/v1/pages/[...slug].ts +7 -0
- package/src/pages/api/v1/pages/home.ts +7 -0
- package/src/pages/api/v1/push/topics.ts +7 -0
- package/src/pages/api/v1/site.ts +7 -0
- package/src/pages/api/v1/theme.ts +7 -0
- package/src/pages/blog/[slug].astro +8 -2
- package/src/pages/blog/index.astro +11 -4
- package/src/pages/book/[resource].ics.ts +7 -1
- package/src/pages/embed/section/[...path].astro +42 -0
- package/src/pages/events/[slug].astro +7 -2
- package/src/pages/index.astro +11 -5
- package/src/pages/sitemap.xml.ts +8 -0
- package/src/styles/admin.css +117 -0
- package/src/styles/cards.css +5 -1
- package/src/styles/editor.css +57 -0
- package/src/styles/mobile-app.css +151 -0
- package/src/styles/schedule.css +325 -0
- package/src/styles/sections.css +21 -12
- package/src/styles/theme-editor.css +255 -0
- package/src/theme-sdk/contract.d.mts +2193 -89
- package/src/theme-sdk/contract.mjs +77 -20
- package/src/theme-sdk/fields.d.mts +88 -0
- package/src/theme-sdk/fields.mjs +330 -0
- package/src/theme-sdk/index.ts +14 -2
- package/src/theme-sdk/tokens.d.mts +11 -0
- package/src/virtual.d.ts +4 -0
- package/src/pages/admin/calendar/index.ts +0 -4
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Admin from '../../../../layouts/Admin.astro';
|
|
3
3
|
import Modal from '../../../../components/Modal.astro';
|
|
4
|
+
import BookingsHead from '../../../../components/calendar/BookingsHead.astro';
|
|
5
|
+
import { approvalShort, linkApproval } from '../../../../lib/booking/approval';
|
|
4
6
|
import { getRepo } from '../../../../lib/repo';
|
|
5
7
|
import { slugify } from '../../../../lib/schema';
|
|
6
8
|
import { bookingsEnabled, CALENDAR, resourceUrl } from '../../../../lib/calendar/config';
|
|
@@ -9,8 +11,9 @@ import { availabilityContext } from '../../../../lib/booking/service';
|
|
|
9
11
|
import { describeLink, nextOpenings } from '../../../../lib/booking/availability';
|
|
10
12
|
import { fmt } from '../../../../lib/booking/view';
|
|
11
13
|
|
|
12
|
-
//
|
|
13
|
-
// shows its booking links
|
|
14
|
+
// Bookings → Rooms & people: everything people can book (rooms, people,
|
|
15
|
+
// vehicles, equipment). Each row shows its booking links, whether they
|
|
16
|
+
// confirm instantly, and when it's next free.
|
|
14
17
|
if (!bookingsEnabled()) return new Response(null, { status: 404 });
|
|
15
18
|
const repo = await getRepo(Astro.locals);
|
|
16
19
|
const settings = await repo.getSettings();
|
|
@@ -26,8 +29,9 @@ if (Astro.request.method === 'POST') {
|
|
|
26
29
|
const kind = String(form.get('kind') || CALENDAR.resourceKinds[0]).slice(0, 40);
|
|
27
30
|
const capacity = form.get('capacity') ? int(form.get('capacity'), 1, 100000, 1) : null;
|
|
28
31
|
const r = await repo.createResource({ name, slug, kind, capacity, hours: DEFAULT_HOURS, timezone: '' });
|
|
29
|
-
// every
|
|
30
|
-
|
|
32
|
+
// every room or person starts with one ready-to-share link that follows
|
|
33
|
+
// the site's approval setting (Calendar settings → Booking approval)
|
|
34
|
+
await repo.createBookingLink({ resourceId: r.id, name: kind === 'Person' ? 'Meeting' : 'Book', slug: 'book', duration: 60, step: 30 });
|
|
31
35
|
return Astro.redirect(`/admin/calendar/resources/${r.id}?created=1`);
|
|
32
36
|
}
|
|
33
37
|
if (op === 'move') {
|
|
@@ -40,7 +44,7 @@ if (Astro.request.method === 'POST') {
|
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
const [resources, links] = await Promise.all([repo.listResources(), repo.listBookingLinks()]);
|
|
47
|
+
const [resources, links, pending] = await Promise.all([repo.listResources(), repo.listBookingLinks(), repo.listBookings({ from: Date.now(), statuses: ['pending'], kinds: ['booking', 'rsvp'] })]);
|
|
44
48
|
const now = Date.now();
|
|
45
49
|
const rows = await Promise.all(resources.map(async (r) => {
|
|
46
50
|
const own = links.filter((l) => l.resourceId === r.id);
|
|
@@ -51,16 +55,11 @@ const rows = await Promise.all(resources.map(async (r) => {
|
|
|
51
55
|
}));
|
|
52
56
|
---
|
|
53
57
|
|
|
54
|
-
<Admin title="
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
<h1>Resources</h1>
|
|
58
|
-
<p class="sub">Rooms, people and things people can book · public list at <a href={CALENDAR.bookBase} target="_blank" rel="noopener">{CALENDAR.bookBase}</a></p>
|
|
59
|
-
</div>
|
|
60
|
-
<div class="page-actions"><button class="btn primary" type="button" data-modal="res-new">New resource</button></div>
|
|
61
|
-
</div>
|
|
58
|
+
<Admin title="Rooms & people" section="calendar:bookings">
|
|
59
|
+
<BookingsHead active="rooms" pending={pending.length} rooms={resources.length}
|
|
60
|
+
sub={`What people can book, and when it’s free · public list at ${CALENDAR.bookBase}`} />
|
|
62
61
|
|
|
63
|
-
<Modal id="
|
|
62
|
+
<Modal id="new-resource" title="New room or person" open={Astro.url.searchParams.has('new')}>
|
|
64
63
|
<form method="post">
|
|
65
64
|
<input type="hidden" name="op" value="create" />
|
|
66
65
|
<label class="field"><span>Name</span><input type="text" name="name" placeholder="Fellowship Hall" required autofocus /></label>
|
|
@@ -69,7 +68,7 @@ const rows = await Promise.all(resources.map(async (r) => {
|
|
|
69
68
|
<label class="field"><span>Capacity <em class="hint">optional</em></span><input type="number" name="capacity" min="1" placeholder="120" /></label>
|
|
70
69
|
</div>
|
|
71
70
|
<div class="modal-foot"><button class="btn ghost" type="button" data-close>Cancel</button><button class="btn primary" type="submit">Create</button></div>
|
|
72
|
-
<p class="hint modal-note">Starts open weekdays 9–5 with one booking link that
|
|
71
|
+
<p class="hint modal-note">Starts open weekdays 9–5 with one booking link that follows your approval setting. Change both on the next screen.</p>
|
|
73
72
|
</form>
|
|
74
73
|
</Modal>
|
|
75
74
|
|
|
@@ -77,7 +76,7 @@ const rows = await Promise.all(resources.map(async (r) => {
|
|
|
77
76
|
<div class="empty-state">
|
|
78
77
|
<b>Nothing to book yet</b>
|
|
79
78
|
<p>Add a room, a person’s time, or a piece of equipment. Each gets its own booking page and links.</p>
|
|
80
|
-
<button class="btn primary" type="button" data-modal="
|
|
79
|
+
<button class="btn primary" type="button" data-modal="new-resource">Add a room or person</button>
|
|
81
80
|
</div>
|
|
82
81
|
) : (
|
|
83
82
|
<div class="res-list">
|
|
@@ -89,7 +88,7 @@ const rows = await Promise.all(resources.map(async (r) => {
|
|
|
89
88
|
<a class="res-main" href={`/admin/calendar/resources/${r.id}`}>
|
|
90
89
|
<b>{r.name} <span class="pill lang">{r.kind}</span>{r.visibility === 'unlisted' && <span class="pill lang">Unlisted</span>}</b>
|
|
91
90
|
<small>{[r.capacity ? `Up to ${r.capacity}` : '', `${own.length} link${own.length === 1 ? '' : 's'}`, next ? `next free ${next}` : 'no openings soon'].filter(Boolean).join(' · ')}</small>
|
|
92
|
-
<span class="res-links">{own.map((l) => <span class={`res-link${l.active ? '' : ' is-off'}`}>{l.name} · {describeLink(l)} · <em class={
|
|
91
|
+
<span class="res-links">{own.map((l) => { const a = linkApproval(l, settings); return <span class={`res-link${l.active ? '' : ' is-off'}`}>{l.name} · {describeLink(l)} · <em class={a.mode === 'auto' ? 'auto' : 'manual'}>{approvalShort(a)}</em></span>; })}</span>
|
|
93
92
|
</a>
|
|
94
93
|
<div class="cal-actions">
|
|
95
94
|
<a class="icon-btn" href={resourceUrl(r.slug)} target="_blank" rel="noopener" title="View booking page" aria-label="View booking page"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5" /></svg></a>
|
|
@@ -7,9 +7,12 @@ import { bookingLinkUrl, bookingsEnabled } from '../../../../lib/calendar/config
|
|
|
7
7
|
import { hoursFromForm, int, list, questionsFromForm } from '../../../../lib/booking/forms';
|
|
8
8
|
import { describeLink } from '../../../../lib/booking/availability';
|
|
9
9
|
import { siteOrigin } from '../../../../lib/seo';
|
|
10
|
+
import { APPROVAL_LABEL, linkApproval, siteApproval } from '../../../../lib/booking/approval';
|
|
11
|
+
import { ApprovalRules } from '../../../../lib/schema';
|
|
10
12
|
|
|
11
|
-
// One booking link
|
|
12
|
-
// own, when it's open (within the
|
|
13
|
+
// One booking link: how people book, how bookings get approved (follow the
|
|
14
|
+
// site setting, or its own mode + rules), when it's open (within the room or
|
|
15
|
+
// person's hours), and what we ask.
|
|
13
16
|
if (!bookingsEnabled()) return new Response(null, { status: 404 });
|
|
14
17
|
const repo = await getRepo(Astro.locals);
|
|
15
18
|
const resource = await repo.getResource(Astro.params.id!);
|
|
@@ -33,6 +36,14 @@ if (Astro.request.method === 'POST') {
|
|
|
33
36
|
for (let n = 2; ; n++) { const hit = await repo.getBookingLinkBySlug(resource.id, slug); if (!hit || hit.id === link.id) break; slug = `${base}-${n}`; }
|
|
34
37
|
const mode = (['slots', 'range', 'day'] as const).find((m) => m === form.get('mode')) ?? 'slots';
|
|
35
38
|
const lengths = list(String(form.get('lengths') || '')).map((x) => Math.round(Number(x) * 60)).filter((n) => n >= 15 && n <= 1440);
|
|
39
|
+
const approval = (['default', 'auto', 'rules', 'manual'] as const).find((m) => m === form.get('approval')) ?? 'default';
|
|
40
|
+
const approvalRules = ApprovalRules.parse({
|
|
41
|
+
longerThan: form.has('ruleLonger') ? int(form.get('longerThanHours'), 1, 24 * 60, 4) * 60 : 0,
|
|
42
|
+
firstTime: form.has('ruleFirst'),
|
|
43
|
+
moreSeats: form.has('ruleSeats') ? int(form.get('moreSeats'), 1, 100000, 20) : 0,
|
|
44
|
+
deadlineHours: form.has('deadline') ? int(form.get('deadlineHours'), 1, 24 * 60, 48) : 0,
|
|
45
|
+
deadlineAction: form.get('deadlineAction') === 'decline' ? 'decline' : 'approve',
|
|
46
|
+
});
|
|
36
47
|
await repo.updateBookingLink(link.id, {
|
|
37
48
|
name, slug, mode: mode as BookingMode,
|
|
38
49
|
description: String(form.get('description') || '').slice(0, 1000),
|
|
@@ -47,7 +58,9 @@ if (Astro.request.method === 'POST') {
|
|
|
47
58
|
maxAdvance: int(form.get('maxAdvance'), 1, 730, 90),
|
|
48
59
|
perDayCap: int(form.get('perDayCap'), 0, 100, 0),
|
|
49
60
|
hours: form.get('customHours') ? hoursFromForm(form, 'lh') : null,
|
|
50
|
-
|
|
61
|
+
approval, approvalRules,
|
|
62
|
+
// legacy flag for older API readers; approval is what decides
|
|
63
|
+
autoConfirm: linkApproval({ approval, approvalRules }, settings).mode === 'auto',
|
|
51
64
|
visibility: form.get('visibility') === 'unlisted' ? 'unlisted' : 'public',
|
|
52
65
|
askPhone: Boolean(form.get('askPhone')),
|
|
53
66
|
questions: questionsFromForm(form),
|
|
@@ -62,12 +75,15 @@ const publicUrl = origin + bookingLinkUrl(resource.slug, link.slug);
|
|
|
62
75
|
const embed = `<iframe src="${origin}/embed/book/${resource.slug}/${link.slug}" style="width:100%;height:620px;border:0" title="${link.name}"></iframe>`;
|
|
63
76
|
const qRows = [...link.questions, ...Array.from({ length: 2 }, () => ({ id: '', label: '', type: 'text' as const, required: false, options: [] as string[], help: '' }))];
|
|
64
77
|
const hours = (link.hours ?? resource.hours);
|
|
78
|
+
const site = siteApproval(settings);
|
|
79
|
+
const own = link.approval === 'default' ? site.rules : link.approvalRules;
|
|
80
|
+
const effective = linkApproval(link, settings).mode;
|
|
65
81
|
---
|
|
66
82
|
|
|
67
|
-
<Admin title={link.name} section="calendar:
|
|
83
|
+
<Admin title={link.name} section="calendar:bookings">
|
|
68
84
|
<div class="page-head">
|
|
69
85
|
<div>
|
|
70
|
-
<p class="sub"><a href="/admin/calendar/resources">
|
|
86
|
+
<p class="sub"><a href="/admin/calendar/bookings">Bookings</a> › <a href="/admin/calendar/resources">Rooms & people</a> › <a href={`/admin/calendar/resources/${resource.id}`}>{resource.name}</a> ›</p>
|
|
71
87
|
<h1>{link.name}</h1>
|
|
72
88
|
<p class="sub"><a href={publicUrl} target="_blank" rel="noopener">{publicUrl.replace(/^https?:\/\//, '')} ↗</a> · {describeLink(link)}</p>
|
|
73
89
|
</div>
|
|
@@ -81,11 +97,32 @@ const hours = (link.hours ?? resource.hours);
|
|
|
81
97
|
<form method="post" id="link-form" class="res-grid">
|
|
82
98
|
<input type="hidden" name="op" value="save" />
|
|
83
99
|
<div>
|
|
84
|
-
<section class=
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
100
|
+
<section class="card card-pad appr" data-approval>
|
|
101
|
+
<div class="card-head"><b>Approval</b><span class="hint">What happens when someone books this link</span></div>
|
|
102
|
+
<div class="appr-choices" role="radiogroup" aria-label="Approval">
|
|
103
|
+
<label class="appr-choice"><input type="radio" name="approval" value="default" checked={link.approval === 'default'} />
|
|
104
|
+
<span><b>Use the site setting</b><small>Currently: {APPROVAL_LABEL[site.mode]}. <a href="/admin/calendar/settings#bookings">Change it</a></small></span></label>
|
|
105
|
+
<label class="appr-choice"><input type="radio" name="approval" value="auto" checked={link.approval === 'auto'} />
|
|
106
|
+
<span><b>{APPROVAL_LABEL.auto}</b><small>Booked means confirmed. It shows on your Schedule right away.</small></span></label>
|
|
107
|
+
<label class="appr-choice"><input type="radio" name="approval" value="rules" checked={link.approval === 'rules'} />
|
|
108
|
+
<span><b>{APPROVAL_LABEL.rules}</b><small>Confirm most bookings at once; hold unusual ones for you.</small></span></label>
|
|
109
|
+
<label class="appr-choice"><input type="radio" name="approval" value="manual" checked={link.approval === 'manual'} />
|
|
110
|
+
<span><b>{APPROVAL_LABEL.manual}</b><small>Every booking waits under Needs approval.</small></span></label>
|
|
111
|
+
</div>
|
|
112
|
+
<fieldset class="appr-rules" data-rules hidden={link.approval !== 'rules'}>
|
|
113
|
+
<legend>Ask me first when a booking is…</legend>
|
|
114
|
+
<label class="appr-check"><input type="checkbox" name="ruleLonger" checked={own.longerThan > 0} /> longer than <input type="number" name="longerThanHours" min="1" max="1440" value={own.longerThan ? Math.round(own.longerThan / 60) : 4} /> hours</label>
|
|
115
|
+
<label class="appr-check"><input type="checkbox" name="ruleFirst" checked={own.firstTime} /> the first booking from that email address</label>
|
|
116
|
+
<label class="appr-check"><input type="checkbox" name="ruleSeats" checked={own.moreSeats > 0} /> for more than <input type="number" name="moreSeats" min="1" max="100000" value={own.moreSeats || 20} /> people</label>
|
|
117
|
+
</fieldset>
|
|
118
|
+
<div class="appr-deadline" data-deadline hidden={link.approval === 'default' || link.approval === 'auto'}>
|
|
119
|
+
<label class="appr-check"><input type="checkbox" name="deadline" checked={own.deadlineHours > 0} /> If I haven’t answered in <input type="number" name="deadlineHours" min="1" max="1440" value={own.deadlineHours || 48} /> hours,</label>
|
|
120
|
+
<select name="deadlineAction" aria-label="What happens then">
|
|
121
|
+
<option value="approve" selected={own.deadlineAction === 'approve'}>approve it</option>
|
|
122
|
+
<option value="decline" selected={own.deadlineAction === 'decline'}>decline it</option>
|
|
123
|
+
</select>
|
|
124
|
+
</div>
|
|
125
|
+
<p class="hint appr-now">Right now this link {effective === 'auto' ? 'confirms every booking instantly' : effective === 'rules' ? 'confirms most bookings instantly' : 'holds every booking for approval'}.</p>
|
|
89
126
|
</section>
|
|
90
127
|
|
|
91
128
|
<section class="card card-pad">
|
|
@@ -175,8 +212,11 @@ const hours = (link.hours ?? resource.hours);
|
|
|
175
212
|
const mode = (form.querySelector<HTMLInputElement>('input[name=mode]:checked')?.value) ?? 'slots';
|
|
176
213
|
form.querySelectorAll<HTMLElement>('.mode-seg label').forEach((l) => l.classList.toggle('on', l.querySelector('input')?.checked ?? false));
|
|
177
214
|
form.querySelectorAll<HTMLElement>('.mode-fields [data-mode]').forEach((el) => { el.hidden = !(el.dataset.mode ?? '').split(' ').includes(mode); });
|
|
178
|
-
const
|
|
179
|
-
form.querySelector('
|
|
215
|
+
const appr = (form.querySelector<HTMLInputElement>('input[name=approval]:checked')?.value) ?? 'default';
|
|
216
|
+
const rules = form.querySelector<HTMLElement>('[data-rules]');
|
|
217
|
+
const deadline = form.querySelector<HTMLElement>('[data-deadline]');
|
|
218
|
+
if (rules) rules.hidden = appr !== 'rules';
|
|
219
|
+
if (deadline) deadline.hidden = appr === 'default' || appr === 'auto';
|
|
180
220
|
};
|
|
181
221
|
form.addEventListener('change', (e) => {
|
|
182
222
|
sync();
|