@swift-food-services/catering-widget 0.2.0-beta.5 → 0.2.0-beta.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.
- package/LICENSE +20 -20
- package/README.md +267 -267
- package/dist/index.cjs +254 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +254 -86
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +2 -2
- package/package.json +87 -87
package/LICENSE
CHANGED
|
@@ -1,21 +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
|
|
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
21
|
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,267 +1,267 @@
|
|
|
1
|
-
# @swift-food-services/catering-widget
|
|
2
|
-
|
|
3
|
-
The Swift Food catering flow, embeddable in any React app. Restaurant browsing, multi-session order building, contact details, and submission — one component, one CSS file, one publishable key.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npm install @swift-food-services/catering-widget
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Quick start
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
import { CateringWidget } from "@swift-food-services/catering-widget";
|
|
13
|
-
|
|
14
|
-
export default function CateringPage() {
|
|
15
|
-
return (
|
|
16
|
-
<CateringWidget
|
|
17
|
-
publishableKey="pk_live_..."
|
|
18
|
-
googleMapsApiKey="AIzaSy..."
|
|
19
|
-
onOrderComplete={({ orderId }) => {
|
|
20
|
-
// Fires after the success-screen countdown (default 15s,
|
|
21
|
-
// configurable via `onOrderCompleteDelaySeconds`).
|
|
22
|
-
window.location.href = `/thanks?order=${orderId}`;
|
|
23
|
-
}}
|
|
24
|
-
/>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Defaults: Swift-hosted backend, `localStorage` persistence, neutral theme.
|
|
30
|
-
|
|
31
|
-
## Requirements
|
|
32
|
-
|
|
33
|
-
- React 18+ and React-DOM 18+ as peer dependencies
|
|
34
|
-
- A publishable key from Swift (see [Getting a publishable key](#getting-a-publishable-key))
|
|
35
|
-
|
|
36
|
-
## Props
|
|
37
|
-
|
|
38
|
-
| Prop | Type | Required | Description |
|
|
39
|
-
|---|---|---|---|
|
|
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. |
|
|
42
|
-
| `theme` | `Theme` | no | Primary color, border radius, and font overrides. |
|
|
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. |
|
|
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`. |
|
|
46
|
-
| `onReady` | `() => void` | no | Fires when the widget has initialized. |
|
|
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. |
|
|
49
|
-
| `onError` | `(error: WidgetError) => void` | no | Fires on unrecoverable errors (bad key, network, submit failure). |
|
|
50
|
-
|
|
51
|
-
## Examples
|
|
52
|
-
|
|
53
|
-
### Next.js with router-based navigation
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
"use client";
|
|
57
|
-
import { CateringWidget } from "@swift-food-services/catering-widget";
|
|
58
|
-
import { useRouter } from "next/navigation";
|
|
59
|
-
|
|
60
|
-
export default function Page() {
|
|
61
|
-
const router = useRouter();
|
|
62
|
-
return (
|
|
63
|
-
<CateringWidget
|
|
64
|
-
publishableKey={process.env.NEXT_PUBLIC_SWIFT_PUBLISHABLE_KEY!}
|
|
65
|
-
googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}
|
|
66
|
-
onOrderComplete={({ orderId, accessToken }) => {
|
|
67
|
-
router.push(`/orders/${orderId}?token=${accessToken}`);
|
|
68
|
-
}}
|
|
69
|
-
/>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### Theming
|
|
75
|
-
|
|
76
|
-
```tsx
|
|
77
|
-
<CateringWidget
|
|
78
|
-
publishableKey="pk_live_..."
|
|
79
|
-
theme={{
|
|
80
|
-
primary: "#0a7ea4",
|
|
81
|
-
radius: "12px",
|
|
82
|
-
font: "\"IBM Plex Mono\", monospace",
|
|
83
|
-
}}
|
|
84
|
-
/>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Pre-filling known event details
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
<CateringWidget
|
|
91
|
-
publishableKey="pk_live_..."
|
|
92
|
-
initialData={{
|
|
93
|
-
eventName: "Alice's Birthday",
|
|
94
|
-
eventStartDate: "2026-07-12",
|
|
95
|
-
eventStartTime: "18:00",
|
|
96
|
-
eventEndDate: "2026-07-12",
|
|
97
|
-
eventEndTime: "22:00",
|
|
98
|
-
guestCount: 40,
|
|
99
|
-
deliveryAddress: {
|
|
100
|
-
line1: "1 Example Lane",
|
|
101
|
-
city: "London",
|
|
102
|
-
postcode: "E1 6AN",
|
|
103
|
-
lat: 51.5074,
|
|
104
|
-
lng: -0.1278,
|
|
105
|
-
},
|
|
106
|
-
contact: { name: "Alice", email: "alice@example.com" },
|
|
107
|
-
}}
|
|
108
|
-
onOrderComplete={/* ... */}
|
|
109
|
-
/>
|
|
110
|
-
```
|
|
111
|
-
|
|
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.
|
|
138
|
-
|
|
139
|
-
### Offsetting below a host navbar
|
|
140
|
-
|
|
141
|
-
If your page has its own sticky/fixed navbar, pass its height as `stickyTopOffset` so the widget's internal date/session nav pins just below it:
|
|
142
|
-
|
|
143
|
-
```tsx
|
|
144
|
-
<CateringWidget
|
|
145
|
-
publishableKey="pk_live_..."
|
|
146
|
-
stickyTopOffset={64}
|
|
147
|
-
/>
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Pass a live value if your navbar resizes (measure it with a ref + `ResizeObserver` and pass the current height). The prop is read on every render, so updates take effect immediately.
|
|
151
|
-
|
|
152
|
-
## Types
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
import {
|
|
156
|
-
CateringWidget,
|
|
157
|
-
type CateringWidgetProps,
|
|
158
|
-
type Theme,
|
|
159
|
-
type InitialData,
|
|
160
|
-
type AllowedCateringTimes,
|
|
161
|
-
type OrderCompleteResult,
|
|
162
|
-
type OrderSummary,
|
|
163
|
-
type MealSession,
|
|
164
|
-
type MealSessionItem,
|
|
165
|
-
type WidgetError,
|
|
166
|
-
} from "@swift-food-services/catering-widget";
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
See the generated `dist/index.d.ts` for the full shapes — your IDE picks them up automatically.
|
|
170
|
-
|
|
171
|
-
## Callbacks
|
|
172
|
-
|
|
173
|
-
### `onOrderComplete(result)`
|
|
174
|
-
|
|
175
|
-
Fires after a successful order submission. `result` contains:
|
|
176
|
-
|
|
177
|
-
- `orderId: string` — Swift's order ID.
|
|
178
|
-
- `accessToken: string` — a token the customer can use to view the order on Swift's hosted view page.
|
|
179
|
-
- `summary: OrderSummary` — structured breakdown of the submitted order.
|
|
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
|
-
|
|
198
|
-
### `onError(error)`
|
|
199
|
-
|
|
200
|
-
Fires on errors the widget can't recover from. `error.code` is one of:
|
|
201
|
-
|
|
202
|
-
- `invalid_publishable_key` — the key was rejected by Swift's backend.
|
|
203
|
-
- `session_failed` — the widget session handshake failed.
|
|
204
|
-
- `network_error` — a backend request failed or returned a non-2xx.
|
|
205
|
-
- `submit_failed` — the order submission itself failed.
|
|
206
|
-
- `unknown` — anything else; see `error.cause` for details.
|
|
207
|
-
|
|
208
|
-
## What the widget handles for you
|
|
209
|
-
|
|
210
|
-
- Restaurant browsing and menu exploration
|
|
211
|
-
- Multi-session meal building
|
|
212
|
-
- Contact details and delivery address
|
|
213
|
-
- Promo codes and pricing preview
|
|
214
|
-
- Google Maps address autocomplete (requires the `googleMapsApiKey` you provide)
|
|
215
|
-
- Order submission to Swift's API
|
|
216
|
-
- State persistence in `localStorage` (namespaced)
|
|
217
|
-
|
|
218
|
-
## What you're responsible for
|
|
219
|
-
|
|
220
|
-
- Getting a publishable key from 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.
|
|
222
|
-
- Rendering `<CateringWidget>` wherever you want the flow to live.
|
|
223
|
-
- Navigating after `onOrderComplete`.
|
|
224
|
-
- Page chrome around the widget.
|
|
225
|
-
|
|
226
|
-
## What you're **not** responsible for
|
|
227
|
-
|
|
228
|
-
- Payment. Swift sends a payment link by email after reviewing the order.
|
|
229
|
-
- Loading the Google Maps script — the widget injects the script using the key you provide.
|
|
230
|
-
- Making API calls to Swift.
|
|
231
|
-
- Session or auth management.
|
|
232
|
-
|
|
233
|
-
## Getting a publishable key
|
|
234
|
-
|
|
235
|
-
Contact Swift at [swiftfooduk@gmail.com](mailto:swiftfooduk@gmail.com) with:
|
|
236
|
-
|
|
237
|
-
1. Your company name.
|
|
238
|
-
2. A technical contact for integration support.
|
|
239
|
-
|
|
240
|
-
Swift provisions a partner record and returns a publishable key (format: `pk_live_...`) out of band.
|
|
241
|
-
|
|
242
|
-
The key is public — it ships in the browser bundle.
|
|
243
|
-
|
|
244
|
-
## Troubleshooting
|
|
245
|
-
|
|
246
|
-
**Errors mentioning `invalid_publishable_key` or `session_failed`**
|
|
247
|
-
Either the key is wrong or the key is inactive. Contact Swift if you need a new one.
|
|
248
|
-
|
|
249
|
-
**TypeScript errors importing types**
|
|
250
|
-
Ensure your `tsconfig.json` has `"moduleResolution": "bundler"` or `"node16"`/`"nodenext"`.
|
|
251
|
-
|
|
252
|
-
## Browser support
|
|
253
|
-
|
|
254
|
-
Modern evergreen browsers (Chrome, Firefox, Safari, Edge) on desktop and mobile.
|
|
255
|
-
|
|
256
|
-
## Versioning
|
|
257
|
-
|
|
258
|
-
`@swift-food-services/catering-widget` follows [semver](https://semver.org/). The public API is everything exported from the package entry — `CateringWidget`, its props, and the re-exported types.
|
|
259
|
-
|
|
260
|
-
## Support
|
|
261
|
-
|
|
262
|
-
- Integration questions: [swiftfooduk@gmail.com](mailto:swiftfooduk@gmail.com)
|
|
263
|
-
- Bug reports: include your publishable key prefix (first 8 chars), the browser, and a reproducible example.
|
|
264
|
-
|
|
265
|
-
## License
|
|
266
|
-
|
|
267
|
-
Proprietary. Distributed for use by authorized Swift Food partners only.
|
|
1
|
+
# @swift-food-services/catering-widget
|
|
2
|
+
|
|
3
|
+
The Swift Food catering flow, embeddable in any React app. Restaurant browsing, multi-session order building, contact details, and submission — one component, one CSS file, one publishable key.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @swift-food-services/catering-widget
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { CateringWidget } from "@swift-food-services/catering-widget";
|
|
13
|
+
|
|
14
|
+
export default function CateringPage() {
|
|
15
|
+
return (
|
|
16
|
+
<CateringWidget
|
|
17
|
+
publishableKey="pk_live_..."
|
|
18
|
+
googleMapsApiKey="AIzaSy..."
|
|
19
|
+
onOrderComplete={({ orderId }) => {
|
|
20
|
+
// Fires after the success-screen countdown (default 15s,
|
|
21
|
+
// configurable via `onOrderCompleteDelaySeconds`).
|
|
22
|
+
window.location.href = `/thanks?order=${orderId}`;
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Defaults: Swift-hosted backend, `localStorage` persistence, neutral theme.
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- React 18+ and React-DOM 18+ as peer dependencies
|
|
34
|
+
- A publishable key from Swift (see [Getting a publishable key](#getting-a-publishable-key))
|
|
35
|
+
|
|
36
|
+
## Props
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Required | Description |
|
|
39
|
+
|---|---|---|---|
|
|
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. |
|
|
42
|
+
| `theme` | `Theme` | no | Primary color, border radius, and font overrides. |
|
|
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. |
|
|
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`. |
|
|
46
|
+
| `onReady` | `() => void` | no | Fires when the widget has initialized. |
|
|
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. |
|
|
49
|
+
| `onError` | `(error: WidgetError) => void` | no | Fires on unrecoverable errors (bad key, network, submit failure). |
|
|
50
|
+
|
|
51
|
+
## Examples
|
|
52
|
+
|
|
53
|
+
### Next.js with router-based navigation
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
"use client";
|
|
57
|
+
import { CateringWidget } from "@swift-food-services/catering-widget";
|
|
58
|
+
import { useRouter } from "next/navigation";
|
|
59
|
+
|
|
60
|
+
export default function Page() {
|
|
61
|
+
const router = useRouter();
|
|
62
|
+
return (
|
|
63
|
+
<CateringWidget
|
|
64
|
+
publishableKey={process.env.NEXT_PUBLIC_SWIFT_PUBLISHABLE_KEY!}
|
|
65
|
+
googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}
|
|
66
|
+
onOrderComplete={({ orderId, accessToken }) => {
|
|
67
|
+
router.push(`/orders/${orderId}?token=${accessToken}`);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Theming
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<CateringWidget
|
|
78
|
+
publishableKey="pk_live_..."
|
|
79
|
+
theme={{
|
|
80
|
+
primary: "#0a7ea4",
|
|
81
|
+
radius: "12px",
|
|
82
|
+
font: "\"IBM Plex Mono\", monospace",
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Pre-filling known event details
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<CateringWidget
|
|
91
|
+
publishableKey="pk_live_..."
|
|
92
|
+
initialData={{
|
|
93
|
+
eventName: "Alice's Birthday",
|
|
94
|
+
eventStartDate: "2026-07-12",
|
|
95
|
+
eventStartTime: "18:00",
|
|
96
|
+
eventEndDate: "2026-07-12",
|
|
97
|
+
eventEndTime: "22:00",
|
|
98
|
+
guestCount: 40,
|
|
99
|
+
deliveryAddress: {
|
|
100
|
+
line1: "1 Example Lane",
|
|
101
|
+
city: "London",
|
|
102
|
+
postcode: "E1 6AN",
|
|
103
|
+
lat: 51.5074,
|
|
104
|
+
lng: -0.1278,
|
|
105
|
+
},
|
|
106
|
+
contact: { name: "Alice", email: "alice@example.com" },
|
|
107
|
+
}}
|
|
108
|
+
onOrderComplete={/* ... */}
|
|
109
|
+
/>
|
|
110
|
+
```
|
|
111
|
+
|
|
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.
|
|
138
|
+
|
|
139
|
+
### Offsetting below a host navbar
|
|
140
|
+
|
|
141
|
+
If your page has its own sticky/fixed navbar, pass its height as `stickyTopOffset` so the widget's internal date/session nav pins just below it:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<CateringWidget
|
|
145
|
+
publishableKey="pk_live_..."
|
|
146
|
+
stickyTopOffset={64}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Pass a live value if your navbar resizes (measure it with a ref + `ResizeObserver` and pass the current height). The prop is read on every render, so updates take effect immediately.
|
|
151
|
+
|
|
152
|
+
## Types
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {
|
|
156
|
+
CateringWidget,
|
|
157
|
+
type CateringWidgetProps,
|
|
158
|
+
type Theme,
|
|
159
|
+
type InitialData,
|
|
160
|
+
type AllowedCateringTimes,
|
|
161
|
+
type OrderCompleteResult,
|
|
162
|
+
type OrderSummary,
|
|
163
|
+
type MealSession,
|
|
164
|
+
type MealSessionItem,
|
|
165
|
+
type WidgetError,
|
|
166
|
+
} from "@swift-food-services/catering-widget";
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
See the generated `dist/index.d.ts` for the full shapes — your IDE picks them up automatically.
|
|
170
|
+
|
|
171
|
+
## Callbacks
|
|
172
|
+
|
|
173
|
+
### `onOrderComplete(result)`
|
|
174
|
+
|
|
175
|
+
Fires after a successful order submission. `result` contains:
|
|
176
|
+
|
|
177
|
+
- `orderId: string` — Swift's order ID.
|
|
178
|
+
- `accessToken: string` — a token the customer can use to view the order on Swift's hosted view page.
|
|
179
|
+
- `summary: OrderSummary` — structured breakdown of the submitted order.
|
|
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
|
+
|
|
198
|
+
### `onError(error)`
|
|
199
|
+
|
|
200
|
+
Fires on errors the widget can't recover from. `error.code` is one of:
|
|
201
|
+
|
|
202
|
+
- `invalid_publishable_key` — the key was rejected by Swift's backend.
|
|
203
|
+
- `session_failed` — the widget session handshake failed.
|
|
204
|
+
- `network_error` — a backend request failed or returned a non-2xx.
|
|
205
|
+
- `submit_failed` — the order submission itself failed.
|
|
206
|
+
- `unknown` — anything else; see `error.cause` for details.
|
|
207
|
+
|
|
208
|
+
## What the widget handles for you
|
|
209
|
+
|
|
210
|
+
- Restaurant browsing and menu exploration
|
|
211
|
+
- Multi-session meal building
|
|
212
|
+
- Contact details and delivery address
|
|
213
|
+
- Promo codes and pricing preview
|
|
214
|
+
- Google Maps address autocomplete (requires the `googleMapsApiKey` you provide)
|
|
215
|
+
- Order submission to Swift's API
|
|
216
|
+
- State persistence in `localStorage` (namespaced)
|
|
217
|
+
|
|
218
|
+
## What you're responsible for
|
|
219
|
+
|
|
220
|
+
- Getting a publishable key from 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.
|
|
222
|
+
- Rendering `<CateringWidget>` wherever you want the flow to live.
|
|
223
|
+
- Navigating after `onOrderComplete`.
|
|
224
|
+
- Page chrome around the widget.
|
|
225
|
+
|
|
226
|
+
## What you're **not** responsible for
|
|
227
|
+
|
|
228
|
+
- Payment. Swift sends a payment link by email after reviewing the order.
|
|
229
|
+
- Loading the Google Maps script — the widget injects the script using the key you provide.
|
|
230
|
+
- Making API calls to Swift.
|
|
231
|
+
- Session or auth management.
|
|
232
|
+
|
|
233
|
+
## Getting a publishable key
|
|
234
|
+
|
|
235
|
+
Contact Swift at [swiftfooduk@gmail.com](mailto:swiftfooduk@gmail.com) with:
|
|
236
|
+
|
|
237
|
+
1. Your company name.
|
|
238
|
+
2. A technical contact for integration support.
|
|
239
|
+
|
|
240
|
+
Swift provisions a partner record and returns a publishable key (format: `pk_live_...`) out of band.
|
|
241
|
+
|
|
242
|
+
The key is public — it ships in the browser bundle.
|
|
243
|
+
|
|
244
|
+
## Troubleshooting
|
|
245
|
+
|
|
246
|
+
**Errors mentioning `invalid_publishable_key` or `session_failed`**
|
|
247
|
+
Either the key is wrong or the key is inactive. Contact Swift if you need a new one.
|
|
248
|
+
|
|
249
|
+
**TypeScript errors importing types**
|
|
250
|
+
Ensure your `tsconfig.json` has `"moduleResolution": "bundler"` or `"node16"`/`"nodenext"`.
|
|
251
|
+
|
|
252
|
+
## Browser support
|
|
253
|
+
|
|
254
|
+
Modern evergreen browsers (Chrome, Firefox, Safari, Edge) on desktop and mobile.
|
|
255
|
+
|
|
256
|
+
## Versioning
|
|
257
|
+
|
|
258
|
+
`@swift-food-services/catering-widget` follows [semver](https://semver.org/). The public API is everything exported from the package entry — `CateringWidget`, its props, and the re-exported types.
|
|
259
|
+
|
|
260
|
+
## Support
|
|
261
|
+
|
|
262
|
+
- Integration questions: [swiftfooduk@gmail.com](mailto:swiftfooduk@gmail.com)
|
|
263
|
+
- Bug reports: include your publishable key prefix (first 8 chars), the browser, and a reproducible example.
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
Proprietary. Distributed for use by authorized Swift Food partners only.
|