@sneat/extension-eventus-shared 0.0.1

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.
Files changed (20) hide show
  1. package/fesm2022/sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs +153 -0
  2. package/fesm2022/sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs.map +1 -0
  3. package/fesm2022/sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs +90 -0
  4. package/fesm2022/sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs.map +1 -0
  5. package/fesm2022/sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs +103 -0
  6. package/fesm2022/sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs.map +1 -0
  7. package/fesm2022/sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs +143 -0
  8. package/fesm2022/sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs.map +1 -0
  9. package/fesm2022/sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs +72 -0
  10. package/fesm2022/sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs.map +1 -0
  11. package/fesm2022/sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs +94 -0
  12. package/fesm2022/sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs.map +1 -0
  13. package/fesm2022/sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs +78 -0
  14. package/fesm2022/sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs.map +1 -0
  15. package/fesm2022/sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs +92 -0
  16. package/fesm2022/sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs.map +1 -0
  17. package/fesm2022/sneat-extension-eventus-shared.mjs +270 -0
  18. package/fesm2022/sneat-extension-eventus-shared.mjs.map +1 -0
  19. package/package.json +43 -0
  20. package/types/sneat-extension-eventus-shared.d.ts +60 -0
@@ -0,0 +1,92 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, signal, Component } from '@angular/core';
3
+ import { IonHeader, IonToolbar, IonTitle, IonButtons, IonBackButton, IonButton, IonContent, IonList, IonListHeader, IonItem, IonLabel, IonNote, IonText, IonSpinner } from '@ionic/angular/standalone';
4
+ import { INVITATION_SERVICE, LINK_SERVICE, extractErrorMessage } from '@sneat/extension-eventus-contract';
5
+ import * as QRCode from 'qrcode';
6
+
7
+ // Host screen to distribute RSVP links: a unique tokenized link + QR per
8
+ // invitation, and one open (shared) event link + QR. QR images are rendered
9
+ // client-side from each link's `url`; links are copied via the Clipboard API.
10
+ // (AC: per-invitee-link-and-qr-issued, open-link-issued-and-anonymous)
11
+ class ShareLinksPageComponent {
12
+ invitationService = inject(INVITATION_SERVICE);
13
+ linkService = inject(LINK_SERVICE);
14
+ spaceID = input.required(...(ngDevMode ? [{ debugName: "spaceID" }] : /* istanbul ignore next */ []));
15
+ eventID = input.required(...(ngDevMode ? [{ debugName: "eventID" }] : /* istanbul ignore next */ []));
16
+ invitations = signal([], ...(ngDevMode ? [{ debugName: "invitations" }] : /* istanbul ignore next */ []));
17
+ loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
18
+ error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
19
+ /** Resolved invitee links keyed by invitation id. */
20
+ inviteeLinks = signal({}, ...(ngDevMode ? [{ debugName: "inviteeLinks" }] : /* istanbul ignore next */ []));
21
+ /** The open event link, once issued. */
22
+ openLink = signal(undefined, ...(ngDevMode ? [{ debugName: "openLink" }] : /* istanbul ignore next */ []));
23
+ ngOnInit() {
24
+ this.invitationService
25
+ .listInvitations(this.spaceID(), this.eventID())
26
+ .subscribe({
27
+ next: (invitations) => {
28
+ this.invitations.set(invitations);
29
+ this.loading.set(false);
30
+ },
31
+ error: (err) => {
32
+ this.loading.set(false);
33
+ this.error.set(extractErrorMessage(err, 'Failed to load invitations.'));
34
+ },
35
+ });
36
+ }
37
+ getInviteeLink(invitationID) {
38
+ this.error.set(undefined);
39
+ this.linkService
40
+ .issueInviteeLink(this.spaceID(), this.eventID(), invitationID)
41
+ .subscribe({
42
+ next: (link) => {
43
+ void this.render(link).then((rendered) => this.inviteeLinks.update((map) => ({
44
+ ...map,
45
+ [invitationID]: rendered,
46
+ })));
47
+ },
48
+ error: (err) => this.error.set(extractErrorMessage(err, 'Failed to issue link.')),
49
+ });
50
+ }
51
+ getOpenLink() {
52
+ this.error.set(undefined);
53
+ this.linkService.issueOpenLink(this.spaceID(), this.eventID()).subscribe({
54
+ next: (link) => {
55
+ void this.render(link).then((rendered) => this.openLink.set(rendered));
56
+ },
57
+ error: (err) => this.error.set(extractErrorMessage(err, 'Failed to issue open link.')),
58
+ });
59
+ }
60
+ copy(url) {
61
+ void navigator.clipboard.writeText(url);
62
+ }
63
+ /** Render a QR data URL for the link's `url`. */
64
+ async render(link) {
65
+ const qr = await QRCode.toDataURL(link.url);
66
+ return { link, qr };
67
+ }
68
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ShareLinksPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
69
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ShareLinksPageComponent, isStandalone: true, selector: "eventus-share-links-page", inputs: { spaceID: { classPropertyName: "spaceID", publicName: "spaceID", isSignal: true, isRequired: true, transformFunction: null }, eventID: { classPropertyName: "eventID", publicName: "eventID", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Share links</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-list>\n <ion-list-header>Open event link</ion-list-header>\n @if (openLink(); as open) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>{{ open.link.url }}</p>\n <img [src]=\"open.qr\" alt=\"Open event link QR code\" width=\"160\" height=\"160\" />\n </ion-label>\n <ion-button slot=\"end\" fill=\"outline\" (click)=\"copy(open.link.url)\">\n Copy\n </ion-button>\n </ion-item>\n } @else {\n <ion-item>\n <ion-note>One shareable link anyone can RSVP through.</ion-note>\n <ion-button slot=\"end\" (click)=\"getOpenLink()\">Get open link</ion-button>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Per-invitee links</ion-list-header>\n @if (loading()) {\n <ion-item>\n <ion-spinner aria-label=\"Loading invitations\"></ion-spinner>\n </ion-item>\n } @else {\n @for (invitation of invitations(); track invitation.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>{{ invitation.inviteeType }}</h3>\n <ion-note>\n {{ invitation.familySpaceID ?? invitation.contactID }}\n </ion-note>\n @if (inviteeLinks()[invitation.id]; as rendered) {\n <p>{{ rendered.link.url }}</p>\n <img\n [src]=\"rendered.qr\"\n alt=\"RSVP link QR code\"\n width=\"160\"\n height=\"160\"\n />\n }\n </ion-label>\n @if (inviteeLinks()[invitation.id]; as rendered) {\n <ion-button slot=\"end\" fill=\"outline\" (click)=\"copy(rendered.link.url)\">\n Copy\n </ion-button>\n } @else {\n <ion-button slot=\"end\" (click)=\"getInviteeLink(invitation.id)\">\n Get link\n </ion-button>\n }\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n }\n </ion-list>\n</ion-content>\n", dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonBackButton, selector: "ion-back-button" }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }] });
70
+ }
71
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ShareLinksPageComponent, decorators: [{
72
+ type: Component,
73
+ args: [{ selector: 'eventus-share-links-page', imports: [
74
+ IonHeader,
75
+ IonToolbar,
76
+ IonTitle,
77
+ IonButtons,
78
+ IonBackButton,
79
+ IonButton,
80
+ IonContent,
81
+ IonList,
82
+ IonListHeader,
83
+ IonItem,
84
+ IonLabel,
85
+ IonNote,
86
+ IonText,
87
+ IonSpinner,
88
+ ], template: "<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Share links</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-list>\n <ion-list-header>Open event link</ion-list-header>\n @if (openLink(); as open) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>{{ open.link.url }}</p>\n <img [src]=\"open.qr\" alt=\"Open event link QR code\" width=\"160\" height=\"160\" />\n </ion-label>\n <ion-button slot=\"end\" fill=\"outline\" (click)=\"copy(open.link.url)\">\n Copy\n </ion-button>\n </ion-item>\n } @else {\n <ion-item>\n <ion-note>One shareable link anyone can RSVP through.</ion-note>\n <ion-button slot=\"end\" (click)=\"getOpenLink()\">Get open link</ion-button>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Per-invitee links</ion-list-header>\n @if (loading()) {\n <ion-item>\n <ion-spinner aria-label=\"Loading invitations\"></ion-spinner>\n </ion-item>\n } @else {\n @for (invitation of invitations(); track invitation.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>{{ invitation.inviteeType }}</h3>\n <ion-note>\n {{ invitation.familySpaceID ?? invitation.contactID }}\n </ion-note>\n @if (inviteeLinks()[invitation.id]; as rendered) {\n <p>{{ rendered.link.url }}</p>\n <img\n [src]=\"rendered.qr\"\n alt=\"RSVP link QR code\"\n width=\"160\"\n height=\"160\"\n />\n }\n </ion-label>\n @if (inviteeLinks()[invitation.id]; as rendered) {\n <ion-button slot=\"end\" fill=\"outline\" (click)=\"copy(rendered.link.url)\">\n Copy\n </ion-button>\n } @else {\n <ion-button slot=\"end\" (click)=\"getInviteeLink(invitation.id)\">\n Get link\n </ion-button>\n }\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n }\n </ion-list>\n</ion-content>\n" }]
89
+ }], propDecorators: { spaceID: [{ type: i0.Input, args: [{ isSignal: true, alias: "spaceID", required: true }] }], eventID: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventID", required: true }] }] } });
90
+
91
+ export { ShareLinksPageComponent };
92
+ //# sourceMappingURL=sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs","sources":["../../../../../../libs/extensions/eventus/shared/src/lib/pages/share-links/share-links-page.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/pages/share-links/share-links-page.component.html"],"sourcesContent":["import { Component, OnInit, inject, input, signal } from '@angular/core';\nimport {\n IonBackButton,\n IonButton,\n IonButtons,\n IonContent,\n IonHeader,\n IonItem,\n IonLabel,\n IonList,\n IonListHeader,\n IonNote,\n IonSpinner,\n IonText,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n extractErrorMessage,\n IInvitation,\n INVITATION_SERVICE,\n IRsvpLink,\n LINK_SERVICE,\n} from '@sneat/extension-eventus-contract';\nimport * as QRCode from 'qrcode';\n\n/** A resolved link plus its rendered QR data URL, shown in the UI. */\ninterface IRenderedLink {\n link: IRsvpLink;\n qr: string;\n}\n\n// Host screen to distribute RSVP links: a unique tokenized link + QR per\n// invitation, and one open (shared) event link + QR. QR images are rendered\n// client-side from each link's `url`; links are copied via the Clipboard API.\n// (AC: per-invitee-link-and-qr-issued, open-link-issued-and-anonymous)\n@Component({\n selector: 'eventus-share-links-page',\n templateUrl: './share-links-page.component.html',\n imports: [\n IonHeader,\n IonToolbar,\n IonTitle,\n IonButtons,\n IonBackButton,\n IonButton,\n IonContent,\n IonList,\n IonListHeader,\n IonItem,\n IonLabel,\n IonNote,\n IonText,\n IonSpinner,\n ],\n})\nexport class ShareLinksPageComponent implements OnInit {\n private readonly invitationService = inject(INVITATION_SERVICE);\n private readonly linkService = inject(LINK_SERVICE);\n\n readonly spaceID = input.required<string>();\n readonly eventID = input.required<string>();\n\n protected readonly invitations = signal<IInvitation[]>([]);\n protected readonly loading = signal(true);\n protected readonly error = signal<string | undefined>(undefined);\n\n /** Resolved invitee links keyed by invitation id. */\n protected readonly inviteeLinks = signal<Record<string, IRenderedLink>>({});\n /** The open event link, once issued. */\n protected readonly openLink = signal<IRenderedLink | undefined>(undefined);\n\n ngOnInit(): void {\n this.invitationService\n .listInvitations(this.spaceID(), this.eventID())\n .subscribe({\n next: (invitations) => {\n this.invitations.set(invitations);\n this.loading.set(false);\n },\n error: (err: unknown) => {\n this.loading.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to load invitations.'));\n },\n });\n }\n\n protected getInviteeLink(invitationID: string): void {\n this.error.set(undefined);\n this.linkService\n .issueInviteeLink(this.spaceID(), this.eventID(), invitationID)\n .subscribe({\n next: (link) => {\n void this.render(link).then((rendered) =>\n this.inviteeLinks.update((map) => ({\n ...map,\n [invitationID]: rendered,\n })),\n );\n },\n error: (err: unknown) =>\n this.error.set(extractErrorMessage(err, 'Failed to issue link.')),\n });\n }\n\n protected getOpenLink(): void {\n this.error.set(undefined);\n this.linkService.issueOpenLink(this.spaceID(), this.eventID()).subscribe({\n next: (link) => {\n void this.render(link).then((rendered) => this.openLink.set(rendered));\n },\n error: (err: unknown) =>\n this.error.set(extractErrorMessage(err, 'Failed to issue open link.')),\n });\n }\n\n protected copy(url: string): void {\n void navigator.clipboard.writeText(url);\n }\n\n /** Render a QR data URL for the link's `url`. */\n private async render(link: IRsvpLink): Promise<IRenderedLink> {\n const qr = await QRCode.toDataURL(link.url);\n return { link, qr };\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"start\">\n <ion-back-button defaultHref=\"/\"></ion-back-button>\n </ion-buttons>\n <ion-title>Share links</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-list>\n <ion-list-header>Open event link</ion-list-header>\n @if (openLink(); as open) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <p>{{ open.link.url }}</p>\n <img [src]=\"open.qr\" alt=\"Open event link QR code\" width=\"160\" height=\"160\" />\n </ion-label>\n <ion-button slot=\"end\" fill=\"outline\" (click)=\"copy(open.link.url)\">\n Copy\n </ion-button>\n </ion-item>\n } @else {\n <ion-item>\n <ion-note>One shareable link anyone can RSVP through.</ion-note>\n <ion-button slot=\"end\" (click)=\"getOpenLink()\">Get open link</ion-button>\n </ion-item>\n }\n </ion-list>\n\n <ion-list>\n <ion-list-header>Per-invitee links</ion-list-header>\n @if (loading()) {\n <ion-item>\n <ion-spinner aria-label=\"Loading invitations\"></ion-spinner>\n </ion-item>\n } @else {\n @for (invitation of invitations(); track invitation.id) {\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h3>{{ invitation.inviteeType }}</h3>\n <ion-note>\n {{ invitation.familySpaceID ?? invitation.contactID }}\n </ion-note>\n @if (inviteeLinks()[invitation.id]; as rendered) {\n <p>{{ rendered.link.url }}</p>\n <img\n [src]=\"rendered.qr\"\n alt=\"RSVP link QR code\"\n width=\"160\"\n height=\"160\"\n />\n }\n </ion-label>\n @if (inviteeLinks()[invitation.id]; as rendered) {\n <ion-button slot=\"end\" fill=\"outline\" (click)=\"copy(rendered.link.url)\">\n Copy\n </ion-button>\n } @else {\n <ion-button slot=\"end\" (click)=\"getInviteeLink(invitation.id)\">\n Get link\n </ion-button>\n }\n </ion-item>\n } @empty {\n <ion-item>\n <ion-note>No invitees yet.</ion-note>\n </ion-item>\n }\n }\n </ion-list>\n</ion-content>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,270 @@
1
+ import { TitleCasePipe, DatePipe } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { signal, computed, inject, ChangeDetectionStrategy, Component, input } from '@angular/core';
4
+ import { RouterLink } from '@angular/router';
5
+ import { MenuController, IonList, IonItem, IonSelect, IonSelectOption, IonIcon, IonLabel, IonHeader, IonToolbar, IonTitle, IonContent, IonInput, IonTextarea, IonNote, IonText, IonButton, IonSpinner, IonSegment, IonSegmentButton } from '@ionic/angular/standalone';
6
+ import { AuthMenuItemComponent } from '@sneat/auth-ui';
7
+ import { ContactusServicesModule } from '@sneat/extension-contactus-internal';
8
+ import { SpaceBaseComponent, SpaceComponentBaseParams } from '@sneat/space-components';
9
+ import { SpaceServiceModule } from '@sneat/space-services';
10
+ import { zipMapBriefsWithIDs } from '@sneat/space-models';
11
+ import { ClassName } from '@sneat/ui';
12
+ import { takeUntil } from 'rxjs/operators';
13
+ import * as i1 from '@angular/forms';
14
+ import { FormsModule } from '@angular/forms';
15
+ import { RSVP_SERVICE, extractErrorMessage, buildRsvpRequest } from '@sneat/extension-eventus-contract';
16
+
17
+ // Host-facing Eventus routes, mounted as children of the app's
18
+ // space/:spaceType/:spaceID route. Paths are relative to that space context;
19
+ // the spaceID (and eventID) route params bind to each page's input() via router
20
+ // component-input-binding. (AC: host-creates-event, only-host-edits)
21
+ //
22
+ // The PUBLIC responder RSVP page (/r/:token) is intentionally NOT here — it is a
23
+ // top-level, un-gated route (see the app's appRoutes), because an invitee
24
+ // follows it anonymously, outside any space.
25
+ const eventusRoutes = [
26
+ {
27
+ // The space menu links to spacePageUrl('eventus'); land that on the events
28
+ // list (the section's home). Sub-flows live under 'events/...'.
29
+ path: 'eventus',
30
+ pathMatch: 'full',
31
+ redirectTo: 'events',
32
+ },
33
+ {
34
+ path: 'events',
35
+ loadComponent: () => import('./sneat-extension-eventus-shared-events-list-page.component-DELSoZ2W.mjs').then((m) => m.EventsListPageComponent),
36
+ },
37
+ {
38
+ path: 'events/new',
39
+ loadComponent: () => import('./sneat-extension-eventus-shared-create-event-page.component-Da39fu36.mjs').then((m) => m.CreateEventPageComponent),
40
+ },
41
+ {
42
+ path: 'events/:eventID/edit',
43
+ loadComponent: () => import('./sneat-extension-eventus-shared-edit-event-page.component-lhOpcqld.mjs').then((m) => m.EditEventPageComponent),
44
+ },
45
+ {
46
+ path: 'events/:eventID/invitees',
47
+ loadComponent: () => import('./sneat-extension-eventus-shared-invitees-page.component-DGG1LDd7.mjs').then((m) => m.InviteesPageComponent),
48
+ },
49
+ {
50
+ path: 'events/:eventID/share-links',
51
+ loadComponent: () => import('./sneat-extension-eventus-shared-share-links-page.component-BzZ6tFBc.mjs').then((m) => m.ShareLinksPageComponent),
52
+ },
53
+ {
54
+ path: 'events/:eventID/responses',
55
+ loadComponent: () => import('./sneat-extension-eventus-shared-responses-page.component-L9ayYgBN.mjs').then((m) => m.ResponsesPageComponent),
56
+ },
57
+ {
58
+ path: 'events/:eventID/bring-along',
59
+ loadComponent: () => import('./sneat-extension-eventus-shared-bring-along-page.component-DHi-E8P1.mjs').then((m) => m.BringAlongPageComponent),
60
+ },
61
+ {
62
+ path: 'events/:eventID',
63
+ loadComponent: () => import('./sneat-extension-eventus-shared-event-page.component-CuOA0EU9.mjs').then((m) => m.EventPageComponent),
64
+ },
65
+ ];
66
+
67
+ // eventus-specific side menu rendered in the space "menu" outlet. Unlike the
68
+ // generic @sneat SpaceMenuComponent (which hardcodes every sneat-app extension —
69
+ // Assets, Budget, Contacts, …, none of which exist in eventus-app), this shows
70
+ // only what eventus has: a space selector (to switch spaces, like sneat-app),
71
+ // the space's Calendar (the main page) and its Events.
72
+ class EventusSpaceMenuComponent extends SpaceBaseComponent {
73
+ $spaces = signal(undefined, ...(ngDevMode ? [{ debugName: "$spaces" }] : /* istanbul ignore next */ []));
74
+ $disabled = computed(() => !this.$spaceID(), ...(ngDevMode ? [{ debugName: "$disabled" }] : /* istanbul ignore next */ []));
75
+ menuCtrl = inject(MenuController);
76
+ constructor() {
77
+ super();
78
+ this.spaceParams.userService.userState
79
+ .pipe(takeUntil(this.destroyed$))
80
+ .subscribe({
81
+ next: (userState) => this.$spaces.set(userState?.record
82
+ ? zipMapBriefsWithIDs(userState.record.spaces) || []
83
+ : undefined),
84
+ error: this.errorLogger.logErrorHandler('failed to get user state'),
85
+ });
86
+ }
87
+ onSpaceSelected(event) {
88
+ const spaceID = event.detail.value;
89
+ if (spaceID === this.space?.id) {
90
+ return;
91
+ }
92
+ const space = this.$spaces()?.find((t) => t.id === spaceID);
93
+ if (space) {
94
+ this.setSpaceRef(space);
95
+ this.spaceNav
96
+ .navigateToSpace(space)
97
+ .catch(this.errorLogger.logErrorHandler('Failed to navigate to selected space'));
98
+ }
99
+ this.closeMenu();
100
+ }
101
+ closeMenu() {
102
+ this.menuCtrl.close().catch(this.errorLogger.logError);
103
+ }
104
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EventusSpaceMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
105
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: EventusSpaceMenuComponent, isStandalone: true, selector: "eventus-space-menu", providers: [
106
+ { provide: ClassName, useValue: 'EventusSpaceMenuComponent' },
107
+ SpaceComponentBaseParams,
108
+ ], usesInheritance: true, ngImport: i0, template: "<ion-list>\n <!-- Space selector: switch between the user's spaces (like sneat-app). -->\n <ion-item>\n <ion-select\n label=\"Space\"\n [value]=\"$space().id\"\n interface=\"popover\"\n (ionChange)=\"onSpaceSelected($event)\"\n style=\"font-weight: bold\"\n >\n @for (userSpace of $spaces(); track userSpace.id) {\n <ion-select-option [value]=\"userSpace.id\">\n {{\n userSpace.brief.title ||\n (userSpace.brief.type | titlecase) ||\n userSpace.id\n }}\n </ion-select-option>\n } @empty {\n @let space = $space();\n <ion-select-option [value]=\"space?.id\">\n @if (!space || (!space.id && !space.type)) {\n Loading...\n } @else {\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n }\n </ion-select-option>\n }\n </ion-select>\n </ion-item>\n\n <!-- Events of the selected space. (No Calendar here \u2014 the calendar lives in\n the Calendarius extension, not eventus.) -->\n <ion-item\n tappable\n [routerLink]=\"spacePageUrl('events')\"\n routerDirection=\"root\"\n [disabled]=\"$disabled()\"\n (click)=\"closeMenu()\"\n >\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label>Events</ion-label>\n </ion-item>\n\n <sneat-auth-menu-item />\n</ion-list>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: ContactusServicesModule }, { kind: "ngmodule", type: SpaceServiceModule }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonSelect, selector: "ion-select", inputs: ["cancelText", "color", "compareWith", "disabled", "errorText", "expandedIcon", "fill", "helperText", "interface", "interfaceOptions", "justify", "label", "labelPlacement", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "shape", "toggleIcon", "value"] }, { kind: "component", type: IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AuthMenuItemComponent, selector: "sneat-auth-menu-item" }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
109
+ }
110
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EventusSpaceMenuComponent, decorators: [{
111
+ type: Component,
112
+ args: [{ selector: 'eventus-space-menu', imports: [
113
+ TitleCasePipe,
114
+ RouterLink,
115
+ ContactusServicesModule,
116
+ SpaceServiceModule,
117
+ IonList,
118
+ IonItem,
119
+ IonSelect,
120
+ IonSelectOption,
121
+ IonIcon,
122
+ IonLabel,
123
+ AuthMenuItemComponent,
124
+ ], providers: [
125
+ { provide: ClassName, useValue: 'EventusSpaceMenuComponent' },
126
+ SpaceComponentBaseParams,
127
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-list>\n <!-- Space selector: switch between the user's spaces (like sneat-app). -->\n <ion-item>\n <ion-select\n label=\"Space\"\n [value]=\"$space().id\"\n interface=\"popover\"\n (ionChange)=\"onSpaceSelected($event)\"\n style=\"font-weight: bold\"\n >\n @for (userSpace of $spaces(); track userSpace.id) {\n <ion-select-option [value]=\"userSpace.id\">\n {{\n userSpace.brief.title ||\n (userSpace.brief.type | titlecase) ||\n userSpace.id\n }}\n </ion-select-option>\n } @empty {\n @let space = $space();\n <ion-select-option [value]=\"space?.id\">\n @if (!space || (!space.id && !space.type)) {\n Loading...\n } @else {\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n }\n </ion-select-option>\n }\n </ion-select>\n </ion-item>\n\n <!-- Events of the selected space. (No Calendar here \u2014 the calendar lives in\n the Calendarius extension, not eventus.) -->\n <ion-item\n tappable\n [routerLink]=\"spacePageUrl('events')\"\n routerDirection=\"root\"\n [disabled]=\"$disabled()\"\n (click)=\"closeMenu()\"\n >\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label>Events</ion-label>\n </ion-item>\n\n <sneat-auth-menu-item />\n</ion-list>\n" }]
128
+ }], ctorParameters: () => [] });
129
+
130
+ // Public responder RSVP page routed by token (`/r/:token`). On load it resolves
131
+ // the token to show the event title/date/location and whether it's still open.
132
+ // The form captures status (yes/no/maybe), adults & children counts, dietary
133
+ // notes and a comment. When status is "no", the headcount inputs are hidden
134
+ // (the backend zeroes them regardless). Works fully anonymously — there is NO
135
+ // login or join/account-creation step.
136
+ //
137
+ // Attribution by link type (Task 5): when the resolved context is an OPEN link
138
+ // (`kind === 'open'`) the form REQUIRES a free-text "Your name" (no guest-list
139
+ // selection) and blocks submit until it is filled; a per-invitee link
140
+ // (`kind === 'invitee'`) hides that field (attribution comes from the
141
+ // invitation). After a successful submit the responder can re-open the form to
142
+ // change their response while the event is open — re-`submitRsvp` for a
143
+ // per-invitee link, or `updateRsvp` keyed by the saved rsvp id for an open link.
144
+ // (AC: rsvp-full-fields-captured, no-rsvp-zeroes-headcount, rsvp-does-not-join-space,
145
+ // attribution-by-link-type, rsvp-can-be-changed)
146
+ class RsvpPageComponent {
147
+ rsvpService = inject(RSVP_SERVICE);
148
+ /** Opaque link token from the route, bound via component-input-binding. */
149
+ token = input.required(...(ngDevMode ? [{ debugName: "token" }] : /* istanbul ignore next */ []));
150
+ context = signal(undefined, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
151
+ loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
152
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
153
+ error = signal(undefined, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
154
+ submitted = signal(undefined, ...(ngDevMode ? [{ debugName: "submitted" }] : /* istanbul ignore next */ []));
155
+ /** True while the responder is editing a previously-submitted response. */
156
+ editing = signal(false, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
157
+ // Form fields.
158
+ status = 'yes';
159
+ adults = 1;
160
+ children = 0;
161
+ dietary = '';
162
+ comment = '';
163
+ selfIdentifiedName = '';
164
+ selfIdentifiedFamilyName = '';
165
+ /** True for an open link — the responder must self-identify. */
166
+ isOpenLink() {
167
+ return this.context()?.kind === 'open';
168
+ }
169
+ /** Block submit until an open-link responder has typed their name. */
170
+ canSubmit() {
171
+ if (this.submitting()) {
172
+ return false;
173
+ }
174
+ if (this.isOpenLink() && !this.selfIdentifiedName.trim()) {
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ ngOnInit() {
180
+ this.rsvpService.resolveToken(this.token()).subscribe({
181
+ next: (ctx) => {
182
+ this.context.set(ctx);
183
+ this.loading.set(false);
184
+ },
185
+ error: (err) => {
186
+ this.loading.set(false);
187
+ this.error.set(extractErrorMessage(err, 'This RSVP link is invalid.'));
188
+ },
189
+ });
190
+ }
191
+ /** Bump a headcount, never below zero. */
192
+ adjust(field, delta) {
193
+ this[field] = Math.max(0, this[field] + delta);
194
+ }
195
+ /**
196
+ * Submit (or re-submit) the RSVP. On status "no" the backend zeroes the
197
+ * headcounts. For an open link an already-saved response is updated by its id
198
+ * (AC: rsvp-can-be-changed); otherwise a new submission is posted (a
199
+ * per-invitee re-submit is keyed by the invitation server-side).
200
+ */
201
+ submit() {
202
+ if (!this.canSubmit()) {
203
+ return;
204
+ }
205
+ this.submitting.set(true);
206
+ this.error.set(undefined);
207
+ const request = buildRsvpRequest({
208
+ status: this.status,
209
+ adults: this.adults,
210
+ children: this.children,
211
+ dietary: this.dietary,
212
+ comment: this.comment,
213
+ selfIdentifiedName: this.isOpenLink() ? this.selfIdentifiedName : '',
214
+ selfIdentifiedFamilyName: this.isOpenLink()
215
+ ? this.selfIdentifiedFamilyName
216
+ : '',
217
+ });
218
+ const existing = this.submitted();
219
+ const save$ = existing && this.isOpenLink()
220
+ ? this.rsvpService.updateRsvp(this.token(), existing.id, request)
221
+ : this.rsvpService.submitRsvp(this.token(), request);
222
+ save$.subscribe({
223
+ next: (rsvp) => {
224
+ this.submitted.set(rsvp);
225
+ this.editing.set(false);
226
+ this.submitting.set(false);
227
+ },
228
+ error: (err) => {
229
+ this.submitting.set(false);
230
+ this.error.set(extractErrorMessage(err, 'Failed to submit RSVP.'));
231
+ },
232
+ });
233
+ }
234
+ /** Re-open the form to change an already-submitted response (event still open). */
235
+ changeResponse() {
236
+ this.error.set(undefined);
237
+ this.editing.set(true);
238
+ }
239
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RsvpPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
240
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: RsvpPageComponent, isStandalone: true, selector: "eventus-rsvp-page", inputs: { token: { classPropertyName: "token", publicName: "token", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ion-header>\n <ion-toolbar>\n <ion-title>RSVP</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading event\"></ion-spinner>\n } @else if (error() && !context()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (context(); as ctx) {\n <ion-list>\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>{{ ctx.eventTitle }}</h2>\n <p>{{ ctx.eventStart | date: 'medium' }}</p>\n <p>{{ ctx.eventLocation }}</p>\n </ion-label>\n </ion-item>\n </ion-list>\n\n @if (!ctx.open) {\n <!-- Cancelled / closed event: show a message instead of the form. -->\n <ion-text color=\"danger\">\n <p>This event is no longer accepting RSVPs.</p>\n </ion-text>\n } @else if (submitted() && !editing()) {\n <!-- Confirmation after a successful submit. (AC: rsvp-can-be-changed) -->\n <ion-text color=\"success\">\n <h3>Thanks \u2014 your RSVP has been recorded.</h3>\n </ion-text>\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"changeResponse()\"\n >\n Change my response\n </ion-button>\n } @else {\n <ion-list>\n @if (isOpenLink()) {\n <!-- Open link: the responder MUST self-identify (free text, no guest\n list). (AC: attribution-by-link-type) -->\n <ion-item>\n <ion-input\n label=\"Your name\"\n labelPlacement=\"floating\"\n required=\"true\"\n [(ngModel)]=\"selfIdentifiedName\"\n ></ion-input>\n </ion-item>\n <ion-item>\n <ion-input\n label=\"Family name (optional)\"\n labelPlacement=\"floating\"\n [(ngModel)]=\"selfIdentifiedFamilyName\"\n ></ion-input>\n </ion-item>\n }\n <ion-item>\n <ion-label>Will you attend?</ion-label>\n </ion-item>\n <ion-segment [(ngModel)]=\"status\">\n <ion-segment-button value=\"yes\">\n <ion-label>Yes</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"no\">\n <ion-label>No</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"maybe\">\n <ion-label>Maybe</ion-label>\n </ion-segment-button>\n </ion-segment>\n\n @if (status !== 'no') {\n <!-- Headcounts only shown when attending. (AC: no-rsvp-zeroes-headcount) -->\n <ion-item>\n <ion-label>Adults</ion-label>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"Fewer adults\"\n (click)=\"adjust('adults', -1)\"\n >\n \u2212\n </ion-button>\n <ion-note slot=\"end\">{{ adults }}</ion-note>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"More adults\"\n (click)=\"adjust('adults', 1)\"\n >\n +\n </ion-button>\n </ion-item>\n <ion-item>\n <ion-label>Children</ion-label>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"Fewer children\"\n (click)=\"adjust('children', -1)\"\n >\n \u2212\n </ion-button>\n <ion-note slot=\"end\">{{ children }}</ion-note>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"More children\"\n (click)=\"adjust('children', 1)\"\n >\n +\n </ion-button>\n </ion-item>\n }\n\n <ion-item>\n <ion-input\n label=\"Dietary notes (optional)\"\n labelPlacement=\"floating\"\n placeholder=\"e.g. nut allergy\"\n [(ngModel)]=\"dietary\"\n ></ion-input>\n </ion-item>\n <ion-item>\n <ion-textarea\n label=\"Comment (optional)\"\n labelPlacement=\"floating\"\n [(ngModel)]=\"comment\"\n ></ion-textarea>\n </ion-item>\n </ion-list>\n\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-button\n expand=\"block\"\n class=\"ion-margin-top\"\n [disabled]=\"!canSubmit()\"\n (click)=\"submit()\"\n >\n {{ submitted() ? 'Update RSVP' : 'Send RSVP' }}\n </ion-button>\n }\n }\n</ion-content>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonSegment, selector: "ion-segment", inputs: ["color", "disabled", "mode", "scrollable", "selectOnFocus", "swipeGesture", "value"] }, { kind: "component", type: IonSegmentButton, selector: "ion-segment-button", inputs: ["contentId", "disabled", "layout", "mode", "type", "value"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
241
+ }
242
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RsvpPageComponent, decorators: [{
243
+ type: Component,
244
+ args: [{ selector: 'eventus-rsvp-page', imports: [
245
+ DatePipe,
246
+ FormsModule,
247
+ IonHeader,
248
+ IonToolbar,
249
+ IonTitle,
250
+ IonContent,
251
+ IonList,
252
+ IonItem,
253
+ IonLabel,
254
+ IonInput,
255
+ IonTextarea,
256
+ IonNote,
257
+ IonText,
258
+ IonButton,
259
+ IonSpinner,
260
+ IonSegment,
261
+ IonSegmentButton,
262
+ ], template: "<ion-header>\n <ion-toolbar>\n <ion-title>RSVP</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading event\"></ion-spinner>\n } @else if (error() && !context()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (context(); as ctx) {\n <ion-list>\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>{{ ctx.eventTitle }}</h2>\n <p>{{ ctx.eventStart | date: 'medium' }}</p>\n <p>{{ ctx.eventLocation }}</p>\n </ion-label>\n </ion-item>\n </ion-list>\n\n @if (!ctx.open) {\n <!-- Cancelled / closed event: show a message instead of the form. -->\n <ion-text color=\"danger\">\n <p>This event is no longer accepting RSVPs.</p>\n </ion-text>\n } @else if (submitted() && !editing()) {\n <!-- Confirmation after a successful submit. (AC: rsvp-can-be-changed) -->\n <ion-text color=\"success\">\n <h3>Thanks \u2014 your RSVP has been recorded.</h3>\n </ion-text>\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"changeResponse()\"\n >\n Change my response\n </ion-button>\n } @else {\n <ion-list>\n @if (isOpenLink()) {\n <!-- Open link: the responder MUST self-identify (free text, no guest\n list). (AC: attribution-by-link-type) -->\n <ion-item>\n <ion-input\n label=\"Your name\"\n labelPlacement=\"floating\"\n required=\"true\"\n [(ngModel)]=\"selfIdentifiedName\"\n ></ion-input>\n </ion-item>\n <ion-item>\n <ion-input\n label=\"Family name (optional)\"\n labelPlacement=\"floating\"\n [(ngModel)]=\"selfIdentifiedFamilyName\"\n ></ion-input>\n </ion-item>\n }\n <ion-item>\n <ion-label>Will you attend?</ion-label>\n </ion-item>\n <ion-segment [(ngModel)]=\"status\">\n <ion-segment-button value=\"yes\">\n <ion-label>Yes</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"no\">\n <ion-label>No</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"maybe\">\n <ion-label>Maybe</ion-label>\n </ion-segment-button>\n </ion-segment>\n\n @if (status !== 'no') {\n <!-- Headcounts only shown when attending. (AC: no-rsvp-zeroes-headcount) -->\n <ion-item>\n <ion-label>Adults</ion-label>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"Fewer adults\"\n (click)=\"adjust('adults', -1)\"\n >\n \u2212\n </ion-button>\n <ion-note slot=\"end\">{{ adults }}</ion-note>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"More adults\"\n (click)=\"adjust('adults', 1)\"\n >\n +\n </ion-button>\n </ion-item>\n <ion-item>\n <ion-label>Children</ion-label>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"Fewer children\"\n (click)=\"adjust('children', -1)\"\n >\n \u2212\n </ion-button>\n <ion-note slot=\"end\">{{ children }}</ion-note>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"More children\"\n (click)=\"adjust('children', 1)\"\n >\n +\n </ion-button>\n </ion-item>\n }\n\n <ion-item>\n <ion-input\n label=\"Dietary notes (optional)\"\n labelPlacement=\"floating\"\n placeholder=\"e.g. nut allergy\"\n [(ngModel)]=\"dietary\"\n ></ion-input>\n </ion-item>\n <ion-item>\n <ion-textarea\n label=\"Comment (optional)\"\n labelPlacement=\"floating\"\n [(ngModel)]=\"comment\"\n ></ion-textarea>\n </ion-item>\n </ion-list>\n\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-button\n expand=\"block\"\n class=\"ion-margin-top\"\n [disabled]=\"!canSubmit()\"\n (click)=\"submit()\"\n >\n {{ submitted() ? 'Update RSVP' : 'Send RSVP' }}\n </ion-button>\n }\n }\n</ion-content>\n" }]
263
+ }], propDecorators: { token: [{ type: i0.Input, args: [{ isSignal: true, alias: "token", required: true }] }] } });
264
+
265
+ /**
266
+ * Generated bundle index. Do not edit.
267
+ */
268
+
269
+ export { EventusSpaceMenuComponent, RsvpPageComponent, eventusRoutes };
270
+ //# sourceMappingURL=sneat-extension-eventus-shared.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sneat-extension-eventus-shared.mjs","sources":["../../../../../../libs/extensions/eventus/shared/src/lib/eventus-routing.ts","../../../../../../libs/extensions/eventus/shared/src/lib/space-menu/eventus-space-menu.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/space-menu/eventus-space-menu.component.html","../../../../../../libs/extensions/eventus/shared/src/lib/pages/rsvp/rsvp-page.component.ts","../../../../../../libs/extensions/eventus/shared/src/lib/pages/rsvp/rsvp-page.component.html","../../../../../../libs/extensions/eventus/shared/src/sneat-extension-eventus-shared.ts"],"sourcesContent":["import { Route } from '@angular/router';\n\n// Host-facing Eventus routes, mounted as children of the app's\n// space/:spaceType/:spaceID route. Paths are relative to that space context;\n// the spaceID (and eventID) route params bind to each page's input() via router\n// component-input-binding. (AC: host-creates-event, only-host-edits)\n//\n// The PUBLIC responder RSVP page (/r/:token) is intentionally NOT here — it is a\n// top-level, un-gated route (see the app's appRoutes), because an invitee\n// follows it anonymously, outside any space.\nexport const eventusRoutes: Route[] = [\n {\n // The space menu links to spacePageUrl('eventus'); land that on the events\n // list (the section's home). Sub-flows live under 'events/...'.\n path: 'eventus',\n pathMatch: 'full',\n redirectTo: 'events',\n },\n {\n path: 'events',\n loadComponent: () =>\n import('./pages/events/events-list-page.component').then(\n (m) => m.EventsListPageComponent,\n ),\n },\n {\n path: 'events/new',\n loadComponent: () =>\n import('./pages/create-event/create-event-page.component').then(\n (m) => m.CreateEventPageComponent,\n ),\n },\n {\n path: 'events/:eventID/edit',\n loadComponent: () =>\n import('./pages/edit-event/edit-event-page.component').then(\n (m) => m.EditEventPageComponent,\n ),\n },\n {\n path: 'events/:eventID/invitees',\n loadComponent: () =>\n import('./pages/invitees/invitees-page.component').then(\n (m) => m.InviteesPageComponent,\n ),\n },\n {\n path: 'events/:eventID/share-links',\n loadComponent: () =>\n import('./pages/share-links/share-links-page.component').then(\n (m) => m.ShareLinksPageComponent,\n ),\n },\n {\n path: 'events/:eventID/responses',\n loadComponent: () =>\n import('./pages/responses/responses-page.component').then(\n (m) => m.ResponsesPageComponent,\n ),\n },\n {\n path: 'events/:eventID/bring-along',\n loadComponent: () =>\n import('./pages/bring-along/bring-along-page.component').then(\n (m) => m.BringAlongPageComponent,\n ),\n },\n {\n path: 'events/:eventID',\n loadComponent: () =>\n import('./pages/event/event-page.component').then(\n (m) => m.EventPageComponent,\n ),\n },\n];\n","import { TitleCasePipe } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n signal,\n} from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport {\n IonIcon,\n IonItem,\n IonLabel,\n IonList,\n IonSelect,\n IonSelectOption,\n MenuController,\n} from '@ionic/angular/standalone';\nimport { ISneatUserState } from '@sneat/auth-core';\nimport { IUserSpaceBrief } from '@sneat/auth-models';\nimport { AuthMenuItemComponent } from '@sneat/auth-ui';\nimport { ContactusServicesModule } from '@sneat/extension-contactus-internal';\nimport { IIdAndBrief } from '@sneat/core';\nimport {\n SpaceBaseComponent,\n SpaceComponentBaseParams,\n} from '@sneat/space-components';\nimport { SpaceServiceModule } from '@sneat/space-services';\nimport { zipMapBriefsWithIDs } from '@sneat/space-models';\nimport { ClassName } from '@sneat/ui';\nimport { takeUntil } from 'rxjs/operators';\n\n// eventus-specific side menu rendered in the space \"menu\" outlet. Unlike the\n// generic @sneat SpaceMenuComponent (which hardcodes every sneat-app extension —\n// Assets, Budget, Contacts, …, none of which exist in eventus-app), this shows\n// only what eventus has: a space selector (to switch spaces, like sneat-app),\n// the space's Calendar (the main page) and its Events.\n@Component({\n selector: 'eventus-space-menu',\n templateUrl: './eventus-space-menu.component.html',\n imports: [\n TitleCasePipe,\n RouterLink,\n ContactusServicesModule,\n SpaceServiceModule,\n IonList,\n IonItem,\n IonSelect,\n IonSelectOption,\n IonIcon,\n IonLabel,\n AuthMenuItemComponent,\n ],\n providers: [\n { provide: ClassName, useValue: 'EventusSpaceMenuComponent' },\n SpaceComponentBaseParams,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class EventusSpaceMenuComponent extends SpaceBaseComponent {\n protected readonly $spaces = signal<\n readonly IIdAndBrief<IUserSpaceBrief>[] | undefined\n >(undefined);\n protected readonly $disabled = computed(() => !this.$spaceID());\n\n private readonly menuCtrl = inject(MenuController);\n\n constructor() {\n super();\n this.spaceParams.userService.userState\n .pipe(takeUntil(this.destroyed$))\n .subscribe({\n next: (userState: ISneatUserState) =>\n this.$spaces.set(\n userState?.record\n ? zipMapBriefsWithIDs(userState.record.spaces) || []\n : undefined,\n ),\n error: this.errorLogger.logErrorHandler('failed to get user state'),\n });\n }\n\n protected onSpaceSelected(event: Event): void {\n const spaceID = (event as CustomEvent).detail.value as string;\n if (spaceID === this.space?.id) {\n return;\n }\n const space = this.$spaces()?.find((t) => t.id === spaceID);\n if (space) {\n this.setSpaceRef(space);\n this.spaceNav\n .navigateToSpace(space)\n .catch(\n this.errorLogger.logErrorHandler(\n 'Failed to navigate to selected space',\n ),\n );\n }\n this.closeMenu();\n }\n\n protected closeMenu(): void {\n this.menuCtrl.close().catch(this.errorLogger.logError);\n }\n}\n","<ion-list>\n <!-- Space selector: switch between the user's spaces (like sneat-app). -->\n <ion-item>\n <ion-select\n label=\"Space\"\n [value]=\"$space().id\"\n interface=\"popover\"\n (ionChange)=\"onSpaceSelected($event)\"\n style=\"font-weight: bold\"\n >\n @for (userSpace of $spaces(); track userSpace.id) {\n <ion-select-option [value]=\"userSpace.id\">\n {{\n userSpace.brief.title ||\n (userSpace.brief.type | titlecase) ||\n userSpace.id\n }}\n </ion-select-option>\n } @empty {\n @let space = $space();\n <ion-select-option [value]=\"space?.id\">\n @if (!space || (!space.id && !space.type)) {\n Loading...\n } @else {\n {{ space.brief?.title || (space.type | titlecase) || space.id }}\n }\n </ion-select-option>\n }\n </ion-select>\n </ion-item>\n\n <!-- Events of the selected space. (No Calendar here — the calendar lives in\n the Calendarius extension, not eventus.) -->\n <ion-item\n tappable\n [routerLink]=\"spacePageUrl('events')\"\n routerDirection=\"root\"\n [disabled]=\"$disabled()\"\n (click)=\"closeMenu()\"\n >\n <ion-icon name=\"list-outline\" slot=\"start\" />\n <ion-label>Events</ion-label>\n </ion-item>\n\n <sneat-auth-menu-item />\n</ion-list>\n","import { DatePipe } from '@angular/common';\nimport { Component, OnInit, inject, input, signal } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport {\n IonButton,\n IonContent,\n IonHeader,\n IonInput,\n IonItem,\n IonLabel,\n IonList,\n IonNote,\n IonSegment,\n IonSegmentButton,\n IonSpinner,\n IonText,\n IonTextarea,\n IonTitle,\n IonToolbar,\n} from '@ionic/angular/standalone';\nimport {\n buildRsvpRequest,\n extractErrorMessage,\n IRsvp,\n IRsvpContext,\n RSVP_SERVICE,\n RsvpStatus,\n} from '@sneat/extension-eventus-contract';\n\n// Public responder RSVP page routed by token (`/r/:token`). On load it resolves\n// the token to show the event title/date/location and whether it's still open.\n// The form captures status (yes/no/maybe), adults & children counts, dietary\n// notes and a comment. When status is \"no\", the headcount inputs are hidden\n// (the backend zeroes them regardless). Works fully anonymously — there is NO\n// login or join/account-creation step.\n//\n// Attribution by link type (Task 5): when the resolved context is an OPEN link\n// (`kind === 'open'`) the form REQUIRES a free-text \"Your name\" (no guest-list\n// selection) and blocks submit until it is filled; a per-invitee link\n// (`kind === 'invitee'`) hides that field (attribution comes from the\n// invitation). After a successful submit the responder can re-open the form to\n// change their response while the event is open — re-`submitRsvp` for a\n// per-invitee link, or `updateRsvp` keyed by the saved rsvp id for an open link.\n// (AC: rsvp-full-fields-captured, no-rsvp-zeroes-headcount, rsvp-does-not-join-space,\n// attribution-by-link-type, rsvp-can-be-changed)\n@Component({\n selector: 'eventus-rsvp-page',\n templateUrl: './rsvp-page.component.html',\n imports: [\n DatePipe,\n FormsModule,\n IonHeader,\n IonToolbar,\n IonTitle,\n IonContent,\n IonList,\n IonItem,\n IonLabel,\n IonInput,\n IonTextarea,\n IonNote,\n IonText,\n IonButton,\n IonSpinner,\n IonSegment,\n IonSegmentButton,\n ],\n})\nexport class RsvpPageComponent implements OnInit {\n private readonly rsvpService = inject(RSVP_SERVICE);\n\n /** Opaque link token from the route, bound via component-input-binding. */\n readonly token = input.required<string>();\n\n protected readonly context = signal<IRsvpContext | undefined>(undefined);\n protected readonly loading = signal(true);\n protected readonly submitting = signal(false);\n protected readonly error = signal<string | undefined>(undefined);\n protected readonly submitted = signal<IRsvp | undefined>(undefined);\n\n /** True while the responder is editing a previously-submitted response. */\n protected readonly editing = signal(false);\n\n // Form fields.\n protected status: RsvpStatus = 'yes';\n protected adults = 1;\n protected children = 0;\n protected dietary = '';\n protected comment = '';\n protected selfIdentifiedName = '';\n protected selfIdentifiedFamilyName = '';\n\n /** True for an open link — the responder must self-identify. */\n protected isOpenLink(): boolean {\n return this.context()?.kind === 'open';\n }\n\n /** Block submit until an open-link responder has typed their name. */\n protected canSubmit(): boolean {\n if (this.submitting()) {\n return false;\n }\n if (this.isOpenLink() && !this.selfIdentifiedName.trim()) {\n return false;\n }\n return true;\n }\n\n ngOnInit(): void {\n this.rsvpService.resolveToken(this.token()).subscribe({\n next: (ctx) => {\n this.context.set(ctx);\n this.loading.set(false);\n },\n error: (err: unknown) => {\n this.loading.set(false);\n this.error.set(extractErrorMessage(err, 'This RSVP link is invalid.'));\n },\n });\n }\n\n /** Bump a headcount, never below zero. */\n protected adjust(field: 'adults' | 'children', delta: number): void {\n this[field] = Math.max(0, this[field] + delta);\n }\n\n /**\n * Submit (or re-submit) the RSVP. On status \"no\" the backend zeroes the\n * headcounts. For an open link an already-saved response is updated by its id\n * (AC: rsvp-can-be-changed); otherwise a new submission is posted (a\n * per-invitee re-submit is keyed by the invitation server-side).\n */\n protected submit(): void {\n if (!this.canSubmit()) {\n return;\n }\n this.submitting.set(true);\n this.error.set(undefined);\n\n const request = buildRsvpRequest({\n status: this.status,\n adults: this.adults,\n children: this.children,\n dietary: this.dietary,\n comment: this.comment,\n selfIdentifiedName: this.isOpenLink() ? this.selfIdentifiedName : '',\n selfIdentifiedFamilyName: this.isOpenLink()\n ? this.selfIdentifiedFamilyName\n : '',\n });\n\n const existing = this.submitted();\n const save$ =\n existing && this.isOpenLink()\n ? this.rsvpService.updateRsvp(this.token(), existing.id, request)\n : this.rsvpService.submitRsvp(this.token(), request);\n\n save$.subscribe({\n next: (rsvp) => {\n this.submitted.set(rsvp);\n this.editing.set(false);\n this.submitting.set(false);\n },\n error: (err: unknown) => {\n this.submitting.set(false);\n this.error.set(extractErrorMessage(err, 'Failed to submit RSVP.'));\n },\n });\n }\n\n /** Re-open the form to change an already-submitted response (event still open). */\n protected changeResponse(): void {\n this.error.set(undefined);\n this.editing.set(true);\n }\n}\n","<ion-header>\n <ion-toolbar>\n <ion-title>RSVP</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (loading()) {\n <ion-spinner aria-label=\"Loading event\"></ion-spinner>\n } @else if (error() && !context()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n } @else if (context(); as ctx) {\n <ion-list>\n <ion-item>\n <ion-label class=\"ion-text-wrap\">\n <h2>{{ ctx.eventTitle }}</h2>\n <p>{{ ctx.eventStart | date: 'medium' }}</p>\n <p>{{ ctx.eventLocation }}</p>\n </ion-label>\n </ion-item>\n </ion-list>\n\n @if (!ctx.open) {\n <!-- Cancelled / closed event: show a message instead of the form. -->\n <ion-text color=\"danger\">\n <p>This event is no longer accepting RSVPs.</p>\n </ion-text>\n } @else if (submitted() && !editing()) {\n <!-- Confirmation after a successful submit. (AC: rsvp-can-be-changed) -->\n <ion-text color=\"success\">\n <h3>Thanks — your RSVP has been recorded.</h3>\n </ion-text>\n <ion-button\n expand=\"block\"\n fill=\"outline\"\n class=\"ion-margin-top\"\n (click)=\"changeResponse()\"\n >\n Change my response\n </ion-button>\n } @else {\n <ion-list>\n @if (isOpenLink()) {\n <!-- Open link: the responder MUST self-identify (free text, no guest\n list). (AC: attribution-by-link-type) -->\n <ion-item>\n <ion-input\n label=\"Your name\"\n labelPlacement=\"floating\"\n required=\"true\"\n [(ngModel)]=\"selfIdentifiedName\"\n ></ion-input>\n </ion-item>\n <ion-item>\n <ion-input\n label=\"Family name (optional)\"\n labelPlacement=\"floating\"\n [(ngModel)]=\"selfIdentifiedFamilyName\"\n ></ion-input>\n </ion-item>\n }\n <ion-item>\n <ion-label>Will you attend?</ion-label>\n </ion-item>\n <ion-segment [(ngModel)]=\"status\">\n <ion-segment-button value=\"yes\">\n <ion-label>Yes</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"no\">\n <ion-label>No</ion-label>\n </ion-segment-button>\n <ion-segment-button value=\"maybe\">\n <ion-label>Maybe</ion-label>\n </ion-segment-button>\n </ion-segment>\n\n @if (status !== 'no') {\n <!-- Headcounts only shown when attending. (AC: no-rsvp-zeroes-headcount) -->\n <ion-item>\n <ion-label>Adults</ion-label>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"Fewer adults\"\n (click)=\"adjust('adults', -1)\"\n >\n −\n </ion-button>\n <ion-note slot=\"end\">{{ adults }}</ion-note>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"More adults\"\n (click)=\"adjust('adults', 1)\"\n >\n +\n </ion-button>\n </ion-item>\n <ion-item>\n <ion-label>Children</ion-label>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"Fewer children\"\n (click)=\"adjust('children', -1)\"\n >\n −\n </ion-button>\n <ion-note slot=\"end\">{{ children }}</ion-note>\n <ion-button\n slot=\"end\"\n fill=\"outline\"\n aria-label=\"More children\"\n (click)=\"adjust('children', 1)\"\n >\n +\n </ion-button>\n </ion-item>\n }\n\n <ion-item>\n <ion-input\n label=\"Dietary notes (optional)\"\n labelPlacement=\"floating\"\n placeholder=\"e.g. nut allergy\"\n [(ngModel)]=\"dietary\"\n ></ion-input>\n </ion-item>\n <ion-item>\n <ion-textarea\n label=\"Comment (optional)\"\n labelPlacement=\"floating\"\n [(ngModel)]=\"comment\"\n ></ion-textarea>\n </ion-item>\n </ion-list>\n\n @if (error()) {\n <ion-text color=\"danger\">{{ error() }}</ion-text>\n }\n\n <ion-button\n expand=\"block\"\n class=\"ion-margin-top\"\n [disabled]=\"!canSubmit()\"\n (click)=\"submit()\"\n >\n {{ submitted() ? 'Update RSVP' : 'Send RSVP' }}\n </ion-button>\n }\n }\n</ion-content>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@sneat/extension-eventus-shared",
3
+ "version": "0.0.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "peerDependencies": {
8
+ "@angular/common": "^21.0.0",
9
+ "@angular/core": "^21.0.0",
10
+ "@angular/router": "^21.0.0",
11
+ "@angular/forms": "^21.0.0",
12
+ "@ionic/angular": "^8.0.0",
13
+ "rxjs": "^7.0.0",
14
+ "qrcode": "^1.5.4",
15
+ "@sneat/auth-core": "^0.11.0",
16
+ "@sneat/auth-models": "^0.11.0",
17
+ "@sneat/auth-ui": "^0.11.0",
18
+ "@sneat/core": "^0.11.0",
19
+ "@sneat/extension-contactus-internal": "0.12.2",
20
+ "@sneat/space-components": "^0.11.0",
21
+ "@sneat/space-models": "^0.11.0",
22
+ "@sneat/space-services": "^0.11.0",
23
+ "@sneat/ui": "^0.11.0",
24
+ "@sneat/extension-eventus-contract": "^0.0.1",
25
+ "vitest": "^4.0.9"
26
+ },
27
+ "sideEffects": false,
28
+ "module": "fesm2022/sneat-extension-eventus-shared.mjs",
29
+ "typings": "types/sneat-extension-eventus-shared.d.ts",
30
+ "exports": {
31
+ "./package.json": {
32
+ "default": "./package.json"
33
+ },
34
+ ".": {
35
+ "types": "./types/sneat-extension-eventus-shared.d.ts",
36
+ "default": "./fesm2022/sneat-extension-eventus-shared.mjs"
37
+ }
38
+ },
39
+ "type": "module",
40
+ "dependencies": {
41
+ "tslib": "^2.3.0"
42
+ }
43
+ }