@bookslot/react 0.1.0 → 0.2.0
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 +8 -6
- package/dist/index.cjs +8 -6
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +8 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @bookslot/react
|
|
2
2
|
|
|
3
|
-
The [BookSlot](https://bookslot
|
|
3
|
+
The [BookSlot](https://bookslot.taulantsela.com) booking widget as a React component. Works in Next.js (App Router and Pages Router), Vite, Remix and any other React app.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @bookslot/react
|
|
@@ -16,7 +16,7 @@ export function Booking() {
|
|
|
16
16
|
|
|
17
17
|
That's a working booking form. It loads the widget from BookSlot, resizes itself to fit, and shows the shop's services and free times. Find your shop slug in the BookSlot dashboard.
|
|
18
18
|
|
|
19
|
-
The package is already marked `"use client"`, so in the Next.js App Router a server component can render it with `shop`, `theme`, `service` and `customer`. Callbacks such as `onBooked` are functions, which React can't send from the server, so pass those from a client component.
|
|
19
|
+
The package is already marked `"use client"`, so in the Next.js App Router a server component can render it with `shop`, `theme`, `service`, `staff` and `customer`. Callbacks such as `onBooked` are functions, which React can't send from the server, so pass those from a client component.
|
|
20
20
|
|
|
21
21
|
## Match your site, prefill, react to bookings (Pro)
|
|
22
22
|
|
|
@@ -25,7 +25,8 @@ The package is already marked `"use client"`, so in the Next.js App Router a ser
|
|
|
25
25
|
shop="your-shop-slug"
|
|
26
26
|
theme={{ primary: "#2dd4bf", scheme: "dark", radius: 6, font: "system" }}
|
|
27
27
|
service="svc_…" // preselect; can change any time
|
|
28
|
-
|
|
28
|
+
staff="stf_…" // preselect a staff member
|
|
29
|
+
customer={{ name: user.name, email: user.email, fields: { any_allergies: "None" } }}
|
|
29
30
|
onBooked={(booking) => router.push(`/thanks?id=${booking.appointmentId}`)}
|
|
30
31
|
/>
|
|
31
32
|
```
|
|
@@ -40,14 +41,15 @@ On the Free plan the widget still works, but it ignores these options and shows
|
|
|
40
41
|
| `bookslotUrl` | `string` | Where BookSlot runs. Defaults to the hosted service. |
|
|
41
42
|
| `theme` | `{ primary?, background?, text?, radius?, font?, scheme? }` | Pro. Hex colours, radius 0–32, font `sans` / `system` / `serif` / `mono`, scheme `light` / `dark` / `auto`. Changing it reloads the widget. |
|
|
42
43
|
| `service` | `string` | Pro. Service id to preselect, listed in **Dashboard → Widget**. |
|
|
43
|
-
| `
|
|
44
|
+
| `staff` | `string` | Pro. Staff member id to preselect, listed in **Dashboard → Widget**; `""` switches back to anyone. |
|
|
45
|
+
| `customer` | `{ name?, email?, phone?, notes?, fields? }` | Pro. Fills in the form; `fields` answers custom questions by key. Sent with `postMessage`, never in the URL. |
|
|
44
46
|
| `initialHeight` | `number` | Height before the widget reports its size. Default 720. |
|
|
45
47
|
| `maxWidth` | `number \| string` | Default 480px. |
|
|
46
48
|
| `title` | `string` | Accessible title of the iframe. |
|
|
47
49
|
| `className`, `style` | | Applied to the wrapper `div`. |
|
|
48
50
|
| `onReady` | `({ shop }) => void` | The widget loaded. |
|
|
49
51
|
| `onResize` | `({ height }) => void` | The widget changed height. |
|
|
50
|
-
| `onBooked` | `(booking) => void` | Pro. `{ appointmentId, serviceId, serviceName, startsAt, endsAt, when }`. |
|
|
52
|
+
| `onBooked` | `(booking) => void` | Pro. `{ appointmentId, serviceId, serviceName, staffId, staffName, startsAt, endsAt, when }`. |
|
|
51
53
|
| `onError` | `({ message }) => void` | Pro for booking errors; also called if the widget script can't load. |
|
|
52
54
|
|
|
53
55
|
## Without React
|
|
@@ -55,7 +57,7 @@ On the Free plan the widget still works, but it ignores these options and shows
|
|
|
55
57
|
Load the script and call it directly:
|
|
56
58
|
|
|
57
59
|
```html
|
|
58
|
-
<script src="https://bookslot
|
|
60
|
+
<script src="https://bookslot.taulantsela.com/widget.js" data-shop="your-shop-slug" async></script>
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
`loadBookSlot(url?)` is exported too, if you want the underlying `BookSlot.mount` API in React.
|
package/dist/index.cjs
CHANGED
|
@@ -28,7 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
29
|
var import_react = require("react");
|
|
30
30
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
-
var DEFAULT_BOOKSLOT_URL = "https://bookslot
|
|
31
|
+
var DEFAULT_BOOKSLOT_URL = "https://bookslot.taulantsela.com";
|
|
32
32
|
function apiFor(origin, { justLoaded = false } = {}) {
|
|
33
33
|
var _a;
|
|
34
34
|
const globals = window;
|
|
@@ -74,6 +74,7 @@ function BookSlotWidget({
|
|
|
74
74
|
bookslotUrl = DEFAULT_BOOKSLOT_URL,
|
|
75
75
|
theme,
|
|
76
76
|
service,
|
|
77
|
+
staff,
|
|
77
78
|
customer,
|
|
78
79
|
initialHeight,
|
|
79
80
|
maxWidth,
|
|
@@ -87,9 +88,9 @@ function BookSlotWidget({
|
|
|
87
88
|
}) {
|
|
88
89
|
const container = (0, import_react.useRef)(null);
|
|
89
90
|
const widget = (0, import_react.useRef)(null);
|
|
90
|
-
const latest = (0, import_react.useRef)({ service, customer, onReady, onResize, onBooked, onError });
|
|
91
|
+
const latest = (0, import_react.useRef)({ service, staff, customer, onReady, onResize, onBooked, onError });
|
|
91
92
|
(0, import_react.useEffect)(() => {
|
|
92
|
-
latest.current = { service, customer, onReady, onResize, onBooked, onError };
|
|
93
|
+
latest.current = { service, staff, customer, onReady, onResize, onBooked, onError };
|
|
93
94
|
});
|
|
94
95
|
const themeKey = JSON.stringify(theme != null ? theme : {});
|
|
95
96
|
(0, import_react.useEffect)(() => {
|
|
@@ -98,11 +99,12 @@ function BookSlotWidget({
|
|
|
98
99
|
loadBookSlot(bookslotUrl).then(
|
|
99
100
|
(api) => {
|
|
100
101
|
if (cancelled || !container.current) return;
|
|
101
|
-
const { service: service2, customer: customer2 } = latest.current;
|
|
102
|
+
const { service: service2, staff: staff2, customer: customer2 } = latest.current;
|
|
102
103
|
instance = api.mount(container.current, {
|
|
103
104
|
shop,
|
|
104
105
|
theme: JSON.parse(themeKey),
|
|
105
106
|
service: service2,
|
|
107
|
+
staff: staff2 || void 0,
|
|
106
108
|
prefill: customer2,
|
|
107
109
|
height: initialHeight,
|
|
108
110
|
maxWidth,
|
|
@@ -136,8 +138,8 @@ function BookSlotWidget({
|
|
|
136
138
|
}, [shop, bookslotUrl, themeKey, initialHeight, maxWidth, title]);
|
|
137
139
|
(0, import_react.useEffect)(() => {
|
|
138
140
|
var _a;
|
|
139
|
-
if (service) (_a = widget.current) == null ? void 0 : _a.prefill({}, service);
|
|
140
|
-
}, [service]);
|
|
141
|
+
if (service || staff !== void 0) (_a = widget.current) == null ? void 0 : _a.prefill({}, service, staff);
|
|
142
|
+
}, [service, staff]);
|
|
141
143
|
const customerKey = JSON.stringify(customer != null ? customer : {});
|
|
142
144
|
(0, import_react.useEffect)(() => {
|
|
143
145
|
var _a;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties } from 'react';
|
|
3
3
|
|
|
4
|
-
declare const DEFAULT_BOOKSLOT_URL = "https://bookslot
|
|
4
|
+
declare const DEFAULT_BOOKSLOT_URL = "https://bookslot.taulantsela.com";
|
|
5
5
|
type BookSlotTheme = {
|
|
6
6
|
/** Hex colour for buttons and highlights. */
|
|
7
7
|
primary?: string;
|
|
@@ -19,11 +19,16 @@ type BookSlotCustomer = {
|
|
|
19
19
|
email?: string;
|
|
20
20
|
phone?: string;
|
|
21
21
|
notes?: string;
|
|
22
|
+
/** Answers to the shop's custom questions, keyed by question key (Dashboard → Booking form). */
|
|
23
|
+
fields?: Record<string, string | number | boolean>;
|
|
22
24
|
};
|
|
23
25
|
type BookSlotBooking = {
|
|
24
26
|
appointmentId: string;
|
|
25
27
|
serviceId: string;
|
|
26
28
|
serviceName: string;
|
|
29
|
+
/** Who it's with; null when the shop has no staff. */
|
|
30
|
+
staffId: string | null;
|
|
31
|
+
staffName: string | null;
|
|
27
32
|
/** ISO timestamp. */
|
|
28
33
|
startsAt: string;
|
|
29
34
|
/** ISO timestamp. */
|
|
@@ -40,6 +45,8 @@ type BookSlotWidgetProps = {
|
|
|
40
45
|
theme?: BookSlotTheme;
|
|
41
46
|
/** Pro: service id to preselect (listed in Dashboard → Widget). Can change at any time. */
|
|
42
47
|
service?: string;
|
|
48
|
+
/** Pro: staff member id to preselect (listed in Dashboard → Widget), or "" for anyone. Can change at any time. */
|
|
49
|
+
staff?: string;
|
|
43
50
|
/** Pro: details of the signed-in user. Sent by postMessage, never in the URL. */
|
|
44
51
|
customer?: BookSlotCustomer;
|
|
45
52
|
/** Height in pixels before the widget reports its own size. Default 720. */
|
|
@@ -66,7 +73,7 @@ type BookSlotWidgetProps = {
|
|
|
66
73
|
type EventName = "ready" | "resize" | "booked" | "error";
|
|
67
74
|
type BookSlotInstance = {
|
|
68
75
|
on(type: EventName, fn: (detail: unknown) => void): BookSlotInstance;
|
|
69
|
-
prefill(customer: BookSlotCustomer, service?: string): BookSlotInstance;
|
|
76
|
+
prefill(customer: BookSlotCustomer, service?: string, staff?: string): BookSlotInstance;
|
|
70
77
|
destroy(): void;
|
|
71
78
|
};
|
|
72
79
|
type BookSlotApi = {
|
|
@@ -77,6 +84,6 @@ type BookSlotApi = {
|
|
|
77
84
|
};
|
|
78
85
|
/** Loads widget.js once per BookSlot address and resolves with that address's API. */
|
|
79
86
|
declare function loadBookSlot(bookslotUrl?: string): Promise<BookSlotApi>;
|
|
80
|
-
declare function BookSlotWidget({ shop, bookslotUrl, theme, service, customer, initialHeight, maxWidth, title, className, style, onReady, onResize, onBooked, onError, }: BookSlotWidgetProps): react.JSX.Element;
|
|
87
|
+
declare function BookSlotWidget({ shop, bookslotUrl, theme, service, staff, customer, initialHeight, maxWidth, title, className, style, onReady, onResize, onBooked, onError, }: BookSlotWidgetProps): react.JSX.Element;
|
|
81
88
|
|
|
82
89
|
export { type BookSlotBooking, type BookSlotCustomer, type BookSlotTheme, BookSlotWidget, type BookSlotWidgetProps, DEFAULT_BOOKSLOT_URL, loadBookSlot };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { CSSProperties } from 'react';
|
|
3
3
|
|
|
4
|
-
declare const DEFAULT_BOOKSLOT_URL = "https://bookslot
|
|
4
|
+
declare const DEFAULT_BOOKSLOT_URL = "https://bookslot.taulantsela.com";
|
|
5
5
|
type BookSlotTheme = {
|
|
6
6
|
/** Hex colour for buttons and highlights. */
|
|
7
7
|
primary?: string;
|
|
@@ -19,11 +19,16 @@ type BookSlotCustomer = {
|
|
|
19
19
|
email?: string;
|
|
20
20
|
phone?: string;
|
|
21
21
|
notes?: string;
|
|
22
|
+
/** Answers to the shop's custom questions, keyed by question key (Dashboard → Booking form). */
|
|
23
|
+
fields?: Record<string, string | number | boolean>;
|
|
22
24
|
};
|
|
23
25
|
type BookSlotBooking = {
|
|
24
26
|
appointmentId: string;
|
|
25
27
|
serviceId: string;
|
|
26
28
|
serviceName: string;
|
|
29
|
+
/** Who it's with; null when the shop has no staff. */
|
|
30
|
+
staffId: string | null;
|
|
31
|
+
staffName: string | null;
|
|
27
32
|
/** ISO timestamp. */
|
|
28
33
|
startsAt: string;
|
|
29
34
|
/** ISO timestamp. */
|
|
@@ -40,6 +45,8 @@ type BookSlotWidgetProps = {
|
|
|
40
45
|
theme?: BookSlotTheme;
|
|
41
46
|
/** Pro: service id to preselect (listed in Dashboard → Widget). Can change at any time. */
|
|
42
47
|
service?: string;
|
|
48
|
+
/** Pro: staff member id to preselect (listed in Dashboard → Widget), or "" for anyone. Can change at any time. */
|
|
49
|
+
staff?: string;
|
|
43
50
|
/** Pro: details of the signed-in user. Sent by postMessage, never in the URL. */
|
|
44
51
|
customer?: BookSlotCustomer;
|
|
45
52
|
/** Height in pixels before the widget reports its own size. Default 720. */
|
|
@@ -66,7 +73,7 @@ type BookSlotWidgetProps = {
|
|
|
66
73
|
type EventName = "ready" | "resize" | "booked" | "error";
|
|
67
74
|
type BookSlotInstance = {
|
|
68
75
|
on(type: EventName, fn: (detail: unknown) => void): BookSlotInstance;
|
|
69
|
-
prefill(customer: BookSlotCustomer, service?: string): BookSlotInstance;
|
|
76
|
+
prefill(customer: BookSlotCustomer, service?: string, staff?: string): BookSlotInstance;
|
|
70
77
|
destroy(): void;
|
|
71
78
|
};
|
|
72
79
|
type BookSlotApi = {
|
|
@@ -77,6 +84,6 @@ type BookSlotApi = {
|
|
|
77
84
|
};
|
|
78
85
|
/** Loads widget.js once per BookSlot address and resolves with that address's API. */
|
|
79
86
|
declare function loadBookSlot(bookslotUrl?: string): Promise<BookSlotApi>;
|
|
80
|
-
declare function BookSlotWidget({ shop, bookslotUrl, theme, service, customer, initialHeight, maxWidth, title, className, style, onReady, onResize, onBooked, onError, }: BookSlotWidgetProps): react.JSX.Element;
|
|
87
|
+
declare function BookSlotWidget({ shop, bookslotUrl, theme, service, staff, customer, initialHeight, maxWidth, title, className, style, onReady, onResize, onBooked, onError, }: BookSlotWidgetProps): react.JSX.Element;
|
|
81
88
|
|
|
82
89
|
export { type BookSlotBooking, type BookSlotCustomer, type BookSlotTheme, BookSlotWidget, type BookSlotWidgetProps, DEFAULT_BOOKSLOT_URL, loadBookSlot };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.tsx
|
|
4
4
|
import { useEffect, useRef } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
|
-
var DEFAULT_BOOKSLOT_URL = "https://bookslot
|
|
6
|
+
var DEFAULT_BOOKSLOT_URL = "https://bookslot.taulantsela.com";
|
|
7
7
|
function apiFor(origin, { justLoaded = false } = {}) {
|
|
8
8
|
var _a;
|
|
9
9
|
const globals = window;
|
|
@@ -49,6 +49,7 @@ function BookSlotWidget({
|
|
|
49
49
|
bookslotUrl = DEFAULT_BOOKSLOT_URL,
|
|
50
50
|
theme,
|
|
51
51
|
service,
|
|
52
|
+
staff,
|
|
52
53
|
customer,
|
|
53
54
|
initialHeight,
|
|
54
55
|
maxWidth,
|
|
@@ -62,9 +63,9 @@ function BookSlotWidget({
|
|
|
62
63
|
}) {
|
|
63
64
|
const container = useRef(null);
|
|
64
65
|
const widget = useRef(null);
|
|
65
|
-
const latest = useRef({ service, customer, onReady, onResize, onBooked, onError });
|
|
66
|
+
const latest = useRef({ service, staff, customer, onReady, onResize, onBooked, onError });
|
|
66
67
|
useEffect(() => {
|
|
67
|
-
latest.current = { service, customer, onReady, onResize, onBooked, onError };
|
|
68
|
+
latest.current = { service, staff, customer, onReady, onResize, onBooked, onError };
|
|
68
69
|
});
|
|
69
70
|
const themeKey = JSON.stringify(theme != null ? theme : {});
|
|
70
71
|
useEffect(() => {
|
|
@@ -73,11 +74,12 @@ function BookSlotWidget({
|
|
|
73
74
|
loadBookSlot(bookslotUrl).then(
|
|
74
75
|
(api) => {
|
|
75
76
|
if (cancelled || !container.current) return;
|
|
76
|
-
const { service: service2, customer: customer2 } = latest.current;
|
|
77
|
+
const { service: service2, staff: staff2, customer: customer2 } = latest.current;
|
|
77
78
|
instance = api.mount(container.current, {
|
|
78
79
|
shop,
|
|
79
80
|
theme: JSON.parse(themeKey),
|
|
80
81
|
service: service2,
|
|
82
|
+
staff: staff2 || void 0,
|
|
81
83
|
prefill: customer2,
|
|
82
84
|
height: initialHeight,
|
|
83
85
|
maxWidth,
|
|
@@ -111,8 +113,8 @@ function BookSlotWidget({
|
|
|
111
113
|
}, [shop, bookslotUrl, themeKey, initialHeight, maxWidth, title]);
|
|
112
114
|
useEffect(() => {
|
|
113
115
|
var _a;
|
|
114
|
-
if (service) (_a = widget.current) == null ? void 0 : _a.prefill({}, service);
|
|
115
|
-
}, [service]);
|
|
116
|
+
if (service || staff !== void 0) (_a = widget.current) == null ? void 0 : _a.prefill({}, service, staff);
|
|
117
|
+
}, [service, staff]);
|
|
116
118
|
const customerKey = JSON.stringify(customer != null ? customer : {});
|
|
117
119
|
useEffect(() => {
|
|
118
120
|
var _a;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bookslot/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React component for the BookSlot booking widget",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"nextjs",
|
|
35
35
|
"bookslot"
|
|
36
36
|
],
|
|
37
|
-
"homepage": "https://bookslot
|
|
37
|
+
"homepage": "https://bookslot.taulantsela.com",
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"react": ">=18"
|
|
40
40
|
},
|