@ticketboothapp/booking 1.2.98 → 1.2.100

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ticketboothapp/booking",
3
- "version": "1.2.98",
3
+ "version": "1.2.100",
4
4
  "private": false,
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -14,6 +14,8 @@
14
14
  "./booking-flow.css": "./src/components/booking/booking-flow.css",
15
15
  "./runtime": "./src/runtime/index.ts",
16
16
  "./contexts/booking-app-context": "./src/contexts/BookingAppContext.tsx",
17
+ "./contexts/company-context": "./src/contexts/CompanyContext.tsx",
18
+ "./contexts/availabilities-cache-context": "./src/contexts/AvailabilitiesCacheContext.tsx",
17
19
  "./providers/booking-dialog-provider": "./src/providers/booking-dialog-provider.tsx",
18
20
  "./hooks/useBookingSourceMetadataFromLocation": "./src/hooks/useBookingSourceMetadataFromLocation.ts",
19
21
  "./hooks/useIsBookingLaunchLive": "./src/hooks/useIsBookingLaunchLive.ts",
@@ -3439,12 +3439,9 @@ export function AdminChangeBookingFlow({
3439
3439
  // Percentage/fixed promos: tax on discounted amount (promo before GST per CRA guidance).
3440
3440
  // Vouchers and gift cards: tax on full subtotal (voucher discount includes tax on free portion; gift card is payment).
3441
3441
  // Change booking: get-promo-discount uses forBookingChange + prior promo from receipt so expired codes stay locked (BE).
3442
- const lockedPromoFallbackAmount =
3443
- lockedPromoCode
3444
- ? Math.max(0, originalReceiptPromoAdjustment.amount)
3445
- : 0;
3442
+ const receiptPromoFallbackAmount = Math.max(0, originalReceiptPromoAdjustment.amount);
3446
3443
  const effectivePromoDiscountAmount =
3447
- promoDiscountAmount > 0 ? promoDiscountAmount : lockedPromoFallbackAmount;
3444
+ promoDiscountAmount > 0 ? promoDiscountAmount : receiptPromoFallbackAmount;
3448
3445
  const effectivePromoLineType =
3449
3446
  isGiftCard || originalReceiptPromoAdjustment.type === 'GIFT_CARD'
3450
3447
  ? 'GIFT_CARD'
@@ -5444,7 +5441,7 @@ export function AdminChangeBookingFlow({
5444
5441
  ...(effectivePromoDiscountAmount > 0
5445
5442
  ? [
5446
5443
  {
5447
- label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
5444
+ label: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || (t('booking.discount') || 'Discount')),
5448
5445
  amount: -effectivePromoDiscountAmount,
5449
5446
  type: effectivePromoLineType,
5450
5447
  },
@@ -5522,7 +5519,7 @@ export function AdminChangeBookingFlow({
5522
5519
  isTaxIncludedInPrice,
5523
5520
  taxRate: pricingConfig?.taxRate ?? 0,
5524
5521
  promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
5525
- discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
5522
+ discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || undefined),
5526
5523
  });
5527
5524
  setShowAdminPaymentChoice(true);
5528
5525
  setLoading(false);
@@ -5573,7 +5570,7 @@ export function AdminChangeBookingFlow({
5573
5570
  isTaxIncludedInPrice,
5574
5571
  taxRate: pricingConfig?.taxRate ?? 0,
5575
5572
  promoDiscountAmount: effectivePromoDiscountAmount > 0 ? effectivePromoDiscountAmount : 0,
5576
- discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceipt?.promoLabel || undefined),
5573
+ discountLabel: appliedPromoCode ? `Promo: ${appliedPromoCode}` : (originalReceiptPromoAdjustment.label || originalReceipt?.promoLabel || undefined),
5577
5574
  changeTotals:
5578
5575
  isCustomerSelfServeChange && originalReceipt
5579
5576
  ? {
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef, useState } from 'react';
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { useBookingSourceMetadataFromLocation } from '../../hooks/useBookingSourceMetadataFromLocation';
5
5
  import { useBookingDialog } from '../../providers/booking-dialog-provider';
6
6
  import { useBookingHost } from '../../runtime';
@@ -49,15 +49,28 @@ function BookFlowScreen({
49
49
  const apiProductId = config?.productId ?? productId;
50
50
 
51
51
  // Build minimal product from config for immediate availability fetch (no /products wait)
52
- const minimalProduct =
53
- config && catalog.buildMinimalProductFromConfig
54
- ? (catalog.buildMinimalProductFromConfig(config, env.COMPANY_ID) as Product)
55
- : null;
52
+ const staticProduct = useMemo(
53
+ () =>
54
+ catalog.getStaticProductByIdOrSlug
55
+ ? (catalog.getStaticProductByIdOrSlug(productId) as Product | null)
56
+ : null,
57
+ [catalog, productId]
58
+ );
59
+
60
+ const minimalProduct = useMemo(
61
+ () =>
62
+ config && catalog.buildMinimalProductFromConfig
63
+ ? (catalog.buildMinimalProductFromConfig(config, env.COMPANY_ID) as Product)
64
+ : null,
65
+ [catalog, config, env.COMPANY_ID]
66
+ );
56
67
 
57
68
  useEffect(() => {
58
69
  if (isPartialLaunch) return; // No API needed for partial launch
59
- let cancelled = false;
60
70
  setError(null);
71
+ setProduct(null);
72
+ if (staticProduct || minimalProduct) return;
73
+ let cancelled = false;
61
74
  getProduct(apiProductId, env.COMPANY_ID)
62
75
  .then((p) => {
63
76
  if (!cancelled && p) setProduct(p);
@@ -67,13 +80,14 @@ function BookFlowScreen({
67
80
  if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to load product');
68
81
  });
69
82
  return () => { cancelled = true; };
70
- }, [apiProductId, config, isPartialLaunch]);
83
+ }, [apiProductId, config, env.COMPANY_ID, isPartialLaunch, minimalProduct, staticProduct]);
71
84
 
72
85
  useEffect(() => {
73
- if (product) {
74
- onProductLoaded?.(product.name);
86
+ const loadedProduct = product?.productId === apiProductId ? product : staticProduct ?? minimalProduct;
87
+ if (loadedProduct) {
88
+ onProductLoaded?.(loadedProduct.name);
75
89
  }
76
- }, [product?.name, onProductLoaded]);
90
+ }, [apiProductId, minimalProduct, onProductLoaded, product, staticProduct]);
77
91
 
78
92
  // Partial launch: show collage + description immediately (no API)
79
93
  if (isPartialLaunch) {
@@ -84,8 +98,10 @@ function BookFlowScreen({
84
98
  );
85
99
  }
86
100
 
87
- // No config: must wait for API (product not in products-config)
88
- if (!config) {
101
+ const displayProduct = product?.productId === apiProductId ? product : staticProduct ?? minimalProduct;
102
+
103
+ // No local/static product: must wait for API (product not in products-config)
104
+ if (!displayProduct) {
89
105
  if (error) {
90
106
  return (
91
107
  <div className={`${styles.screen} booking-flow-preflight`}>
@@ -107,10 +123,7 @@ function BookFlowScreen({
107
123
  );
108
124
  }
109
125
 
110
- // Use full product when loaded, otherwise minimal from config (availabilities fetch starts immediately)
111
- const displayProduct = product ?? minimalProduct!;
112
-
113
- if (error && !product) {
126
+ if (error && !displayProduct) {
114
127
  return (
115
128
  <div className={`${styles.screen} booking-flow-preflight`}>
116
129
  <div className="flex flex-col items-center justify-center py-16 gap-4">
@@ -107,12 +107,20 @@ export default function ChangeBookingDialog({
107
107
  : null,
108
108
  [config, catalog, env.COMPANY_ID]
109
109
  );
110
+ const staticProduct = useMemo(
111
+ () =>
112
+ catalog.getStaticProductByIdOrSlug
113
+ ? (catalog.getStaticProductByIdOrSlug(bookingProductId) as Product | null)
114
+ : null,
115
+ [bookingProductId, catalog]
116
+ );
110
117
 
111
118
  useEffect(() => {
112
119
  if (!isOpen) return;
113
120
  let cancelled = false;
114
121
  setError(null);
115
- if (minimalProduct) setProduct(minimalProduct);
122
+ setProduct(staticProduct ?? minimalProduct);
123
+ if (staticProduct || minimalProduct) return;
116
124
  getProduct(apiProductId, env.COMPANY_ID)
117
125
  .then((p) => {
118
126
  if (!cancelled && p) setProduct(p);
@@ -123,7 +131,7 @@ export default function ChangeBookingDialog({
123
131
  return () => {
124
132
  cancelled = true;
125
133
  };
126
- }, [isOpen, apiProductId, minimalProduct]);
134
+ }, [isOpen, apiProductId, minimalProduct, staticProduct]);
127
135
 
128
136
  useEffect(() => {
129
137
  if (isOpen) {
@@ -334,9 +334,12 @@ export function CheckoutModal({
334
334
  : []),
335
335
  ...feeLineItems.map((fee) => {
336
336
  const isMoraineLakeRoadAccessFee = fee.name.toLowerCase().includes('moraine') && (fee.name.toLowerCase().includes('access') || fee.name.toLowerCase().includes('road') || fee.name.toLowerCase().includes('license'));
337
+ const showQuantityLabel = fee.showQuantityLabel !== false;
337
338
  return {
338
339
  kind: 'line' as const,
339
- label: `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`,
340
+ label: showQuantityLabel
341
+ ? `${fee.name} (${totalQuantity} ${totalQuantity === 1 ? 'person' : 'people'})`
342
+ : fee.name,
340
343
  amount: fee.totalAmount,
341
344
  type: 'fee',
342
345
  tooltip: isMoraineLakeRoadAccessFee