@swift-food-services/catering-widget 0.1.0-beta.2 → 0.1.0-beta.20

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arnav Vaish
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,13 +10,15 @@ npm install @swift-food-services/catering-widget
10
10
 
11
11
  ```tsx
12
12
  import { CateringWidget } from "@swift-food-services/catering-widget";
13
- import "@swift-food-services/catering-widget/dist/styles.css";
14
13
 
15
14
  export default function CateringPage() {
16
15
  return (
17
16
  <CateringWidget
18
17
  publishableKey="pk_live_..."
18
+ googleMapsApiKey="AIzaSy..."
19
19
  onOrderComplete={({ orderId }) => {
20
+ // Fires after the success-screen countdown (default 15s,
21
+ // configurable via `onOrderCompleteDelaySeconds`).
20
22
  window.location.href = `/thanks?order=${orderId}`;
21
23
  }}
22
24
  />
@@ -30,18 +32,20 @@ Defaults: Swift-hosted backend, `localStorage` persistence, neutral theme.
30
32
 
31
33
  - React 18+ and React-DOM 18+ as peer dependencies
32
34
  - A publishable key from Swift (see [Getting a publishable key](#getting-a-publishable-key))
33
- - At least one origin registered with Swift where the widget will load
34
35
 
35
36
  ## Props
36
37
 
37
38
  | Prop | Type | Required | Description |
38
39
  |---|---|---|---|
39
- | `publishableKey` | `string` | yes | Your `pk_live_...` or `pk_test_...` key from Swift. |
40
+ | `publishableKey` | `string` | yes | Your `pk_live_...` key from Swift. |
41
+ | `googleMapsApiKey` | `string` | yes | Google Maps JavaScript API key with the Places library enabled. Used by the address-autocomplete field. |
40
42
  | `theme` | `Theme` | no | Primary color, border radius, and font overrides. |
41
- | `initialData` | `InitialData` | no | Pre-populate event, address, and contact fields. Applied only on first mount. |
43
+ | `initialData` | `InitialData` | no | Pre-populate event window, address, and contact fields. Re-applied when its fingerprint changes between mounts; preserved on reload. See [Pre-filling known event details](#pre-filling-known-event-details). |
44
+ | `allowedCateringTimes` | `AllowedCateringTimes` | no | Partner-level catering availability window (`{ start: "HH:MM", end: "HH:MM" }`). Restricts the session delivery-time picker to slots within this range. |
42
45
  | `stickyTopOffset` | `number` | no | Pixels to offset the widget's internal sticky date/session nav from the top of the viewport. Set this to the height of any sticky/fixed navbar in your host layout so the widget's nav doesn't slide underneath it. Defaults to `0`. |
43
46
  | `onReady` | `() => void` | no | Fires when the widget has initialized. |
44
- | `onOrderComplete` | `(result: OrderCompleteResult) => void` | no | Fires after a successful order submission. |
47
+ | `onOrderComplete` | `(result: OrderCompleteResult) => void` | no | Fires after a successful order submission. See [`onOrderComplete` behaviour](#onordercompleteresult) for the timing. |
48
+ | `onOrderCompleteDelaySeconds` | `number` | no | Seconds to keep the success screen visible before firing `onOrderComplete`. Defaults to `15`. Ignored if `onOrderComplete` is not provided. |
45
49
  | `onError` | `(error: WidgetError) => void` | no | Fires on unrecoverable errors (bad key, network, submit failure). |
46
50
 
47
51
  ## Examples
@@ -51,14 +55,14 @@ Defaults: Swift-hosted backend, `localStorage` persistence, neutral theme.
51
55
  ```tsx
52
56
  "use client";
53
57
  import { CateringWidget } from "@swift-food-services/catering-widget";
54
- import "@swift-food-services/catering-widget/dist/styles.css";
55
58
  import { useRouter } from "next/navigation";
56
59
 
57
60
  export default function Page() {
58
61
  const router = useRouter();
59
62
  return (
60
63
  <CateringWidget
61
- publishableKey={process.env.NEXT_PUBLIC_SWIFT_KEY!}
64
+ publishableKey={process.env.NEXT_PUBLIC_SWIFT_PUBLISHABLE_KEY!}
65
+ googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}
62
66
  onOrderComplete={({ orderId, accessToken }) => {
63
67
  router.push(`/orders/${orderId}?token=${accessToken}`);
64
68
  }}
@@ -87,13 +91,17 @@ export default function Page() {
87
91
  publishableKey="pk_live_..."
88
92
  initialData={{
89
93
  eventName: "Alice's Birthday",
90
- eventDate: "2026-07-12",
91
- eventTime: "19:00",
94
+ eventStartDate: "2026-07-12",
95
+ eventStartTime: "18:00",
96
+ eventEndDate: "2026-07-12",
97
+ eventEndTime: "22:00",
92
98
  guestCount: 40,
93
99
  deliveryAddress: {
94
100
  line1: "1 Example Lane",
95
101
  city: "London",
96
102
  postcode: "E1 6AN",
103
+ lat: 51.5074,
104
+ lng: -0.1278,
97
105
  },
98
106
  contact: { name: "Alice", email: "alice@example.com" },
99
107
  }}
@@ -101,7 +109,32 @@ export default function Page() {
101
109
  />
102
110
  ```
103
111
 
104
- Every field in `initialData` is optional. Fields remain editable after pre-fill. `initialData` is only applied on first mount — if the user has a half-finished order in progress (persisted in `localStorage`), the persisted state wins.
112
+ Every field in `initialData` is optional. Fields remain editable after pre-fill.
113
+
114
+ **How `initialData` interacts with persisted in-progress orders:** the widget fingerprints the supplied `initialData` (event window, guest count, address, and contact fields) and stores the fingerprint alongside the order. On every mount it compares the incoming fingerprint to the stored one:
115
+
116
+ - **Match** (e.g. page reload) → the in-progress order is preserved; `initialData` is _not_ re-applied.
117
+ - **Differ** (e.g. customer navigated back to your booking page, edited details, then returned) → the stored order is cleared and `initialData` is applied fresh.
118
+ - **No fingerprint stored yet** (first visit) → `initialData` is applied and the fingerprint is recorded.
119
+
120
+ For this to behave predictably, make sure the values you pass for the same booking are stable across renders — avoid recomputing dynamic values like `new Date()` inline in render.
121
+
122
+ The event window is a start–end pair (`eventStartDate` + `eventStartTime` to `eventEndDate` + `eventEndTime`). When set, the session editor restricts session dates to that range and filters the delivery-time picker so it stays within the window on the boundary days.
123
+
124
+ **Address requires `lat` and `lng`.** Delivery pricing depends on coordinates, so if you pass a `deliveryAddress` without both `lat` and `lng`, the widget ignores the entire address (no fields are pre-filled) and the guest is asked to pick one via the address-autocomplete field instead. The other `initialData` fields (event window, guest count, contact) still apply.
125
+
126
+ ### Restricting catering hours (`allowedCateringTimes`)
127
+
128
+ Pass a partner-level catering availability window to restrict the session delivery-time picker to a subset of the day. Times are 24-hour `"HH:MM"` strings.
129
+
130
+ ```tsx
131
+ <CateringWidget
132
+ publishableKey="pk_live_..."
133
+ allowedCateringTimes={{ start: "09:00", end: "19:00" }}
134
+ />
135
+ ```
136
+
137
+ Each slot is a 30-minute window. A slot is shown only if it fits entirely within `[start, end]` — its start must be ≥ `start` and its end must be ≤ `end`. With `end: "19:00"`, for example, the latest visible slot is 6:30 PM – 7:00 PM. If `initialData` also defines an event window, the two filters compose: the partner window is the outer bound, and the event start/end further narrow the picker on boundary days.
105
138
 
106
139
  ### Offsetting below a host navbar
107
140
 
@@ -124,6 +157,7 @@ import {
124
157
  type CateringWidgetProps,
125
158
  type Theme,
126
159
  type InitialData,
160
+ type AllowedCateringTimes,
127
161
  type OrderCompleteResult,
128
162
  type OrderSummary,
129
163
  type MealSession,
@@ -138,12 +172,29 @@ See the generated `dist/index.d.ts` for the full shapes — your IDE picks them
138
172
 
139
173
  ### `onOrderComplete(result)`
140
174
 
141
- Fires when an order has been successfully submitted. `result` contains:
175
+ Fires after a successful order submission. `result` contains:
142
176
 
143
177
  - `orderId: string` — Swift's order ID.
144
178
  - `accessToken: string` — a token the customer can use to view the order on Swift's hosted view page.
145
179
  - `summary: OrderSummary` — structured breakdown of the submitted order.
146
180
 
181
+ **Timing.** When `onOrderComplete` is wired, the widget shows its built-in success screen with a "Continuing in N seconds…" countdown for `onOrderCompleteDelaySeconds` seconds (default `15`), then fires the callback. This gives the guest a moment to read the confirmation before the host navigates away. A "Continue now" button under the countdown lets the guest skip the wait — clicking it cancels the timer and fires `onOrderComplete` immediately.
182
+
183
+ If `onOrderComplete` is **not** wired, the success screen stays up indefinitely. The success screen shows the event, customer, and pricing summary, and any navigation is the host's responsibility (do it from `onOrderComplete`).
184
+
185
+ **Heads up:** the widget does not navigate on its own. If your `onOrderComplete` runs without triggering a navigation (e.g. you only fire analytics or close a modal), the guest will stay on the widget's order-confirmation page even after the callback fires. To send them elsewhere, do it from inside `onOrderComplete` — `router.push(...)`, `window.location.href = ...`, hide the widget from your layout, etc.
186
+
187
+ ```tsx
188
+ <CateringWidget
189
+ publishableKey="pk_live_..."
190
+ googleMapsApiKey="AIzaSy..."
191
+ onOrderCompleteDelaySeconds={5}
192
+ onOrderComplete={({ orderId, accessToken }) => {
193
+ router.push(`/orders/${orderId}?token=${accessToken}`);
194
+ }}
195
+ />
196
+ ```
197
+
147
198
  ### `onError(error)`
148
199
 
149
200
  Fires on errors the widget can't recover from. `error.code` is one of:
@@ -160,14 +211,14 @@ Fires on errors the widget can't recover from. `error.code` is one of:
160
211
  - Multi-session meal building
161
212
  - Contact details and delivery address
162
213
  - Promo codes and pricing preview
163
- - Google Maps address autocomplete
214
+ - Google Maps address autocomplete (requires the `googleMapsApiKey` you provide)
164
215
  - Order submission to Swift's API
165
216
  - State persistence in `localStorage` (namespaced)
166
217
 
167
218
  ## What you're responsible for
168
219
 
169
220
  - Getting a publishable key from Swift.
170
- - Registering your site's origin(s) with Swift.
221
+ - Providing a Google Maps JavaScript API key (with the Places library enabled) via the `googleMapsApiKey` prop. Restrict the key to your own domain(s) in the Google Cloud Console.
171
222
  - Rendering `<CateringWidget>` wherever you want the flow to live.
172
223
  - Navigating after `onOrderComplete`.
173
224
  - Page chrome around the widget.
@@ -175,36 +226,25 @@ Fires on errors the widget can't recover from. `error.code` is one of:
175
226
  ## What you're **not** responsible for
176
227
 
177
228
  - Payment. Swift sends a payment link by email after reviewing the order.
178
- - Loading Google Maps — the widget handles this internally.
229
+ - Loading the Google Maps script — the widget injects the script using the key you provide.
179
230
  - Making API calls to Swift.
180
231
  - Session or auth management.
181
232
 
182
- ## Build-time configuration
183
-
184
- Set `SWIFT_WIDGET_GOOGLE_MAPS_KEY` before building the library so the Maps script loads with the correct key. CI publishes with the production key; local development can use a restricted dev key.
185
-
186
233
  ## Getting a publishable key
187
234
 
188
- Contact Swift at [partners@swiftfood.uk](mailto:partners@swiftfood.uk) with:
235
+ Contact Swift at [swiftfooduk@gmail.com](mailto:swiftfooduk@gmail.com) with:
189
236
 
190
237
  1. Your company name.
191
- 2. The origin(s) where the widget will be embedded.
192
- 3. A technical contact for integration support.
238
+ 2. A technical contact for integration support.
193
239
 
194
- Swift provisions a partner record and returns a publishable key (format: `pk_live_...` or `pk_test_...`) out of band.
240
+ Swift provisions a partner record and returns a publishable key (format: `pk_live_...`) out of band.
195
241
 
196
- The key is public (it ships in the browser bundle). Swift's backend validates the key against the request's origin, so a stolen key cannot be used from a non-allowlisted domain.
242
+ The key is public it ships in the browser bundle.
197
243
 
198
244
  ## Troubleshooting
199
245
 
200
- **Widget doesn't render / screen is blank**
201
- Check that you've imported the stylesheet: `import "@swift-food-services/catering-widget/dist/styles.css";`.
202
-
203
246
  **Errors mentioning `invalid_publishable_key` or `session_failed`**
204
- Either the key is wrong, the key is inactive, or your current origin isn't in the allowlist.
205
-
206
- **Widget works on `localhost` but not in production**
207
- Your production origin isn't registered. Ask Swift to add it.
247
+ Either the key is wrong or the key is inactive. Contact Swift if you need a new one.
208
248
 
209
249
  **TypeScript errors importing types**
210
250
  Ensure your `tsconfig.json` has `"moduleResolution": "bundler"` or `"node16"`/`"nodenext"`.
@@ -219,7 +259,7 @@ Modern evergreen browsers (Chrome, Firefox, Safari, Edge) on desktop and mobile.
219
259
 
220
260
  ## Support
221
261
 
222
- - Integration questions: [partners@swiftfood.uk](mailto:partners@swiftfood.uk)
262
+ - Integration questions: [swiftfooduk@gmail.com](mailto:swiftfooduk@gmail.com)
223
263
  - Bug reports: include your publishable key prefix (first 8 chars), the browser, and a reproducible example.
224
264
 
225
265
  ## License