@code-collective/booking-widget 1.0.4 → 1.0.6

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.
@@ -30,31 +30,41 @@
30
30
  load();
31
31
  </script>
32
32
 
33
- {#if isLoading}
34
- <div class="loading-center" style="height:100vh">
35
- <div class="spinner"></div>
36
- </div>
37
- {:else if cart && cart.items.length > 0}
38
- <CheckoutModal
39
- {cart}
40
- {api}
41
- {wizardPages}
42
- {editPages}
43
- {autoSelectSingleTimeSlot}
44
- onClose={() => postMessage({ type: 'modal:close' })}
45
- onOrderConfirmed={() => {
46
- const total = cart!.items.reduce((s, i) => s + i.amount, 0);
47
- const currency = cart!.items[0]?.currencyCode ?? 'ZAR';
48
- postMessage({
49
- type: 'order:complete',
50
- cartToken: cart!.cartToken,
51
- value: total,
52
- currency,
53
- });
54
- }}
55
- />
56
- {:else}
57
- <div class="loading-center" style="height:100vh;color:var(--bw-color-text-secondary)">
58
- No items in cart.
59
- </div>
60
- {/if}
33
+ <div class="bw-widget">
34
+ {#if isLoading}
35
+ <div class="loading-center" style="height:100vh">
36
+ <div class="spinner"></div>
37
+ </div>
38
+ {:else if cart && cart.items.length > 0}
39
+ <CheckoutModal
40
+ {cart}
41
+ {api}
42
+ {wizardPages}
43
+ {editPages}
44
+ {autoSelectSingleTimeSlot}
45
+ onClose={() => postMessage({ type: 'modal:close' })}
46
+ onOrderConfirmed={() => {
47
+ const total = cart!.items.reduce((s, i) => s + i.amount, 0);
48
+ const currency = cart!.items[0]?.currencyCode ?? 'ZAR';
49
+ postMessage({
50
+ type: 'order:complete',
51
+ cartToken: cart!.cartToken,
52
+ value: total,
53
+ currency,
54
+ });
55
+ }}
56
+ />
57
+ {:else}
58
+ <div class="loading-center" style="height:100vh;color:var(--bw-color-text-secondary)">
59
+ No items in cart.
60
+ </div>
61
+ {/if}
62
+ </div>
63
+
64
+ <style>
65
+ /* display: contents - a plain box here would break CheckoutModal's own .modal{height:100%}, which needs
66
+ to resolve against this component's real parent, not an unsized wrapper inserted in between. */
67
+ .bw-widget {
68
+ display: contents;
69
+ }
70
+ </style>
@@ -126,15 +126,25 @@
126
126
  const updated = await api.getCart();
127
127
  cart = updated;
128
128
  currentView = 'cart';
129
+ notifyCartUpdated();
129
130
  }
130
131
 
131
132
  async function removeItem(item: CheckoutCartItemDetailDto) {
132
133
  await api.removeCartItem(item.id);
133
134
  const updated = await api.getCart();
134
135
  cart = updated;
136
+ notifyCartUpdated();
135
137
  if (cart.items.length === 0) close();
136
138
  }
137
139
 
140
+ // A standalone postMessage - not the itemCount/cartItemId-shaped cart:change TicketConfigurator posts on
141
+ // add (which bw-configurator.svelte re-dispatches as the public bw:cart-change event and auto-opens
142
+ // checkout for). This is purely internal: CartBar/CartOverviewButton listen for it to refetch their own
143
+ // total after an edit or remove, which otherwise only ever fetch once on mount.
144
+ function notifyCartUpdated(): void {
145
+ postMessage({ type: 'cart:updated' });
146
+ }
147
+
138
148
  async function onPayNow() {
139
149
  hasAttemptedSubmit = true;
140
150
  if (!contactForm?.isValid() || !privacyAccepted) return;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { BookingApi } from './api';
3
3
  import type { WizardPages } from './config';
4
+ import { DEFAULT_WIZARD_PAGES } from './config';
4
5
  import type { CartItem, CheckoutProductDto } from './client-types';
5
6
  import { CartManager } from './cart-manager';
6
7
  import { postMessage } from './messages';
@@ -12,11 +13,11 @@
12
13
  api: BookingApi;
13
14
  cartManager: CartManager;
14
15
  productId: string;
15
- wizardPages: WizardPages;
16
+ wizardPages?: WizardPages;
16
17
  autoSelectSingleTimeSlot?: boolean;
17
18
  onCancel?: () => void;
18
19
  }
19
- let { api, cartManager, productId, wizardPages, autoSelectSingleTimeSlot = false, onCancel }: Props = $props();
20
+ let { api, cartManager, productId, wizardPages = DEFAULT_WIZARD_PAGES, autoSelectSingleTimeSlot = false, onCancel }: Props = $props();
20
21
 
21
22
  let product = $state<CheckoutProductDto | null>(null);
22
23
  let isLoading = $state(true);
@@ -56,23 +57,37 @@
56
57
  totalFormatted: total,
57
58
  openCheckout: true,
58
59
  });
60
+ // Separate from cart:change above - that one is per-item-added detail forwarded to consumers as the
61
+ // public bw:cart-change event. This one just tells CartBar/CartOverviewButton to refetch their own
62
+ // total, same signal update/remove in CheckoutModal use - see this event's own doc comment there.
63
+ postMessage({ type: 'cart:updated' });
59
64
  } finally {
60
65
  isAddingToCart = false;
61
66
  }
62
67
  }
63
68
  </script>
64
69
 
65
- {#if isLoading || isAddingToCart}
66
- <div class="loading-center" style="height:100vh">
67
- <div class="spinner"></div>
68
- </div>
69
- {:else if product}
70
- <WizardPage
71
- {product}
72
- {api}
73
- {wizardPages}
74
- {autoSelectSingleTimeSlot}
75
- {onCancel}
76
- onComplete={onAddToCart}
77
- />
78
- {/if}
70
+ <div class="bw-widget">
71
+ {#if isLoading || isAddingToCart}
72
+ <div class="loading-center" style="height:100vh">
73
+ <div class="spinner"></div>
74
+ </div>
75
+ {:else if product}
76
+ <WizardPage
77
+ {product}
78
+ {api}
79
+ {wizardPages}
80
+ {autoSelectSingleTimeSlot}
81
+ {onCancel}
82
+ onComplete={onAddToCart}
83
+ />
84
+ {/if}
85
+ </div>
86
+
87
+ <style>
88
+ /* display: contents - a plain box here would break WizardPage's own .wizard{height:100%}, which needs
89
+ to resolve against this component's real parent, not an unsized wrapper inserted in between. */
90
+ .bw-widget {
91
+ display: contents;
92
+ }
93
+ </style>
package/src/lib/api.ts CHANGED
@@ -17,6 +17,7 @@ import { dateKey } from './utils';
17
17
 
18
18
  export interface BookingApi {
19
19
  sessionToken: string;
20
+ cartToken: string;
20
21
 
21
22
  // Session
22
23
  startSession(checkoutKey: string): Promise<CheckoutSessionDto>;
@@ -46,7 +47,7 @@ export interface BookingApi {
46
47
 
47
48
  export class ApiClient implements BookingApi {
48
49
  sessionToken = '';
49
- private cartToken = '';
50
+ cartToken = '';
50
51
  private client;
51
52
 
52
53
  constructor(baseUrl: string) {
package/src/lib/app.css CHANGED
@@ -26,13 +26,17 @@
26
26
  }
27
27
 
28
28
  /* ── Reset ─────────────────────────────────────────────── */
29
- * {
29
+ /* Scoped to .bw-widget (present on every mountable component's own root - TicketConfigurator, Checkout,
30
+ CartBar, CartOverviewButton) rather than the bare selectors this used to be, since Option 2/3 consumers
31
+ mount these directly into their own app alongside their own global styles/resets (Tailwind preflight,
32
+ etc.) - an unscoped * or body rule here would leak onto their entire page, not just the widget. */
33
+ .bw-widget, .bw-widget * {
30
34
  margin: 0;
31
35
  padding: 0;
32
36
  box-sizing: border-box;
33
37
  }
34
38
 
35
- body {
39
+ .bw-widget {
36
40
  font-family: var(--bw-font-family);
37
41
  font-size: 14px;
38
42
  line-height: 1.5;
@@ -41,8 +45,12 @@ body {
41
45
  -webkit-font-smoothing: antialiased;
42
46
  }
43
47
 
44
- button { cursor: pointer; font-family: inherit; }
45
- input, select, textarea { font-family: inherit; }
48
+ /* button/input/etc form controls don't inherit font-family by default even from an ancestor that sets it
49
+ (a standard browser quirk, not specific to this reset) - covers both a bw-widget div wrapping a button
50
+ (TicketConfigurator, Checkout) and bw-widget being set directly on the control itself (CartOverviewButton's
51
+ own <button>), since a plain descendant selector alone only matches the first case. */
52
+ .bw-widget button, button.bw-widget { cursor: pointer; font-family: inherit; }
53
+ .bw-widget :is(input, select, textarea), :is(input, select, textarea).bw-widget { font-family: inherit; }
46
54
 
47
55
  /* ── Spinner ───────────────────────────────────────────── */
48
56
  @keyframes spin { to { transform: rotate(360deg); } }
@@ -69,6 +69,7 @@ export class CartManager {
69
69
  idleExpiresAt: '',
70
70
  absoluteExpiresAt: stored.absoluteExpiresAt,
71
71
  };
72
+ this.api.cartToken = stored.cartToken;
72
73
  } catch {
73
74
  // corrupt or unavailable
74
75
  }
@@ -43,7 +43,7 @@ const ITEMS_STORAGE_KEY = 'morii-checkout-cart-items';
43
43
 
44
44
  export class FakeApiClient implements BookingApi {
45
45
  sessionToken = '';
46
- private cartToken = '';
46
+ cartToken = '';
47
47
  private cartItems: FakeCartItem[] = [];
48
48
 
49
49
  constructor() {
package/src/lib/index.ts CHANGED
@@ -19,8 +19,9 @@ export { default as CartOverviewButton } from './CartOverviewButton.svelte';
19
19
 
20
20
  // Re-export utilities
21
21
  export { ApiClient, FakeApiClient, SessionManager, CartManager };
22
+ export { resolveWizardPages, resolveEditPages, DEFAULT_WIZARD_PAGES } from './config';
22
23
  export type { BookingApi } from './api';
23
- export type { WizardPages, CartOverviewDisplay, WidgetMode, WizardWidgetType } from './config';
24
+ export type { WizardPages, WizardPageConfig, CartOverviewDisplay, WidgetMode, WizardWidgetType } from './config';
24
25
  export type { components, operations, paths } from './generated-types';
25
26
  export type * from './client-types';
26
27