@code-collective/booking-widget 1.0.2 → 1.0.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/README.md +318 -282
- package/dist/booking-widget.css +1 -1
- package/dist/booking-widget.js +1060 -830
- package/dist/booking-widget.min.css +2 -0
- package/dist/booking-widget.min.js +8 -0
- package/dist/booking-widget.umd.cjs +2 -2
- package/package.json +2 -1
- package/src/lib/Checkout.svelte +6 -1
- package/src/lib/CheckoutModal.svelte +16 -43
- package/src/lib/EditBookingView.svelte +5 -1
- package/src/lib/WizardPage.svelte +371 -96
- package/src/lib/config.ts +72 -14
- package/src/lib/elements/bw-checkout.svelte +9 -4
- package/src/lib/elements/bw-configurator.svelte +2 -2
- package/src/lib/elements/register.ts +12 -1
- package/src/lib/index.ts +4 -0
package/src/lib/config.ts
CHANGED
|
@@ -4,14 +4,78 @@ export type CartOverviewDisplay = 'bar' | 'button';
|
|
|
4
4
|
|
|
5
5
|
export type WizardWidgetType = 'option' | 'age-category' | 'date' | 'time' | 'pickup' | 'addon';
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export interface WizardPageConfig {
|
|
8
|
+
title: string;
|
|
9
|
+
widgets: WizardWidgetType[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type WizardPages = WizardPageConfig[];
|
|
8
13
|
|
|
9
14
|
export const DEFAULT_WIZARD_PAGES: WizardPages = [
|
|
10
|
-
['option', 'age-category'],
|
|
11
|
-
['date', 'time'],
|
|
12
|
-
['pickup'],
|
|
15
|
+
{ title: 'Option', widgets: ['option', 'age-category'] },
|
|
16
|
+
{ title: 'Schedule', widgets: ['date', 'time'] },
|
|
17
|
+
{ title: 'Pickup', widgets: ['pickup'] },
|
|
13
18
|
];
|
|
14
19
|
|
|
20
|
+
const WIZARD_PRESETS: Record<string, WizardPages> = {
|
|
21
|
+
'option-first': DEFAULT_WIZARD_PAGES,
|
|
22
|
+
'date-first': [
|
|
23
|
+
{ title: 'Age & capacity', widgets: ['age-category'] },
|
|
24
|
+
{ title: 'Schedule', widgets: ['date', 'time'] },
|
|
25
|
+
{ title: 'Option', widgets: ['option'] },
|
|
26
|
+
{ title: 'Pickup', widgets: ['pickup'] },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Accepts a preset name ("option-first", "date-first") or a JSON array of { title, widgets } objects.
|
|
31
|
+
export function resolveWizardPages(value: string | null | undefined): WizardPages {
|
|
32
|
+
if (!value) return DEFAULT_WIZARD_PAGES;
|
|
33
|
+
if (value in WIZARD_PRESETS) return WIZARD_PRESETS[value];
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(value);
|
|
36
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].widgets) return parsed;
|
|
37
|
+
} catch { /* use default */ }
|
|
38
|
+
return DEFAULT_WIZARD_PAGES;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Grouping for the checkout edit-item accordion - deliberately different from the step wizard's own page
|
|
42
|
+
// breakdown, since a step-by-step flow and a "review everything at once" accordion don't need the same
|
|
43
|
+
// shape. Age & capacity, date and time always live together in one section here, regardless of which
|
|
44
|
+
// preset is in play - only where that section falls (first vs. second) changes with option-first vs.
|
|
45
|
+
// date-first.
|
|
46
|
+
const EDIT_PRESETS: Record<string, WizardPages> = {
|
|
47
|
+
'option-first': [
|
|
48
|
+
{ title: 'Option', widgets: ['option'] },
|
|
49
|
+
{ title: 'Age, Date & Time', widgets: ['age-category', 'date', 'time'] },
|
|
50
|
+
{ title: 'Pickup', widgets: ['pickup'] },
|
|
51
|
+
],
|
|
52
|
+
'date-first': [
|
|
53
|
+
{ title: 'Age, Date & Time', widgets: ['age-category', 'date', 'time'] },
|
|
54
|
+
{ title: 'Option', widgets: ['option'] },
|
|
55
|
+
{ title: 'Pickup', widgets: ['pickup'] },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Resolves the grouping for the checkout edit-item accordion. If editValue is set, it's resolved the same
|
|
60
|
+
// way as wizardPages (a preset name or a custom JSON array). Otherwise, defaults to the grouped layout for
|
|
61
|
+
// whichever named wizard preset wizardValue resolves to (including the "no value given" case, which is
|
|
62
|
+
// option-first) - but if wizardValue is itself a custom, unnamed page layout, there's no sensible grouping
|
|
63
|
+
// to infer, so editPages just mirrors it directly.
|
|
64
|
+
export function resolveEditPages(wizardValue: string | null | undefined, editValue: string | null | undefined): WizardPages {
|
|
65
|
+
if (editValue) {
|
|
66
|
+
if (editValue in EDIT_PRESETS) return EDIT_PRESETS[editValue];
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(editValue);
|
|
69
|
+
if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].widgets) return parsed;
|
|
70
|
+
} catch { /* fall through to the wizardPages-derived default */ }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const wizardKey = wizardValue ? (wizardValue in WIZARD_PRESETS ? wizardValue : null) : 'option-first';
|
|
74
|
+
if (wizardKey) return EDIT_PRESETS[wizardKey];
|
|
75
|
+
|
|
76
|
+
return resolveWizardPages(wizardValue);
|
|
77
|
+
}
|
|
78
|
+
|
|
15
79
|
export interface AppConfig {
|
|
16
80
|
apiBaseUrl: string;
|
|
17
81
|
productId: string;
|
|
@@ -22,6 +86,7 @@ export interface AppConfig {
|
|
|
22
86
|
cartDisplay: CartOverviewDisplay;
|
|
23
87
|
orderId: string;
|
|
24
88
|
wizardPages: WizardPages;
|
|
89
|
+
editPages: WizardPages;
|
|
25
90
|
autoSelectSingleTimeSlot: boolean;
|
|
26
91
|
name: string;
|
|
27
92
|
cancelable: boolean;
|
|
@@ -42,7 +107,8 @@ export function readConfig(): AppConfig {
|
|
|
42
107
|
mode: parseMode(params.get('mode')),
|
|
43
108
|
cartDisplay: parseCartDisplay(params.get('cartDisplay')),
|
|
44
109
|
orderId: params.get('orderId') ?? '',
|
|
45
|
-
wizardPages:
|
|
110
|
+
wizardPages: resolveWizardPages(params.get('wizard')),
|
|
111
|
+
editPages: resolveEditPages(params.get('wizard'), params.get('edit')),
|
|
46
112
|
autoSelectSingleTimeSlot: params.get('autoSelectSingleTimeSlot') === 'true',
|
|
47
113
|
name: params.get('name') ?? '',
|
|
48
114
|
cancelable: params.get('cancelable') === 'true',
|
|
@@ -59,6 +125,7 @@ export function readConfig(): AppConfig {
|
|
|
59
125
|
cartDisplay: 'bar',
|
|
60
126
|
orderId: '',
|
|
61
127
|
wizardPages: DEFAULT_WIZARD_PAGES,
|
|
128
|
+
editPages: EDIT_PRESETS['option-first'],
|
|
62
129
|
autoSelectSingleTimeSlot: false,
|
|
63
130
|
name: '',
|
|
64
131
|
cancelable: false,
|
|
@@ -81,12 +148,3 @@ function deriveCheckoutKey(productId: string): string {
|
|
|
81
148
|
const first = productId[0]?.toLowerCase() ?? '';
|
|
82
149
|
return first >= 'a' && first <= 'j' ? 'morii' : 'csbs';
|
|
83
150
|
}
|
|
84
|
-
|
|
85
|
-
function parseWizardPages(value: string | null): WizardPages {
|
|
86
|
-
if (!value) return DEFAULT_WIZARD_PAGES;
|
|
87
|
-
try {
|
|
88
|
-
const parsed = JSON.parse(value);
|
|
89
|
-
if (Array.isArray(parsed) && parsed.every(Array.isArray)) return parsed;
|
|
90
|
-
} catch { /* use default */ }
|
|
91
|
-
return DEFAULT_WIZARD_PAGES;
|
|
92
|
-
}
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import Checkout from '../Checkout.svelte';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveWizardPages, resolveEditPages } from '../config';
|
|
6
6
|
import { getSharedServices } from './shared';
|
|
7
7
|
|
|
8
8
|
let {
|
|
9
9
|
'wizard-pages': wizardPagesJson = '',
|
|
10
|
+
'edit-pages': editPagesJson = '',
|
|
10
11
|
'is-modal': isModalAttr = undefined,
|
|
12
|
+
'auto-select-single-time-slot': autoSelectSingleTimeSlotAttr = undefined,
|
|
11
13
|
} = $props();
|
|
12
14
|
|
|
15
|
+
let autoSelectSingleTimeSlot = $derived(autoSelectSingleTimeSlotAttr !== undefined);
|
|
16
|
+
|
|
13
17
|
let isModal = $derived(isModalAttr !== undefined);
|
|
14
18
|
let showModal = $state(false);
|
|
15
19
|
|
|
@@ -18,7 +22,8 @@
|
|
|
18
22
|
let ready = $state(false);
|
|
19
23
|
readyPromise.then(() => { ready = true; });
|
|
20
24
|
|
|
21
|
-
let wizardPages = $derived(
|
|
25
|
+
let wizardPages = $derived(resolveWizardPages(wizardPagesJson));
|
|
26
|
+
let editPages = $derived(resolveEditPages(wizardPagesJson, editPagesJson));
|
|
22
27
|
|
|
23
28
|
function dispatch(name: string, detail: unknown) {
|
|
24
29
|
const host = (document.querySelector('bw-checkout') as HTMLElement) ?? document.body;
|
|
@@ -69,12 +74,12 @@
|
|
|
69
74
|
<div class="bw-modal-overlay" onclick={() => close()}>
|
|
70
75
|
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
71
76
|
<div class="bw-modal-inner" onclick={(e) => e.stopPropagation()}>
|
|
72
|
-
<Checkout {api} {wizardPages} />
|
|
77
|
+
<Checkout {api} {wizardPages} {editPages} {autoSelectSingleTimeSlot} />
|
|
73
78
|
</div>
|
|
74
79
|
</div>
|
|
75
80
|
{/if}
|
|
76
81
|
{:else if ready}
|
|
77
|
-
<Checkout {api} {wizardPages} />
|
|
82
|
+
<Checkout {api} {wizardPages} {editPages} {autoSelectSingleTimeSlot} />
|
|
78
83
|
{:else}
|
|
79
84
|
<div class="loading-center"><div class="spinner"></div></div>
|
|
80
85
|
{/if}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import TicketConfigurator from '../TicketConfigurator.svelte';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveWizardPages } from '../config';
|
|
6
6
|
import { postMessage } from '../messages';
|
|
7
7
|
import { getSharedServices } from './shared';
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
let ready = $state(false);
|
|
19
19
|
readyPromise.then(() => { ready = true; });
|
|
20
20
|
|
|
21
|
-
let wizardPages = $derived(
|
|
21
|
+
let wizardPages = $derived(resolveWizardPages(wizardPagesJson));
|
|
22
22
|
|
|
23
23
|
function dispatch(name: string, detail: unknown) {
|
|
24
24
|
const host = (document.querySelector('bw-configurator') as HTMLElement) ?? document.body;
|
|
@@ -26,12 +26,14 @@ interface BwOptions {
|
|
|
26
26
|
shouldBottomCloseOnModal: boolean;
|
|
27
27
|
autoSelectSingleTimeSlot: boolean;
|
|
28
28
|
wizardPages: string;
|
|
29
|
+
editPages: string;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const defaultOptions: BwOptions = {
|
|
32
33
|
shouldBottomCloseOnModal: true,
|
|
33
34
|
autoSelectSingleTimeSlot: false,
|
|
34
35
|
wizardPages: '',
|
|
36
|
+
editPages: '',
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
const options: BwOptions = {
|
|
@@ -61,7 +63,16 @@ function autoWire() {
|
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
// edit-pages only affects the checkout edit-item accordion, so it's only meaningful on bw-checkout.
|
|
67
|
+
if (options.editPages) {
|
|
68
|
+
checkouts.forEach((el) => {
|
|
69
|
+
if (!el.hasAttribute('edit-pages')) {
|
|
70
|
+
el.setAttribute('edit-pages', options.editPages);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[...configurators, ...checkouts].forEach((el) => {
|
|
65
76
|
if (options.autoSelectSingleTimeSlot && !el.hasAttribute('auto-select-single-time-slot')) {
|
|
66
77
|
el.setAttribute('auto-select-single-time-slot', '');
|
|
67
78
|
}
|
package/src/lib/index.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface ConfiguratorConfig extends BaseConfig {
|
|
|
44
44
|
|
|
45
45
|
export interface CheckoutConfig extends BaseConfig {
|
|
46
46
|
wizardPages?: WizardPages;
|
|
47
|
+
editPages?: WizardPages;
|
|
48
|
+
autoSelectSingleTimeSlot?: boolean;
|
|
47
49
|
onClose?: () => void;
|
|
48
50
|
onOrderConfirmed?: (detail: { cartToken: string; value: number; currency: string }) => void;
|
|
49
51
|
}
|
|
@@ -126,6 +128,8 @@ export async function mountCheckout(target: HTMLElement, config: CheckoutConfig)
|
|
|
126
128
|
props: {
|
|
127
129
|
api,
|
|
128
130
|
wizardPages: config.wizardPages,
|
|
131
|
+
editPages: config.editPages,
|
|
132
|
+
autoSelectSingleTimeSlot: config.autoSelectSingleTimeSlot ?? false,
|
|
129
133
|
},
|
|
130
134
|
});
|
|
131
135
|
|