@springbrand/gravel 0.1.3-alpha.2 → 0.1.3-alpha.3
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/dist/components/ui-base/index.d.ts +1 -0
- package/dist/components/ui-base/responsive-dialog/index.d.ts +55 -0
- package/dist/components/ui-block/background/index.d.ts +8 -0
- package/dist/components/ui-block/cover/index.d.ts +17 -0
- package/dist/components/ui-block/index.d.ts +4 -0
- package/dist/components/ui-block/sparkles/index.d.ts +13 -0
- package/dist/components/ui-block/spotlight/index.d.ts +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -9
- package/package.json +3 -11
- package/dist/_virtual/_commonjsHelpers.js +0 -8
- package/dist/_virtual/timezone.js +0 -4
- package/dist/_virtual/utc.js +0 -4
- package/dist/components/runtime-site/action/index.js +0 -57
- package/dist/components/runtime-site/floating-booking-bubble/api/booking-public.js +0 -147
- package/dist/components/runtime-site/floating-booking-bubble/booking-form-field.js +0 -102
- package/dist/components/runtime-site/floating-booking-bubble/bubble-container.js +0 -89
- package/dist/components/runtime-site/floating-booking-bubble/bubble-trigger.js +0 -41
- package/dist/components/runtime-site/floating-booking-bubble/dayjs-tz.js +0 -9
- package/dist/components/runtime-site/floating-booking-bubble/index.js +0 -146
- package/dist/components/runtime-site/floating-booking-bubble/public-appointment-model.js +0 -92
- package/dist/components/runtime-site/floating-booking-bubble/public-availability-model.js +0 -14
- package/dist/components/runtime-site/floating-booking-bubble/public-service-detail-model.js +0 -33
- package/dist/components/runtime-site/floating-booking-bubble/public-service-model.js +0 -19
- package/dist/components/runtime-site/floating-booking-bubble/step-1-service-picker.js +0 -83
- package/dist/components/runtime-site/floating-booking-bubble/step-2-time-picker.js +0 -151
- package/dist/components/runtime-site/floating-booking-bubble/step-3-details-form.js +0 -142
- package/dist/components/runtime-site/floating-booking-bubble/step-4-confirm.js +0 -122
- package/dist/components/runtime-site/floating-booking-bubble/use-booking-flow.js +0 -287
- package/dist/components/runtime-site/runtime-host-provider/index.js +0 -20
- package/dist/node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/timezone.js +0 -67
- package/dist/node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/utc.js +0 -79
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ResponsiveDialog, ResponsiveDialogTrigger, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogHeader, ResponsiveDialogFooter, ResponsiveDialogTitle, ResponsiveDialogDescription, } from "./responsive-dialog/index.tsx";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type ResponsiveMode = "auto" | "dialog" | "drawer";
|
|
3
|
+
type ResponsiveDialogProps = {
|
|
4
|
+
/** 'auto' 会按 useIsMobile() 判断;也可强制 'dialog' / 'drawer' */
|
|
5
|
+
mode?: ResponsiveMode;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
defaultOpen?: boolean;
|
|
8
|
+
onOpenChange?: (open: boolean) => void;
|
|
9
|
+
modal?: boolean;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
/** drawer 模式透传:方向、snap 等 */
|
|
12
|
+
drawerProps?: Record<string, any>;
|
|
13
|
+
/** dialog 模式透传 */
|
|
14
|
+
dialogProps?: Record<string, any>;
|
|
15
|
+
};
|
|
16
|
+
declare function ResponsiveDialog({ mode, children, drawerProps, dialogProps, ...props }: ResponsiveDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
type TriggerProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
18
|
+
asChild?: boolean;
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
};
|
|
21
|
+
declare function ResponsiveDialogTrigger(props: TriggerProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
type CloseProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
23
|
+
asChild?: boolean;
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
};
|
|
26
|
+
declare function ResponsiveDialogClose(props: CloseProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
type ResponsiveDialogContentProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
28
|
+
/** dialog 专属:是否展示右上角关闭按钮 */
|
|
29
|
+
showCloseButton?: boolean;
|
|
30
|
+
/** 仅 dialog 模式追加的 className */
|
|
31
|
+
dialogClassName?: string;
|
|
32
|
+
/** 仅 drawer 模式追加的 className */
|
|
33
|
+
drawerClassName?: string;
|
|
34
|
+
onEscapeKeyDown?: (e: KeyboardEvent) => void;
|
|
35
|
+
onPointerDownOutside?: (e: Event) => void;
|
|
36
|
+
};
|
|
37
|
+
declare function ResponsiveDialogContent({ className, dialogClassName, drawerClassName, children, showCloseButton, onEscapeKeyDown, onPointerDownOutside, ...props }: ResponsiveDialogContentProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
type HeaderProps = React.ComponentProps<"div">;
|
|
39
|
+
declare function ResponsiveDialogHeader(props: HeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
type FooterProps = React.ComponentProps<"div"> & {
|
|
41
|
+
/** dialog 专属 */
|
|
42
|
+
showCloseButton?: boolean;
|
|
43
|
+
};
|
|
44
|
+
declare function ResponsiveDialogFooter({ showCloseButton, className, ...props }: FooterProps): import("react/jsx-runtime").JSX.Element;
|
|
45
|
+
type TitleProps = React.HTMLAttributes<HTMLHeadingElement> & {
|
|
46
|
+
asChild?: boolean;
|
|
47
|
+
children?: React.ReactNode;
|
|
48
|
+
};
|
|
49
|
+
declare function ResponsiveDialogTitle(props: TitleProps): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
type DescriptionProps = React.HTMLAttributes<HTMLParagraphElement> & {
|
|
51
|
+
asChild?: boolean;
|
|
52
|
+
children?: React.ReactNode;
|
|
53
|
+
};
|
|
54
|
+
declare function ResponsiveDialogDescription(props: DescriptionProps): import("react/jsx-runtime").JSX.Element;
|
|
55
|
+
export { ResponsiveDialog, ResponsiveDialogTrigger, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogHeader, ResponsiveDialogFooter, ResponsiveDialogTitle, ResponsiveDialogDescription, };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
interface IBlurryBackgroundProps {
|
|
3
|
+
children?: ReactNode;
|
|
4
|
+
primaryColor?: string;
|
|
5
|
+
secondaryColor?: string;
|
|
6
|
+
}
|
|
7
|
+
export default function Background({ children, primaryColor, secondaryColor, }: IBlurryBackgroundProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { motion } from "motion/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
export declare const Cover: ({ children, className, }: {
|
|
4
|
+
children?: React.ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare const Beam: ({ className, delay, duration, hovered, width, ...svgProps }: {
|
|
8
|
+
className?: string;
|
|
9
|
+
delay?: number;
|
|
10
|
+
duration?: number;
|
|
11
|
+
hovered?: boolean;
|
|
12
|
+
width?: number;
|
|
13
|
+
} & React.ComponentProps<typeof motion.svg>) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare const CircleIcon: ({ className, }: {
|
|
15
|
+
className?: string;
|
|
16
|
+
delay?: number;
|
|
17
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type ParticlesProps = {
|
|
2
|
+
id?: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
background?: string;
|
|
5
|
+
particleSize?: number;
|
|
6
|
+
minSize?: number;
|
|
7
|
+
maxSize?: number;
|
|
8
|
+
speed?: number;
|
|
9
|
+
particleColor?: string;
|
|
10
|
+
particleDensity?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const SparklesCore: (props: ParticlesProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type SpotlightProps = {
|
|
2
|
+
gradientFirst?: string;
|
|
3
|
+
gradientSecond?: string;
|
|
4
|
+
gradientThird?: string;
|
|
5
|
+
translateY?: number;
|
|
6
|
+
width?: number;
|
|
7
|
+
height?: number;
|
|
8
|
+
smallWidth?: number;
|
|
9
|
+
duration?: number;
|
|
10
|
+
xOffset?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const Spotlight: ({ gradientFirst, gradientSecond, gradientThird, translateY, width, height, smallWidth, duration, xOffset, }?: SpotlightProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import { default as default2 } from "./components/ui-block/background/index.js";
|
|
2
|
-
import { Action } from "./components/runtime-site/action/index.js";
|
|
3
2
|
import { Cover } from "./components/ui-block/cover/index.js";
|
|
4
|
-
import { FloatingBookingBubble, FloatingBookingBubbleDefaults } from "./components/runtime-site/floating-booking-bubble/index.js";
|
|
5
3
|
import { ResponsiveDialog, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, ResponsiveDialogTrigger } from "./components/ui-base/responsive-dialog/index.js";
|
|
6
|
-
import { RuntimeHostProvider, useRuntimeHost } from "./components/runtime-site/runtime-host-provider/index.js";
|
|
7
4
|
import { SparklesCore } from "./components/ui-block/sparkles/index.js";
|
|
8
5
|
import { Spotlight } from "./components/ui-block/spotlight/index.js";
|
|
9
6
|
export {
|
|
10
|
-
Action,
|
|
11
7
|
default2 as Background,
|
|
12
8
|
Cover,
|
|
13
|
-
FloatingBookingBubble,
|
|
14
|
-
FloatingBookingBubbleDefaults,
|
|
15
9
|
ResponsiveDialog,
|
|
16
10
|
ResponsiveDialogClose,
|
|
17
11
|
ResponsiveDialogContent,
|
|
@@ -20,8 +14,6 @@ export {
|
|
|
20
14
|
ResponsiveDialogHeader,
|
|
21
15
|
ResponsiveDialogTitle,
|
|
22
16
|
ResponsiveDialogTrigger,
|
|
23
|
-
RuntimeHostProvider,
|
|
24
17
|
SparklesCore,
|
|
25
|
-
Spotlight
|
|
26
|
-
useRuntimeHost
|
|
18
|
+
Spotlight
|
|
27
19
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/gravel",
|
|
3
|
-
"version": "0.1.3-alpha.
|
|
3
|
+
"version": "0.1.3-alpha.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,19 +38,11 @@
|
|
|
38
38
|
"types": "./dist/components/ui-base/*/index.d.ts",
|
|
39
39
|
"import": "./dist/components/ui-base/*/index.js",
|
|
40
40
|
"default": "./dist/components/ui-base/*/index.js"
|
|
41
|
-
},
|
|
42
|
-
"./components/runtime-site/*": {
|
|
43
|
-
"types": "./dist/components/runtime-site/*/index.d.ts",
|
|
44
|
-
"import": "./dist/components/runtime-site/*/index.js",
|
|
45
|
-
"default": "./dist/components/runtime-site/*/index.js"
|
|
46
41
|
}
|
|
47
42
|
},
|
|
48
43
|
"dependencies": {
|
|
49
44
|
"@springbrand/site-block": "0.1.3-alpha.2",
|
|
50
|
-
"@springbrand/utils": "0.1.1"
|
|
51
|
-
"dayjs": "^1.11.0",
|
|
52
|
-
"lucide-react": ">=0.400.0",
|
|
53
|
-
"zod": "4.3.6"
|
|
45
|
+
"@springbrand/utils": "0.1.1"
|
|
54
46
|
},
|
|
55
47
|
"peerDependencies": {
|
|
56
48
|
"@tsparticles/engine": "^3.0.0",
|
|
@@ -73,5 +65,5 @@
|
|
|
73
65
|
"optional": true
|
|
74
66
|
}
|
|
75
67
|
},
|
|
76
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "b7a0bc2ff49d1ad39bcb241f46ad4467fa6f9430"
|
|
77
69
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
2
|
-
function getDefaultExportFromCjs(x) {
|
|
3
|
-
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
4
|
-
}
|
|
5
|
-
export {
|
|
6
|
-
commonjsGlobal,
|
|
7
|
-
getDefaultExportFromCjs
|
|
8
|
-
};
|
package/dist/_virtual/utc.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { Children, cloneElement } from "react";
|
|
3
|
-
import { useRuntimeHost } from "../runtime-host-provider/index.js";
|
|
4
|
-
function createRuntimeActionContext(trigger, event) {
|
|
5
|
-
const element = event.currentTarget;
|
|
6
|
-
const ownerDocument = element.ownerDocument;
|
|
7
|
-
return {
|
|
8
|
-
trigger,
|
|
9
|
-
event,
|
|
10
|
-
element,
|
|
11
|
-
ownerDocument,
|
|
12
|
-
ownerWindow: ownerDocument.defaultView
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
function Action({
|
|
16
|
-
children,
|
|
17
|
-
id,
|
|
18
|
-
input,
|
|
19
|
-
trigger = "click"
|
|
20
|
-
}) {
|
|
21
|
-
const runtimeHost = useRuntimeHost();
|
|
22
|
-
const child = Children.only(children);
|
|
23
|
-
const originalHandler = trigger === "click" ? child.props.onClick : trigger === "pointerDown" ? child.props.onPointerDown : child.props.onSubmit;
|
|
24
|
-
const actionHandler = (event) => {
|
|
25
|
-
var _a, _b;
|
|
26
|
-
originalHandler == null ? void 0 : originalHandler(event);
|
|
27
|
-
if (event.defaultPrevented || !id || !runtimeHost.actions) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const context = createRuntimeActionContext(trigger, event);
|
|
31
|
-
try {
|
|
32
|
-
const result = runtimeHost.actions.runAction(id, input, context);
|
|
33
|
-
void Promise.resolve(result).catch((error) => {
|
|
34
|
-
var _a2, _b2;
|
|
35
|
-
(_b2 = (_a2 = runtimeHost.actions) == null ? void 0 : _a2.onError) == null ? void 0 : _b2.call(_a2, error, context);
|
|
36
|
-
});
|
|
37
|
-
} catch (error) {
|
|
38
|
-
(_b = (_a = runtimeHost.actions).onError) == null ? void 0 : _b.call(_a, error, context);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
if (trigger === "pointerDown") {
|
|
42
|
-
return cloneElement(child, {
|
|
43
|
-
onPointerDown: actionHandler
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (trigger === "submit") {
|
|
47
|
-
return cloneElement(child, {
|
|
48
|
-
onSubmit: actionHandler
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
return cloneElement(child, {
|
|
52
|
-
onClick: actionHandler
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
export {
|
|
56
|
-
Action
|
|
57
|
-
};
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { PublicServiceModel } from "../public-service-model.js";
|
|
2
|
-
import { PublicServiceDetailModel } from "../public-service-detail-model.js";
|
|
3
|
-
import { PublicAvailabilityModel } from "../public-availability-model.js";
|
|
4
|
-
import { PublicAppointmentModel } from "../public-appointment-model.js";
|
|
5
|
-
class BookingApiError extends Error {
|
|
6
|
-
constructor(kind, code, httpStatus, message, details) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.kind = kind;
|
|
9
|
-
this.code = code;
|
|
10
|
-
this.httpStatus = httpStatus;
|
|
11
|
-
this.details = details;
|
|
12
|
-
this.name = "BookingApiError";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function unwrapPublicRpc(wire, httpStatus) {
|
|
16
|
-
if ((wire == null ? void 0 : wire.success) === true && wire.msg !== void 0) {
|
|
17
|
-
return wire.msg;
|
|
18
|
-
}
|
|
19
|
-
throw new BookingApiError(
|
|
20
|
-
"biz",
|
|
21
|
-
(wire == null ? void 0 : wire.code) ?? "BOOKING_UNKNOWN_ERROR",
|
|
22
|
-
httpStatus,
|
|
23
|
-
(wire == null ? void 0 : wire.errMsg) ?? "Booking request failed",
|
|
24
|
-
wire
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
function normalizeBaseUrl(domain) {
|
|
28
|
-
return (domain ?? "").replace(/\/$/, "");
|
|
29
|
-
}
|
|
30
|
-
async function publicFetch(domain, path, init) {
|
|
31
|
-
const baseUrl = normalizeBaseUrl(domain);
|
|
32
|
-
let res;
|
|
33
|
-
try {
|
|
34
|
-
res = await fetch(`${baseUrl}${path}`, {
|
|
35
|
-
...init,
|
|
36
|
-
headers: {
|
|
37
|
-
"Content-Type": "application/json",
|
|
38
|
-
...(init == null ? void 0 : init.headers) ?? {}
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
} catch (err) {
|
|
42
|
-
throw new BookingApiError(
|
|
43
|
-
"network",
|
|
44
|
-
"NETWORK_ERROR",
|
|
45
|
-
null,
|
|
46
|
-
err instanceof Error ? err.message : "Network error"
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
let body = null;
|
|
51
|
-
try {
|
|
52
|
-
body = await res.json();
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
55
|
-
const code = res.status === 429 ? "RATE_LIMITED" : "INTERNAL_ERROR";
|
|
56
|
-
throw new BookingApiError(
|
|
57
|
-
"http",
|
|
58
|
-
code,
|
|
59
|
-
res.status,
|
|
60
|
-
`HTTP ${res.status}`,
|
|
61
|
-
body
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
let wire;
|
|
65
|
-
try {
|
|
66
|
-
wire = await res.json();
|
|
67
|
-
} catch (err) {
|
|
68
|
-
throw new BookingApiError(
|
|
69
|
-
"network",
|
|
70
|
-
"INVALID_JSON",
|
|
71
|
-
res.status,
|
|
72
|
-
err instanceof Error ? err.message : "Invalid JSON"
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
return unwrapPublicRpc(wire, res.status);
|
|
76
|
-
}
|
|
77
|
-
async function fetchServices(domain, brandSlug) {
|
|
78
|
-
const wire = await publicFetch(
|
|
79
|
-
domain,
|
|
80
|
-
`/api/public/booking/${encodeURIComponent(brandSlug)}/services`
|
|
81
|
-
);
|
|
82
|
-
return {
|
|
83
|
-
services: wire.services.map(PublicServiceModel.fromWire),
|
|
84
|
-
timezone: wire.timezone
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
async function fetchServiceDetail(domain, brandSlug, serviceId) {
|
|
88
|
-
const wire = await publicFetch(
|
|
89
|
-
domain,
|
|
90
|
-
`/api/public/booking/${encodeURIComponent(brandSlug)}/services/${serviceId}`
|
|
91
|
-
);
|
|
92
|
-
return {
|
|
93
|
-
service: PublicServiceDetailModel.fromWire(wire.service),
|
|
94
|
-
timezone: wire.timezone
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
async function fetchAvailability(domain, brandSlug, params) {
|
|
98
|
-
const qs = new URLSearchParams({
|
|
99
|
-
service_id: String(params.service_id),
|
|
100
|
-
from: params.from,
|
|
101
|
-
to: params.to
|
|
102
|
-
});
|
|
103
|
-
const wire = await publicFetch(
|
|
104
|
-
domain,
|
|
105
|
-
`/api/public/booking/${encodeURIComponent(brandSlug)}/availability?${qs.toString()}`
|
|
106
|
-
);
|
|
107
|
-
return PublicAvailabilityModel.fromWire(wire);
|
|
108
|
-
}
|
|
109
|
-
async function submitAppointment(domain, brandSlug, body) {
|
|
110
|
-
const wireBody = {
|
|
111
|
-
service_id: body.service_id,
|
|
112
|
-
scheduled_at: body.scheduled_at,
|
|
113
|
-
contact: {
|
|
114
|
-
name: [body.customer.first_name, body.customer.last_name].filter(Boolean).join(" ").trim(),
|
|
115
|
-
email: body.customer.email,
|
|
116
|
-
phone: body.customer.phone,
|
|
117
|
-
metadata: {
|
|
118
|
-
first_name: body.customer.first_name,
|
|
119
|
-
last_name: body.customer.last_name,
|
|
120
|
-
address: body.customer.address
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
custom_answers: body.custom_answers.map((a) => ({
|
|
124
|
-
question_id: a.questionId,
|
|
125
|
-
label: a.label,
|
|
126
|
-
answer: a.answer
|
|
127
|
-
})),
|
|
128
|
-
source_page: body.source_page
|
|
129
|
-
};
|
|
130
|
-
const wire = await publicFetch(
|
|
131
|
-
domain,
|
|
132
|
-
`/api/public/booking/${encodeURIComponent(brandSlug)}/appointments`,
|
|
133
|
-
{
|
|
134
|
-
method: "POST",
|
|
135
|
-
body: JSON.stringify(wireBody)
|
|
136
|
-
}
|
|
137
|
-
);
|
|
138
|
-
return PublicAppointmentModel.fromWire(wire.appointment);
|
|
139
|
-
}
|
|
140
|
-
export {
|
|
141
|
-
BookingApiError,
|
|
142
|
-
fetchAvailability,
|
|
143
|
-
fetchServiceDetail,
|
|
144
|
-
fetchServices,
|
|
145
|
-
submitAppointment,
|
|
146
|
-
unwrapPublicRpc
|
|
147
|
-
};
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
-
import { Input } from "@springbrand/site-block/components/shadcn/input";
|
|
4
|
-
import { Label } from "@springbrand/site-block/components/shadcn/label";
|
|
5
|
-
import { Textarea } from "@springbrand/site-block/components/shadcn/textarea";
|
|
6
|
-
import { RadioGroup, RadioGroupItem } from "@springbrand/site-block/components/shadcn/radio-group";
|
|
7
|
-
import { Checkbox } from "@springbrand/site-block/components/shadcn/checkbox";
|
|
8
|
-
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@springbrand/site-block/components/shadcn/select";
|
|
9
|
-
import { cn } from "@springbrand/site-block/lib/utils";
|
|
10
|
-
function BookingFormField({
|
|
11
|
-
question,
|
|
12
|
-
value,
|
|
13
|
-
onChange,
|
|
14
|
-
error
|
|
15
|
-
}) {
|
|
16
|
-
const fieldId = `q-${question.id}`;
|
|
17
|
-
const renderControl = () => {
|
|
18
|
-
switch (question.type) {
|
|
19
|
-
case "one_line":
|
|
20
|
-
return /* @__PURE__ */ jsx(
|
|
21
|
-
Input,
|
|
22
|
-
{
|
|
23
|
-
id: fieldId,
|
|
24
|
-
value: value ?? "",
|
|
25
|
-
onChange: (e) => onChange(e.target.value),
|
|
26
|
-
"aria-invalid": !!error || void 0
|
|
27
|
-
}
|
|
28
|
-
);
|
|
29
|
-
case "multi_line":
|
|
30
|
-
return /* @__PURE__ */ jsx(
|
|
31
|
-
Textarea,
|
|
32
|
-
{
|
|
33
|
-
id: fieldId,
|
|
34
|
-
value: value ?? "",
|
|
35
|
-
onChange: (e) => onChange(e.target.value),
|
|
36
|
-
"aria-invalid": !!error || void 0
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
case "radio":
|
|
40
|
-
return /* @__PURE__ */ jsx(
|
|
41
|
-
RadioGroup,
|
|
42
|
-
{
|
|
43
|
-
value: value ?? "",
|
|
44
|
-
onValueChange: (v) => onChange(v),
|
|
45
|
-
className: "gap-2",
|
|
46
|
-
children: (question.options ?? []).map((opt) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
47
|
-
/* @__PURE__ */ jsx(RadioGroupItem, { id: `${fieldId}-${opt.id}`, value: opt.id }),
|
|
48
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: `${fieldId}-${opt.id}`, className: "font-normal", children: opt.label })
|
|
49
|
-
] }, opt.id))
|
|
50
|
-
}
|
|
51
|
-
);
|
|
52
|
-
case "checkboxes": {
|
|
53
|
-
const arr = Array.isArray(value) ? value : [];
|
|
54
|
-
return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: (question.options ?? []).map((opt) => {
|
|
55
|
-
const checked = arr.includes(opt.id);
|
|
56
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
57
|
-
/* @__PURE__ */ jsx(
|
|
58
|
-
Checkbox,
|
|
59
|
-
{
|
|
60
|
-
id: `${fieldId}-${opt.id}`,
|
|
61
|
-
checked,
|
|
62
|
-
onCheckedChange: (c) => {
|
|
63
|
-
const next = c ? [...arr, opt.id] : arr.filter((id) => id !== opt.id);
|
|
64
|
-
onChange(next);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
),
|
|
68
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: `${fieldId}-${opt.id}`, className: "font-normal", children: opt.label })
|
|
69
|
-
] }, opt.id);
|
|
70
|
-
}) });
|
|
71
|
-
}
|
|
72
|
-
case "dropdown": {
|
|
73
|
-
const options = question.options ?? [];
|
|
74
|
-
return /* @__PURE__ */ jsxs(
|
|
75
|
-
Select,
|
|
76
|
-
{
|
|
77
|
-
value: value ?? "",
|
|
78
|
-
items: options.map((opt) => ({ label: opt.label, value: opt.id })),
|
|
79
|
-
onValueChange: (v) => onChange(v),
|
|
80
|
-
children: [
|
|
81
|
-
/* @__PURE__ */ jsx(SelectTrigger, { id: fieldId, "aria-invalid": !!error || void 0, children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select…" }) }),
|
|
82
|
-
/* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.id, children: opt.label }, opt.id)) })
|
|
83
|
-
]
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
default:
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1.5", error && "[&>label]:text-destructive"), children: [
|
|
92
|
-
/* @__PURE__ */ jsxs(Label, { htmlFor: fieldId, children: [
|
|
93
|
-
question.label,
|
|
94
|
-
question.required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-destructive", children: "*" })
|
|
95
|
-
] }),
|
|
96
|
-
renderControl(),
|
|
97
|
-
error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error })
|
|
98
|
-
] });
|
|
99
|
-
}
|
|
100
|
-
export {
|
|
101
|
-
BookingFormField
|
|
102
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { X } from "lucide-react";
|
|
4
|
-
import { useState, useEffect } from "react";
|
|
5
|
-
import { Sheet, SheetContent } from "@springbrand/site-block/components/shadcn/sheet";
|
|
6
|
-
import { Button } from "@springbrand/site-block/components/shadcn/button";
|
|
7
|
-
import { cn } from "@springbrand/site-block/lib/utils";
|
|
8
|
-
function BubbleContainer({
|
|
9
|
-
open,
|
|
10
|
-
onOpenChange,
|
|
11
|
-
title,
|
|
12
|
-
children,
|
|
13
|
-
onBack,
|
|
14
|
-
stepIndicator,
|
|
15
|
-
className,
|
|
16
|
-
container
|
|
17
|
-
}) {
|
|
18
|
-
const [isDesktop, setIsDesktop] = useState(false);
|
|
19
|
-
const headerTitle = title ?? (onBack ? null : "Book");
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
const targetWindow = resolveContainerWindow(container);
|
|
22
|
-
if (!targetWindow) return;
|
|
23
|
-
const mq = targetWindow.matchMedia("(min-width: 768px)");
|
|
24
|
-
const update = () => setIsDesktop(mq.matches);
|
|
25
|
-
update();
|
|
26
|
-
mq.addEventListener("change", update);
|
|
27
|
-
return () => mq.removeEventListener("change", update);
|
|
28
|
-
}, [container]);
|
|
29
|
-
return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
|
|
30
|
-
SheetContent,
|
|
31
|
-
{
|
|
32
|
-
side: isDesktop ? "right" : "bottom",
|
|
33
|
-
showCloseButton: false,
|
|
34
|
-
container,
|
|
35
|
-
className: cn(
|
|
36
|
-
"z-[1001] p-0 flex flex-col gap-0 overflow-hidden",
|
|
37
|
-
isDesktop ? (
|
|
38
|
-
// 桌面 popover 与气泡按钮的相对位置:
|
|
39
|
-
// - bottom-28 (112px) 让对话框底端高于按钮顶部 (72px) 约 40px,留出明显呼吸感
|
|
40
|
-
// - right-8 (32px) 把对话框右边再让出一点,避免和屏幕右沿/按钮贴在一起
|
|
41
|
-
// SheetContent 基类带 data-[side=right]:{inset-y-0,h-full,w-3/4},
|
|
42
|
-
// 必须用 ! important 覆盖 top / bottom / 高宽,否则会被钉在 top:0 全高。
|
|
43
|
-
"w-[400px]! sm:max-w-[420px] right-8 top-auto! bottom-28! h-[600px]! max-h-[calc(100vh-9rem)] rounded-2xl border shadow-2xl"
|
|
44
|
-
) : "h-[min(85svh,calc(100dvh-env(safe-area-inset-top)-0.75rem))] w-full rounded-t-2xl border-t",
|
|
45
|
-
className
|
|
46
|
-
),
|
|
47
|
-
overlayClassName: "z-[1000]",
|
|
48
|
-
children: [
|
|
49
|
-
/* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b px-4 h-12 shrink-0", children: [
|
|
50
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
51
|
-
onBack && /* @__PURE__ */ jsx(
|
|
52
|
-
Button,
|
|
53
|
-
{
|
|
54
|
-
type: "button",
|
|
55
|
-
variant: "ghost",
|
|
56
|
-
size: "sm",
|
|
57
|
-
className: "-ml-2 h-8 px-2",
|
|
58
|
-
onClick: onBack,
|
|
59
|
-
children: "Back"
|
|
60
|
-
}
|
|
61
|
-
),
|
|
62
|
-
headerTitle && /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm truncate", children: headerTitle })
|
|
63
|
-
] }),
|
|
64
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
65
|
-
stepIndicator && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: stepIndicator }),
|
|
66
|
-
/* @__PURE__ */ jsx(
|
|
67
|
-
"button",
|
|
68
|
-
{
|
|
69
|
-
type: "button",
|
|
70
|
-
"aria-label": "Close",
|
|
71
|
-
onClick: () => onOpenChange(false),
|
|
72
|
-
className: "rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
73
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
74
|
-
}
|
|
75
|
-
)
|
|
76
|
-
] })
|
|
77
|
-
] }),
|
|
78
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain [-webkit-overflow-scrolling:touch]", children })
|
|
79
|
-
]
|
|
80
|
-
}
|
|
81
|
-
) });
|
|
82
|
-
}
|
|
83
|
-
function resolveContainerWindow(container) {
|
|
84
|
-
const element = container && "current" in container ? container.current : container;
|
|
85
|
-
return (element == null ? void 0 : element.ownerDocument.defaultView) ?? null;
|
|
86
|
-
}
|
|
87
|
-
export {
|
|
88
|
-
BubbleContainer
|
|
89
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Calendar } from "lucide-react";
|
|
4
|
-
import { Button } from "@springbrand/site-block/components/shadcn/button";
|
|
5
|
-
import { cn } from "@springbrand/site-block/lib/utils";
|
|
6
|
-
function BubbleTrigger({
|
|
7
|
-
label = "Book now",
|
|
8
|
-
onClick,
|
|
9
|
-
editorMode,
|
|
10
|
-
className,
|
|
11
|
-
style,
|
|
12
|
-
...domProps
|
|
13
|
-
}) {
|
|
14
|
-
return /* @__PURE__ */ jsx(
|
|
15
|
-
"div",
|
|
16
|
-
{
|
|
17
|
-
...domProps,
|
|
18
|
-
className: cn(
|
|
19
|
-
"fixed bottom-[calc(env(safe-area-inset-bottom)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)] z-50 print:hidden sm:bottom-6 sm:right-6",
|
|
20
|
-
editorMode && "group cursor-pointer outline-primary/40 rounded-full"
|
|
21
|
-
),
|
|
22
|
-
onClick,
|
|
23
|
-
children: /* @__PURE__ */ jsxs(
|
|
24
|
-
Button,
|
|
25
|
-
{
|
|
26
|
-
type: "button",
|
|
27
|
-
size: "lg",
|
|
28
|
-
className: cn("rounded-full shadow-lg gap-2 px-5 h-12", className),
|
|
29
|
-
style,
|
|
30
|
-
children: [
|
|
31
|
-
/* @__PURE__ */ jsx(Calendar, { className: "h-5 w-5" }),
|
|
32
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: label })
|
|
33
|
-
]
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
export {
|
|
40
|
-
BubbleTrigger
|
|
41
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import dayjs from "dayjs";
|
|
2
|
-
import { default as default2 } from "dayjs";
|
|
3
|
-
import utc from "../../../node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/utc.js";
|
|
4
|
-
import timezone from "../../../node_modules/.pnpm/dayjs@1.11.20/node_modules/dayjs/plugin/timezone.js";
|
|
5
|
-
dayjs.extend(utc);
|
|
6
|
-
dayjs.extend(timezone);
|
|
7
|
-
export {
|
|
8
|
-
default2 as default
|
|
9
|
-
};
|