@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bookslot/react
2
2
 
3
- The [BookSlot](https://bookslot-rho.vercel.app) booking widget as a React component. Works in Next.js (App Router and Pages Router), Vite, Remix and any other React app.
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
- customer={{ name: user.name, email: user.email }} // the signed-in user
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
- | `customer` | `{ name?, email?, phone?, notes? }` | Pro. Fills in the form. Sent with `postMessage`, never in the URL. |
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-rho.vercel.app/widget.js" data-shop="your-shop-slug" async></script>
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-rho.vercel.app";
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-rho.vercel.app";
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-rho.vercel.app";
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-rho.vercel.app";
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.1.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-rho.vercel.app",
37
+ "homepage": "https://bookslot.taulantsela.com",
38
38
  "peerDependencies": {
39
39
  "react": ">=18"
40
40
  },