keystone-design-bootstrap 1.0.55 → 1.0.56
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/design_system/elements/index.js +8 -3
- package/dist/design_system/elements/index.js.map +1 -1
- package/dist/design_system/sections/index.js +203 -106
- package/dist/design_system/sections/index.js.map +1 -1
- package/dist/index.js +303 -247
- package/dist/index.js.map +1 -1
- package/dist/lib/hooks/index.js +72 -0
- package/dist/lib/hooks/index.js.map +1 -1
- package/dist/lib/server-api.js.map +1 -1
- package/dist/utils/phone-helpers.js +26 -0
- package/dist/utils/phone-helpers.js.map +1 -0
- package/package.json +5 -2
- package/src/design_system/components/ChatWidget.tsx +51 -34
- package/src/design_system/components/DynamicFormFields.tsx +1 -24
- package/src/design_system/elements/modal/modal.tsx +54 -35
- package/src/design_system/portal/LoginForm.tsx +339 -0
- package/src/design_system/portal/LoginModalController.tsx +63 -0
- package/src/design_system/portal/LogoutButton.tsx +23 -0
- package/src/design_system/portal/MessageComposer.tsx +84 -0
- package/src/design_system/portal/PortalPage.tsx +754 -0
- package/src/design_system/portal/RowThumbnail.tsx +76 -0
- package/src/design_system/portal/actions.ts +160 -0
- package/src/design_system/portal/index.ts +5 -0
- package/src/design_system/sections/index.tsx +1 -1
- package/src/design_system/sections/service-menu-section.tsx +7 -108
- package/src/lib/actions.ts +51 -115
- package/src/lib/consumer-session.ts +74 -0
- package/src/lib/hooks/index.ts +2 -0
- package/src/lib/hooks/use-image-cycle.ts +105 -0
- package/src/lib/server-api.ts +7 -6
- package/src/next/routes/chat.ts +30 -58
- package/src/next/routes/consumer-auth.ts +113 -0
- package/src/types/api/consumer.ts +39 -0
- package/src/types/api/offer.ts +1 -1
- package/src/types/api/package.ts +20 -0
- package/src/types/api/service.ts +6 -24
- package/src/types/index.ts +2 -0
- package/src/utils/phone-helpers.ts +27 -0
- package/dist/blog-post-DGjaJ3wf.d.ts +0 -50
- package/dist/contexts/index.d.ts +0 -13
- package/dist/design_system/elements/index.d.ts +0 -372
- package/dist/design_system/logo/keystone-logo.d.ts +0 -6
- package/dist/design_system/sections/index.d.ts +0 -237
- package/dist/form-CpsCONG5.d.ts +0 -151
- package/dist/index.d.ts +0 -76
- package/dist/lib/component-registry.d.ts +0 -13
- package/dist/lib/hooks/index.d.ts +0 -64
- package/dist/lib/server-api.d.ts +0 -43
- package/dist/themes/index.d.ts +0 -16
- package/dist/types/index.d.ts +0 -264
- package/dist/utils/cx.d.ts +0 -15
- package/dist/utils/gradient-placeholder.d.ts +0 -8
- package/dist/utils/is-react-component.d.ts +0 -21
- package/dist/utils/markdown-toc.d.ts +0 -14
- package/dist/utils/photo-helpers.d.ts +0 -37
- package/dist/website-photos-Bm-CBK9g.d.ts +0 -47
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ var __objRest = (source, exclude) => {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
// src/design_system/sections/index.tsx
|
|
40
|
-
import
|
|
40
|
+
import React62 from "react";
|
|
41
41
|
|
|
42
42
|
// src/lib/component-registry.ts
|
|
43
43
|
var registry = /* @__PURE__ */ new Map();
|
|
@@ -3740,6 +3740,9 @@ function Modal({
|
|
|
3740
3740
|
title,
|
|
3741
3741
|
titleId = "modal-title",
|
|
3742
3742
|
children,
|
|
3743
|
+
footer,
|
|
3744
|
+
hideHeader = false,
|
|
3745
|
+
ariaLabel,
|
|
3743
3746
|
overlayClassName,
|
|
3744
3747
|
panelClassName,
|
|
3745
3748
|
maxWidth = "md"
|
|
@@ -3771,7 +3774,8 @@ function Modal({
|
|
|
3771
3774
|
className: overlayClassName != null ? overlayClassName : "fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/50",
|
|
3772
3775
|
role: "dialog",
|
|
3773
3776
|
"aria-modal": "true",
|
|
3774
|
-
"aria-
|
|
3777
|
+
"aria-label": ariaLabel,
|
|
3778
|
+
"aria-labelledby": !ariaLabel && title && !hideHeader ? titleId : void 0,
|
|
3775
3779
|
onClick: (e) => e.target === overlayRef.current && onClose()
|
|
3776
3780
|
},
|
|
3777
3781
|
/* @__PURE__ */ React13.createElement(
|
|
@@ -3780,7 +3784,7 @@ function Modal({
|
|
|
3780
3784
|
className: panelClassName != null ? panelClassName : `bg-primary border border-secondary rounded-lg shadow-xl w-full overflow-hidden ${maxWidthClass} max-h-[90vh] flex flex-col`,
|
|
3781
3785
|
onClick: (e) => e.stopPropagation()
|
|
3782
3786
|
},
|
|
3783
|
-
/* @__PURE__ */ React13.createElement("div", { className: "flex items-start justify-between gap-4 p-4 md:p-6 border-b border-secondary flex-shrink-0" }, title ? /* @__PURE__ */ React13.createElement(
|
|
3787
|
+
!hideHeader && /* @__PURE__ */ React13.createElement("div", { className: "flex items-start justify-between gap-4 p-4 md:p-6 border-b border-secondary flex-shrink-0" }, title ? /* @__PURE__ */ React13.createElement(
|
|
3784
3788
|
"h2",
|
|
3785
3789
|
{
|
|
3786
3790
|
id: titleId,
|
|
@@ -3816,7 +3820,8 @@ function Modal({
|
|
|
3816
3820
|
)
|
|
3817
3821
|
)
|
|
3818
3822
|
)),
|
|
3819
|
-
/* @__PURE__ */ React13.createElement("div", { className: "overflow-y-auto flex-1 p-4 md:p-6" }, children)
|
|
3823
|
+
/* @__PURE__ */ React13.createElement("div", { className: "overflow-y-auto flex-1 p-4 md:p-6" }, children),
|
|
3824
|
+
footer && /* @__PURE__ */ React13.createElement("div", { className: "flex-shrink-0 border-t border-secondary" }, footer)
|
|
3820
3825
|
)
|
|
3821
3826
|
);
|
|
3822
3827
|
}
|
|
@@ -5977,8 +5982,7 @@ var countriesOptions = countries.map((country) => ({
|
|
|
5977
5982
|
}));
|
|
5978
5983
|
var countries_default = countries;
|
|
5979
5984
|
|
|
5980
|
-
// src/
|
|
5981
|
-
var INPUT_TYPES = ["text", "email", "tel"];
|
|
5985
|
+
// src/utils/phone-helpers.ts
|
|
5982
5986
|
function getNationalMask(country) {
|
|
5983
5987
|
if (!(country == null ? void 0 : country.phoneMask)) return "";
|
|
5984
5988
|
const code = country.phoneCode.startsWith("+") ? country.phoneCode : `+${country.phoneCode}`;
|
|
@@ -5999,6 +6003,9 @@ function formatDigitsToMask(digits, mask) {
|
|
|
5999
6003
|
}
|
|
6000
6004
|
return out;
|
|
6001
6005
|
}
|
|
6006
|
+
|
|
6007
|
+
// src/design_system/components/DynamicFormFields.tsx
|
|
6008
|
+
var INPUT_TYPES = ["text", "email", "tel"];
|
|
6002
6009
|
function allFieldsFlat(fields) {
|
|
6003
6010
|
const out = [];
|
|
6004
6011
|
for (const item of fields) {
|
|
@@ -6194,14 +6201,20 @@ function DynamicFormFields({ form, jobSlug, privacyPolicyUrl, termsOfServiceUrl
|
|
|
6194
6201
|
function trackMetaLead(eventId) {
|
|
6195
6202
|
if (typeof window === "undefined" || !eventId) return;
|
|
6196
6203
|
const fbq = window.fbq;
|
|
6197
|
-
if (fbq)
|
|
6204
|
+
if (!fbq) {
|
|
6205
|
+
console.debug("[MetaPixel] Lead skipped \u2014 fbq not loaded", { eventId });
|
|
6206
|
+
return;
|
|
6207
|
+
}
|
|
6208
|
+
console.debug("[MetaPixel] Lead", { eventId });
|
|
6209
|
+
fbq("track", "Lead", {}, { eventID: eventId });
|
|
6198
6210
|
}
|
|
6199
6211
|
|
|
6200
6212
|
// src/next/contexts/form-definitions.tsx
|
|
6201
6213
|
import React19, { createContext as createContext9, useContext as useContext10 } from "react";
|
|
6202
6214
|
var FormDefinitionsContext = createContext9({
|
|
6203
6215
|
leadFormDefinition: null,
|
|
6204
|
-
jobApplicationFormDefinition: null
|
|
6216
|
+
jobApplicationFormDefinition: null,
|
|
6217
|
+
marketingListSignupFormDefinition: null
|
|
6205
6218
|
});
|
|
6206
6219
|
function useFormDefinitions() {
|
|
6207
6220
|
return useContext10(FormDefinitionsContext);
|
|
@@ -8536,8 +8549,9 @@ function HeaderNavigation2({
|
|
|
8536
8549
|
React36.useEffect(() => {
|
|
8537
8550
|
const handleScroll = () => {
|
|
8538
8551
|
setIsScrolled(window.scrollY > 10);
|
|
8552
|
+
setActiveDropdown(null);
|
|
8539
8553
|
};
|
|
8540
|
-
window.addEventListener("scroll", handleScroll);
|
|
8554
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
8541
8555
|
return () => window.removeEventListener("scroll", handleScroll);
|
|
8542
8556
|
}, []);
|
|
8543
8557
|
React36.useEffect(() => {
|
|
@@ -8569,6 +8583,8 @@ function HeaderNavigation2({
|
|
|
8569
8583
|
setDropdownTop(rect.bottom);
|
|
8570
8584
|
}
|
|
8571
8585
|
setActiveDropdown(item.label);
|
|
8586
|
+
} else {
|
|
8587
|
+
setActiveDropdown(null);
|
|
8572
8588
|
}
|
|
8573
8589
|
}, [cancelCloseTimeout]);
|
|
8574
8590
|
const handleMouseLeave = useCallback5(() => {
|
|
@@ -17646,6 +17662,8 @@ function HeaderNavigation3({
|
|
|
17646
17662
|
cancelCloseTimeout();
|
|
17647
17663
|
if (item.children && item.children.length > 0) {
|
|
17648
17664
|
setActiveDropdown(item.label);
|
|
17665
|
+
} else {
|
|
17666
|
+
setActiveDropdown(null);
|
|
17649
17667
|
}
|
|
17650
17668
|
}, [cancelCloseTimeout]);
|
|
17651
17669
|
const handleMouseLeave = useCallback8(() => {
|
|
@@ -18436,6 +18454,8 @@ function HeaderNavigation4({
|
|
|
18436
18454
|
cancelCloseTimeout();
|
|
18437
18455
|
if (item.children && item.children.length > 0) {
|
|
18438
18456
|
setActiveDropdown(item.label);
|
|
18457
|
+
} else {
|
|
18458
|
+
setActiveDropdown(null);
|
|
18439
18459
|
}
|
|
18440
18460
|
}, [cancelCloseTimeout]);
|
|
18441
18461
|
const handleMouseLeave = useCallback9(() => {
|
|
@@ -19138,12 +19158,90 @@ var FeatureTabHorizontal = ({ title, subtitle, footer, isCurrent }) => /* @__PUR
|
|
|
19138
19158
|
footer
|
|
19139
19159
|
);
|
|
19140
19160
|
|
|
19161
|
+
// src/design_system/sections/email-signup-section.tsx
|
|
19162
|
+
import React60, { useRef as useRef18, useState as useState30 } from "react";
|
|
19163
|
+
var EmailSignupSection = ({
|
|
19164
|
+
title = "Stay in the loop",
|
|
19165
|
+
subtitle = "Subscribe to our newsletter for updates, tips, and exclusive offers.",
|
|
19166
|
+
buttonText = "Subscribe",
|
|
19167
|
+
successMessage = "You're subscribed! Thank you.",
|
|
19168
|
+
formDefinition
|
|
19169
|
+
}) => {
|
|
19170
|
+
const { marketingListSignupFormDefinition } = useFormDefinitions();
|
|
19171
|
+
const resolvedFormDefinition = formDefinition != null ? formDefinition : marketingListSignupFormDefinition;
|
|
19172
|
+
const [isSubmitting, setIsSubmitting] = useState30(false);
|
|
19173
|
+
const [submitStatus, setSubmitStatus] = useState30("idle");
|
|
19174
|
+
const [statusMessage, setStatusMessage] = useState30("");
|
|
19175
|
+
const formRef = useRef18(null);
|
|
19176
|
+
const handleSubmit = async (e) => {
|
|
19177
|
+
var _a;
|
|
19178
|
+
e.preventDefault();
|
|
19179
|
+
setIsSubmitting(true);
|
|
19180
|
+
setSubmitStatus("idle");
|
|
19181
|
+
setStatusMessage("");
|
|
19182
|
+
const formData = new FormData(e.currentTarget);
|
|
19183
|
+
const data = { formType: "marketing_list_signup" };
|
|
19184
|
+
formData.forEach((value, key) => {
|
|
19185
|
+
if (key.endsWith("_prefix")) return;
|
|
19186
|
+
if (typeof value === "string") data[key] = value;
|
|
19187
|
+
});
|
|
19188
|
+
try {
|
|
19189
|
+
const response = await fetch("/api/form/", {
|
|
19190
|
+
method: "POST",
|
|
19191
|
+
headers: { "Content-Type": "application/json" },
|
|
19192
|
+
body: JSON.stringify(data)
|
|
19193
|
+
});
|
|
19194
|
+
const result = await response.json();
|
|
19195
|
+
if (result.success) {
|
|
19196
|
+
setSubmitStatus("success");
|
|
19197
|
+
setStatusMessage(result.message || successMessage);
|
|
19198
|
+
(_a = formRef.current) == null ? void 0 : _a.reset();
|
|
19199
|
+
setTimeout(() => setSubmitStatus("idle"), 6e3);
|
|
19200
|
+
} else {
|
|
19201
|
+
setSubmitStatus("error");
|
|
19202
|
+
setStatusMessage(result.error || "Something went wrong. Please try again.");
|
|
19203
|
+
}
|
|
19204
|
+
} catch (e2) {
|
|
19205
|
+
setSubmitStatus("error");
|
|
19206
|
+
setStatusMessage("Network error. Please try again later.");
|
|
19207
|
+
}
|
|
19208
|
+
setIsSubmitting(false);
|
|
19209
|
+
};
|
|
19210
|
+
if (!resolvedFormDefinition) return null;
|
|
19211
|
+
return /* @__PURE__ */ React60.createElement("section", { className: "bg-secondary py-16 md:py-20" }, /* @__PURE__ */ React60.createElement("div", { className: "mx-auto max-w-container px-4 md:px-8" }, /* @__PURE__ */ React60.createElement("div", { className: "mx-auto max-w-xl text-center" }, /* @__PURE__ */ React60.createElement("h2", { className: "font-display text-display-sm font-semibold text-primary md:text-display-md" }, title), subtitle && /* @__PURE__ */ React60.createElement("p", { className: "mt-4 font-body text-lg text-tertiary" }, subtitle), /* @__PURE__ */ React60.createElement(
|
|
19212
|
+
Form2,
|
|
19213
|
+
{
|
|
19214
|
+
ref: formRef,
|
|
19215
|
+
onSubmit: handleSubmit,
|
|
19216
|
+
className: "mt-8 flex flex-col gap-6 text-left"
|
|
19217
|
+
},
|
|
19218
|
+
/* @__PURE__ */ React60.createElement(DynamicFormFields, { form: resolvedFormDefinition }),
|
|
19219
|
+
submitStatus === "success" && /* @__PURE__ */ React60.createElement("div", { className: "rounded-lg bg-success-50 p-4 text-success-700" }, statusMessage || successMessage),
|
|
19220
|
+
submitStatus === "error" && /* @__PURE__ */ React60.createElement("div", { className: "rounded-lg bg-error-50 p-4 text-error-700" }, statusMessage),
|
|
19221
|
+
/* @__PURE__ */ React60.createElement(
|
|
19222
|
+
Button2,
|
|
19223
|
+
{
|
|
19224
|
+
type: "submit",
|
|
19225
|
+
color: "primary",
|
|
19226
|
+
size: "xl",
|
|
19227
|
+
isDisabled: isSubmitting,
|
|
19228
|
+
isLoading: isSubmitting,
|
|
19229
|
+
className: "w-full"
|
|
19230
|
+
},
|
|
19231
|
+
isSubmitting ? "Subscribing..." : buttonText
|
|
19232
|
+
)
|
|
19233
|
+
))));
|
|
19234
|
+
};
|
|
19235
|
+
|
|
19141
19236
|
// src/design_system/sections/service-menu-section.tsx
|
|
19142
|
-
import
|
|
19237
|
+
import React61, { useState as useState32, useEffect as useEffect12, useCallback as useCallback10 } from "react";
|
|
19143
19238
|
import { createPortal } from "react-dom";
|
|
19144
|
-
|
|
19239
|
+
|
|
19240
|
+
// src/lib/hooks/use-image-cycle.ts
|
|
19241
|
+
import { useState as useState31, useEffect as useEffect11, useMemo as useMemo8 } from "react";
|
|
19145
19242
|
var CYCLE_INTERVAL_MIN_MS = 6e3;
|
|
19146
19243
|
var CYCLE_INTERVAL_MAX_MS = 8e3;
|
|
19244
|
+
var CROSSFADE_DURATION_MS = 600;
|
|
19147
19245
|
function seedToUnit(seed) {
|
|
19148
19246
|
let h = 2166136261 >>> 0;
|
|
19149
19247
|
for (let i = 0; i < seed.length; i++) {
|
|
@@ -19152,18 +19250,6 @@ function seedToUnit(seed) {
|
|
|
19152
19250
|
}
|
|
19153
19251
|
return (h >>> 0) / 4294967296;
|
|
19154
19252
|
}
|
|
19155
|
-
function getActivePublicOffers(offers) {
|
|
19156
|
-
if (!Array.isArray(offers) || offers.length === 0) return [];
|
|
19157
|
-
return offers.filter((o) => o.active !== false && o.expired !== true);
|
|
19158
|
-
}
|
|
19159
|
-
function photoAttachmentDisplayUrl(pa) {
|
|
19160
|
-
var _a, _b;
|
|
19161
|
-
return ((_a = pa.photo) == null ? void 0 : _a.large_url) || ((_b = pa.photo) == null ? void 0 : _b.medium_url);
|
|
19162
|
-
}
|
|
19163
|
-
function photoAttachmentAlt(pa) {
|
|
19164
|
-
var _a, _b;
|
|
19165
|
-
return ((_a = pa.photo) == null ? void 0 : _a.alt_text) || ((_b = pa.photo) == null ? void 0 : _b.title) || "";
|
|
19166
|
-
}
|
|
19167
19253
|
function shuffleWithSeed(array, seed) {
|
|
19168
19254
|
if (array.length <= 1) return array;
|
|
19169
19255
|
const arr = [...array];
|
|
@@ -19182,39 +19268,25 @@ function shuffleWithSeed(array, seed) {
|
|
|
19182
19268
|
}
|
|
19183
19269
|
return arr;
|
|
19184
19270
|
}
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
return
|
|
19188
|
-
() => {
|
|
19189
|
-
const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];
|
|
19190
|
-
if (arr.length === 0) return [];
|
|
19191
|
-
const shuffled = shuffleWithSeed(arr, seed);
|
|
19192
|
-
return shuffled.map((pa) => {
|
|
19193
|
-
var _a;
|
|
19194
|
-
return { url: (_a = photoAttachmentDisplayUrl(pa)) != null ? _a : "", alt: photoAttachmentAlt(pa) };
|
|
19195
|
-
}).filter((x) => x.url);
|
|
19196
|
-
},
|
|
19197
|
-
[photoAttachments, seed]
|
|
19198
|
-
);
|
|
19271
|
+
function photoUrlFromAttachment(pa) {
|
|
19272
|
+
var _a, _b, _c;
|
|
19273
|
+
return ((_a = pa.photo) == null ? void 0 : _a.large_url) || ((_b = pa.photo) == null ? void 0 : _b.medium_url) || ((_c = pa.photo) == null ? void 0 : _c.thumbnail_url);
|
|
19199
19274
|
}
|
|
19200
|
-
function
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
})
|
|
19213
|
-
|
|
19214
|
-
const
|
|
19215
|
-
const list = useCycledPhotoList(photoAttachments, seed);
|
|
19216
|
-
const [currentIndex, setCurrentIndex] = useState30(0);
|
|
19217
|
-
const [transitioning, setTransitioning] = useState30(false);
|
|
19275
|
+
function photoAltFromAttachment(pa) {
|
|
19276
|
+
var _a, _b;
|
|
19277
|
+
return ((_a = pa.photo) == null ? void 0 : _a.alt_text) || ((_b = pa.photo) == null ? void 0 : _b.title) || "";
|
|
19278
|
+
}
|
|
19279
|
+
function useImageCycle(photoAttachments, seed) {
|
|
19280
|
+
const list = useMemo8(() => {
|
|
19281
|
+
const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];
|
|
19282
|
+
if (arr.length === 0) return [];
|
|
19283
|
+
return shuffleWithSeed(arr, seed).map((pa) => {
|
|
19284
|
+
var _a;
|
|
19285
|
+
return { url: (_a = photoUrlFromAttachment(pa)) != null ? _a : "", alt: photoAltFromAttachment(pa) };
|
|
19286
|
+
}).filter((x) => x.url);
|
|
19287
|
+
}, [photoAttachments, seed]);
|
|
19288
|
+
const [currentIndex, setCurrentIndex] = useState31(0);
|
|
19289
|
+
const [transitioning, setTransitioning] = useState31(false);
|
|
19218
19290
|
const intervalMs = useMemo8(
|
|
19219
19291
|
() => CYCLE_INTERVAL_MIN_MS + Math.floor(seedToUnit(seed) * (CYCLE_INTERVAL_MAX_MS - CYCLE_INTERVAL_MIN_MS + 1)),
|
|
19220
19292
|
[seed]
|
|
@@ -19233,24 +19305,48 @@ function GridCardWithImage({
|
|
|
19233
19305
|
return () => clearTimeout(t);
|
|
19234
19306
|
}, [transitioning, list.length]);
|
|
19235
19307
|
const nextIndex = list.length > 1 ? (currentIndex + 1) % list.length : 0;
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19308
|
+
return { list, currentIndex, nextIndex, transitioning };
|
|
19309
|
+
}
|
|
19310
|
+
|
|
19311
|
+
// src/design_system/sections/service-menu-section.tsx
|
|
19312
|
+
var SERVICE_MENU_MODAL_ROOT_ID = "service-menu-modal-root";
|
|
19313
|
+
function getActivePublicOffers(offers) {
|
|
19314
|
+
if (!Array.isArray(offers) || offers.length === 0) return [];
|
|
19315
|
+
return offers.filter((o) => o.active !== false && o.expired !== true);
|
|
19316
|
+
}
|
|
19317
|
+
function GridCardWithImage({
|
|
19318
|
+
photoAttachments,
|
|
19319
|
+
fallbackId,
|
|
19320
|
+
fallbackAlt,
|
|
19321
|
+
title,
|
|
19322
|
+
subtitle,
|
|
19323
|
+
children,
|
|
19324
|
+
onClick,
|
|
19325
|
+
websitePhotos,
|
|
19326
|
+
companyInformation,
|
|
19327
|
+
cycleSeed,
|
|
19328
|
+
hasSpecial
|
|
19329
|
+
}) {
|
|
19330
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
19331
|
+
const seed = cycleSeed != null ? cycleSeed : String(fallbackId);
|
|
19332
|
+
const { list, currentIndex, nextIndex, transitioning } = useImageCycle(photoAttachments, seed);
|
|
19333
|
+
const displayAlt = ((_a = list[currentIndex]) == null ? void 0 : _a.alt) || fallbackAlt;
|
|
19334
|
+
const singleUrl = (_b = list[0]) == null ? void 0 : _b.url;
|
|
19335
|
+
return /* @__PURE__ */ React61.createElement(
|
|
19240
19336
|
"button",
|
|
19241
19337
|
{
|
|
19242
19338
|
type: "button",
|
|
19243
19339
|
onClick,
|
|
19244
19340
|
className: "flex flex-col h-full w-full text-left group block rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-brand-accent focus-visible:ring-offset-2"
|
|
19245
19341
|
},
|
|
19246
|
-
/* @__PURE__ */
|
|
19342
|
+
/* @__PURE__ */ React61.createElement("div", { className: "w-full h-36 overflow-hidden rounded-lg mb-3 relative" }, hasSpecial ? /* @__PURE__ */ React61.createElement(
|
|
19247
19343
|
"span",
|
|
19248
19344
|
{
|
|
19249
19345
|
className: "absolute top-2 right-2 z-10 rounded-full border border-brand-accent/40 bg-secondary/95 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-accent shadow-sm backdrop-blur-sm",
|
|
19250
19346
|
"aria-label": "Has special offer"
|
|
19251
19347
|
},
|
|
19252
19348
|
"Special"
|
|
19253
|
-
) : null, list.length === 0 ? /* @__PURE__ */
|
|
19349
|
+
) : null, list.length === 0 ? /* @__PURE__ */ React61.createElement(
|
|
19254
19350
|
PhotoWithFallback2,
|
|
19255
19351
|
{
|
|
19256
19352
|
item: void 0,
|
|
@@ -19262,7 +19358,7 @@ function GridCardWithImage({
|
|
|
19262
19358
|
}
|
|
19263
19359
|
) : list.length === 1 && singleUrl ? (
|
|
19264
19360
|
// eslint-disable-next-line @next/next/no-img-element -- dynamic API URLs; next/image not configured for external host
|
|
19265
|
-
/* @__PURE__ */
|
|
19361
|
+
/* @__PURE__ */ React61.createElement(
|
|
19266
19362
|
"img",
|
|
19267
19363
|
{
|
|
19268
19364
|
src: singleUrl,
|
|
@@ -19270,37 +19366,37 @@ function GridCardWithImage({
|
|
|
19270
19366
|
className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
19271
19367
|
}
|
|
19272
19368
|
)
|
|
19273
|
-
) : /* @__PURE__ */
|
|
19369
|
+
) : /* @__PURE__ */ React61.createElement(React61.Fragment, null, /* @__PURE__ */ React61.createElement(
|
|
19274
19370
|
"div",
|
|
19275
19371
|
{
|
|
19276
19372
|
className: `absolute inset-0 ${transitioning ? "transition-opacity duration-[600ms] ease-in-out" : "transition-none"}`,
|
|
19277
19373
|
style: { opacity: transitioning ? 0 : 1 }
|
|
19278
19374
|
},
|
|
19279
|
-
/* @__PURE__ */
|
|
19375
|
+
/* @__PURE__ */ React61.createElement(
|
|
19280
19376
|
"img",
|
|
19281
19377
|
{
|
|
19282
|
-
src: (
|
|
19283
|
-
alt: (
|
|
19378
|
+
src: (_c = list[currentIndex]) == null ? void 0 : _c.url,
|
|
19379
|
+
alt: (_e = (_d = list[currentIndex]) == null ? void 0 : _d.alt) != null ? _e : displayAlt,
|
|
19284
19380
|
className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
19285
19381
|
}
|
|
19286
19382
|
)
|
|
19287
|
-
), /* @__PURE__ */
|
|
19383
|
+
), /* @__PURE__ */ React61.createElement(
|
|
19288
19384
|
"div",
|
|
19289
19385
|
{
|
|
19290
19386
|
className: `absolute inset-0 ${transitioning ? "transition-opacity duration-[600ms] ease-in-out" : "transition-none"}`,
|
|
19291
19387
|
style: { opacity: transitioning ? 1 : 0 }
|
|
19292
19388
|
},
|
|
19293
|
-
/* @__PURE__ */
|
|
19389
|
+
/* @__PURE__ */ React61.createElement(
|
|
19294
19390
|
"img",
|
|
19295
19391
|
{
|
|
19296
|
-
src: (
|
|
19297
|
-
alt: (
|
|
19392
|
+
src: (_f = list[nextIndex]) == null ? void 0 : _f.url,
|
|
19393
|
+
alt: (_h = (_g = list[nextIndex]) == null ? void 0 : _g.alt) != null ? _h : displayAlt,
|
|
19298
19394
|
className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
19299
19395
|
}
|
|
19300
19396
|
)
|
|
19301
19397
|
))),
|
|
19302
|
-
subtitle && /* @__PURE__ */
|
|
19303
|
-
/* @__PURE__ */
|
|
19398
|
+
subtitle && /* @__PURE__ */ React61.createElement("p", { className: "text-xs font-medium text-fg-secondary uppercase tracking-wide line-clamp-1" }, subtitle),
|
|
19399
|
+
/* @__PURE__ */ React61.createElement("h4", { className: "font-display text-base font-normal text-fg-primary mt-1 group-hover:underline line-clamp-2" }, title),
|
|
19304
19400
|
children
|
|
19305
19401
|
);
|
|
19306
19402
|
}
|
|
@@ -19310,9 +19406,9 @@ function CarouselRow({
|
|
|
19310
19406
|
renderItem
|
|
19311
19407
|
}) {
|
|
19312
19408
|
if (!(items == null ? void 0 : items.length)) return null;
|
|
19313
|
-
return /* @__PURE__ */
|
|
19409
|
+
return /* @__PURE__ */ React61.createElement("div", { className: "mb-12 last:mb-0" }, /* @__PURE__ */ React61.createElement(Carousel.Root, { opts: { align: "start", loop: true } }, /* @__PURE__ */ React61.createElement("div", { className: "flex items-center justify-between gap-4 mb-2" }, /* @__PURE__ */ React61.createElement("h3", { className: "font-display text-2xl font-normal text-fg-primary md:text-3xl" }, rowTitle), /* @__PURE__ */ React61.createElement("div", { className: "flex gap-2 flex-shrink-0" }, /* @__PURE__ */ React61.createElement(Carousel.PrevTrigger, { className: "rounded-full p-1.5 border border-secondary hover:bg-primary_hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed" }, /* @__PURE__ */ React61.createElement("svg", { className: "w-5 h-5 text-fg-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true }, /* @__PURE__ */ React61.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }))), /* @__PURE__ */ React61.createElement(Carousel.NextTrigger, { className: "rounded-full p-1.5 border border-secondary hover:bg-primary_hover transition-colors disabled:opacity-30 disabled:cursor-not-allowed" }, /* @__PURE__ */ React61.createElement("svg", { className: "w-5 h-5 text-fg-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": true }, /* @__PURE__ */ React61.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }))))), /* @__PURE__ */ React61.createElement(Carousel.Content, { className: "-ml-3" }, items.map((item, index) => {
|
|
19314
19410
|
var _a;
|
|
19315
|
-
return /* @__PURE__ */
|
|
19411
|
+
return /* @__PURE__ */ React61.createElement(Carousel.Item, { key: (_a = item.id) != null ? _a : index, className: "pl-3 basis-[70%] sm:basis-1/2 md:basis-1/3 lg:basis-1/4" }, renderItem(item, index));
|
|
19316
19412
|
}))));
|
|
19317
19413
|
}
|
|
19318
19414
|
function GridRow({
|
|
@@ -19321,9 +19417,9 @@ function GridRow({
|
|
|
19321
19417
|
renderItem
|
|
19322
19418
|
}) {
|
|
19323
19419
|
if (!(items == null ? void 0 : items.length)) return null;
|
|
19324
|
-
return /* @__PURE__ */
|
|
19420
|
+
return /* @__PURE__ */ React61.createElement("div", { className: "mb-12 last:mb-0" }, /* @__PURE__ */ React61.createElement("h3", { className: "font-display text-2xl font-normal text-fg-primary mb-3 md:text-3xl" }, rowTitle), /* @__PURE__ */ React61.createElement("div", { className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" }, items.map((item, index) => {
|
|
19325
19421
|
var _a;
|
|
19326
|
-
return /* @__PURE__ */
|
|
19422
|
+
return /* @__PURE__ */ React61.createElement("div", { key: (_a = item.id) != null ? _a : index, className: "min-w-0 flex" }, renderItem(item, index));
|
|
19327
19423
|
})));
|
|
19328
19424
|
}
|
|
19329
19425
|
function MenuBlock({
|
|
@@ -19333,27 +19429,27 @@ function MenuBlock({
|
|
|
19333
19429
|
layout: layout2
|
|
19334
19430
|
}) {
|
|
19335
19431
|
if (!(items == null ? void 0 : items.length)) return null;
|
|
19336
|
-
return layout2 === "carousel" ? /* @__PURE__ */
|
|
19432
|
+
return layout2 === "carousel" ? /* @__PURE__ */ React61.createElement(CarouselRow, { rowTitle, items, renderItem }) : /* @__PURE__ */ React61.createElement(GridRow, { rowTitle, items, renderItem });
|
|
19337
19433
|
}
|
|
19338
19434
|
function formatPriceCents(cents) {
|
|
19339
19435
|
if (cents == null) return null;
|
|
19340
19436
|
return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(cents / 100);
|
|
19341
19437
|
}
|
|
19342
19438
|
function ModalSection({ title, children }) {
|
|
19343
|
-
return /* @__PURE__ */
|
|
19439
|
+
return /* @__PURE__ */ React61.createElement("div", null, /* @__PURE__ */ React61.createElement("h3", { className: "font-display text-sm font-semibold text-fg-primary uppercase tracking-wide mb-2" }, title), children);
|
|
19344
19440
|
}
|
|
19345
19441
|
function ActiveOffersCallout({ offers }) {
|
|
19346
19442
|
if (!offers.length) return null;
|
|
19347
|
-
return /* @__PURE__ */
|
|
19443
|
+
return /* @__PURE__ */ React61.createElement("div", { className: "rounded-xl border border-secondary bg-secondary/25 p-4 md:p-5 space-y-3 ring-1 ring-brand-accent/25" }, /* @__PURE__ */ React61.createElement("p", { className: "text-xs font-semibold uppercase tracking-wide text-brand-accent" }, "Current specials"), /* @__PURE__ */ React61.createElement("ul", { className: "space-y-3 list-none m-0 p-0" }, offers.map((o) => /* @__PURE__ */ React61.createElement(
|
|
19348
19444
|
"li",
|
|
19349
19445
|
{
|
|
19350
19446
|
key: o.id,
|
|
19351
19447
|
className: "rounded-lg border border-secondary border-l-[3px] border-l-brand-accent bg-primary_hover/30 p-4"
|
|
19352
19448
|
},
|
|
19353
|
-
/* @__PURE__ */
|
|
19354
|
-
o.value_terms ? /* @__PURE__ */
|
|
19355
|
-
o.description ? /* @__PURE__ */
|
|
19356
|
-
o.expires_at ? /* @__PURE__ */
|
|
19449
|
+
/* @__PURE__ */ React61.createElement("p", { className: "font-display text-base font-medium text-fg-primary" }, o.name),
|
|
19450
|
+
o.value_terms ? /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-brand-accent mt-1 font-medium" }, o.value_terms) : null,
|
|
19451
|
+
o.description ? /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-fg-secondary mt-2 leading-relaxed" }, o.description) : null,
|
|
19452
|
+
o.expires_at ? /* @__PURE__ */ React61.createElement("p", { className: "text-xs text-tertiary mt-2" }, "Ends ", new Date(o.expires_at).toLocaleDateString(void 0, { dateStyle: "medium" })) : null
|
|
19357
19453
|
))));
|
|
19358
19454
|
}
|
|
19359
19455
|
function DetailModalContent({
|
|
@@ -19366,20 +19462,20 @@ function DetailModalContent({
|
|
|
19366
19462
|
const categoryLine = ((_a = p.category_names) == null ? void 0 : _a.length) ? p.category_names.join(" | ") : null;
|
|
19367
19463
|
const descriptionMarkdown2 = p.description_markdown || p.summary || p.first_service_description_markdown;
|
|
19368
19464
|
const packageSpecials = getActivePublicOffers(p.offers);
|
|
19369
|
-
return /* @__PURE__ */
|
|
19465
|
+
return /* @__PURE__ */ React61.createElement("div", { className: "space-y-6" }, categoryLine && /* @__PURE__ */ React61.createElement("p", { className: "text-sm font-medium text-fg-secondary uppercase tracking-wide" }, categoryLine), /* @__PURE__ */ React61.createElement(ActiveOffersCallout, { offers: packageSpecials }), descriptionMarkdown2 && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Package details" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: descriptionMarkdown2 }))), p.package_items && p.package_items.length > 0 && /* @__PURE__ */ React61.createElement(ModalSection, { title: "What's included" }, /* @__PURE__ */ React61.createElement("ul", { className: "space-y-4" }, p.package_items.map((pi, i) => {
|
|
19370
19466
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
19371
19467
|
const fullItem = ((_a2 = pi.service_item) == null ? void 0 : _a2.id) != null ? serviceItems.find((si2) => si2.id === pi.service_item.id) : null;
|
|
19372
19468
|
const name = (_d = (_c = fullItem == null ? void 0 : fullItem.name) != null ? _c : (_b = pi.service_item) == null ? void 0 : _b.name) != null ? _d : "Item";
|
|
19373
19469
|
const desc = (_g = (_e = fullItem == null ? void 0 : fullItem.summary) != null ? _e : fullItem == null ? void 0 : fullItem.description_markdown) != null ? _g : (_f = pi.service_item) == null ? void 0 : _f.summary;
|
|
19374
|
-
return /* @__PURE__ */
|
|
19375
|
-
}))), p.pricing_info && /* @__PURE__ */
|
|
19470
|
+
return /* @__PURE__ */ React61.createElement("li", { key: (_i = (_h = pi.service_item) == null ? void 0 : _h.id) != null ? _i : i, className: "border border-secondary rounded-lg p-4 bg-secondary/20" }, /* @__PURE__ */ React61.createElement("p", { className: "font-display font-medium text-fg-primary" }, pi.quantity > 1 && `${pi.quantity}\xD7 `, name), desc && /* @__PURE__ */ React61.createElement("div", { className: "mt-2 prose prose-sm font-body text-fg-secondary max-w-none" }, typeof desc === "string" && !desc.includes("\n") && !desc.match(/[#*\[\]]/) ? /* @__PURE__ */ React61.createElement("p", null, desc) : /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: desc })));
|
|
19471
|
+
}))), p.pricing_info && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Pricing" }, /* @__PURE__ */ React61.createElement("div", { className: "rounded-lg border border-secondary bg-secondary/40 p-4" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: p.pricing_info })))));
|
|
19376
19472
|
}
|
|
19377
19473
|
const si = detail.item;
|
|
19378
19474
|
const priceStr = formatPriceCents(si.price_cents);
|
|
19379
19475
|
const summary = si.summary || si.service_summary;
|
|
19380
19476
|
const descriptionMarkdown = si.description_markdown || si.service_description_markdown;
|
|
19381
19477
|
const itemSpecials = getActivePublicOffers(si.offers);
|
|
19382
|
-
return /* @__PURE__ */
|
|
19478
|
+
return /* @__PURE__ */ React61.createElement("div", { className: "space-y-6" }, si.service_name && /* @__PURE__ */ React61.createElement("p", { className: "text-sm font-medium text-fg-secondary uppercase tracking-wide" }, si.service_name), /* @__PURE__ */ React61.createElement(ActiveOffersCallout, { offers: itemSpecials }), priceStr && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Price" }, /* @__PURE__ */ React61.createElement("p", { className: "font-display text-lg font-normal text-fg-primary" }, priceStr), si.duration_minutes != null && si.duration_minutes > 0 && /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-fg-secondary mt-1" }, "Duration: ", si.duration_minutes, " min")), summary && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Overview" }, /* @__PURE__ */ React61.createElement("p", { className: "font-body text-fg-primary" }, summary)), descriptionMarkdown && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Full description" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: descriptionMarkdown }))), si.pricing_info && /* @__PURE__ */ React61.createElement(ModalSection, { title: "Pricing details" }, /* @__PURE__ */ React61.createElement("div", { className: "rounded-lg border border-secondary bg-secondary/40 p-4" }, /* @__PURE__ */ React61.createElement("div", { className: "prose prose-sm font-body text-fg-primary max-w-none" }, /* @__PURE__ */ React61.createElement(MarkdownRenderer2, { content: si.pricing_info })))));
|
|
19383
19479
|
}
|
|
19384
19480
|
function ServiceMenuSection({
|
|
19385
19481
|
title = "Service Menu",
|
|
@@ -19393,20 +19489,20 @@ function ServiceMenuSection({
|
|
|
19393
19489
|
servicesRowTitle = "Treatments",
|
|
19394
19490
|
variant = "section"
|
|
19395
19491
|
}) {
|
|
19396
|
-
const packageList =
|
|
19492
|
+
const packageList = React61.useMemo(
|
|
19397
19493
|
() => Array.isArray(packages) ? packages : [],
|
|
19398
19494
|
[packages]
|
|
19399
19495
|
);
|
|
19400
|
-
const serviceList =
|
|
19496
|
+
const serviceList = React61.useMemo(
|
|
19401
19497
|
() => Array.isArray(services) ? services : [],
|
|
19402
19498
|
[services]
|
|
19403
19499
|
);
|
|
19404
|
-
const serviceItemIdToService =
|
|
19500
|
+
const serviceItemIdToService = React61.useMemo(() => {
|
|
19405
19501
|
const m = /* @__PURE__ */ new Map();
|
|
19406
19502
|
serviceList.forEach((s) => (s.service_items || []).forEach((si) => m.set(si.id, s)));
|
|
19407
19503
|
return m;
|
|
19408
19504
|
}, [serviceList]);
|
|
19409
|
-
const packagesForMenu =
|
|
19505
|
+
const packagesForMenu = React61.useMemo(
|
|
19410
19506
|
() => packageList.map((pkg) => {
|
|
19411
19507
|
var _a, _b, _c;
|
|
19412
19508
|
const category_names = [
|
|
@@ -19436,9 +19532,9 @@ function ServiceMenuSection({
|
|
|
19436
19532
|
const isPage = variant === "page";
|
|
19437
19533
|
const packagesLayout = isPage ? "grid" : packagesForMenu.length >= CAROUSEL_MIN ? "carousel" : "grid";
|
|
19438
19534
|
const serviceItemsLayout = isPage ? "grid" : serviceItemsForMenu.length >= CAROUSEL_MIN ? "carousel" : "grid";
|
|
19439
|
-
const [detailItem, setDetailItem] =
|
|
19440
|
-
const [portalTarget, setPortalTarget] =
|
|
19441
|
-
|
|
19535
|
+
const [detailItem, setDetailItem] = useState32(null);
|
|
19536
|
+
const [portalTarget, setPortalTarget] = useState32(null);
|
|
19537
|
+
useEffect12(() => {
|
|
19442
19538
|
let el = document.getElementById(SERVICE_MENU_MODAL_ROOT_ID);
|
|
19443
19539
|
if (!el) {
|
|
19444
19540
|
el = document.createElement("div");
|
|
@@ -19459,7 +19555,7 @@ function ServiceMenuSection({
|
|
|
19459
19555
|
const plainDesc = cardDesc ? cardDesc.replace(/[#*`\[\]]/g, "").replace(/\n+/g, " ").trim() : "";
|
|
19460
19556
|
const descText = plainDesc.length > 120 ? plainDesc.slice(0, 120) + "\u2026" : plainDesc;
|
|
19461
19557
|
const hasSpecial = getActivePublicOffers(pkg.offers).length > 0;
|
|
19462
|
-
return /* @__PURE__ */
|
|
19558
|
+
return /* @__PURE__ */ React61.createElement(
|
|
19463
19559
|
GridCardWithImage,
|
|
19464
19560
|
{
|
|
19465
19561
|
photoAttachments: pkg.photo_attachments,
|
|
@@ -19473,7 +19569,7 @@ function ServiceMenuSection({
|
|
|
19473
19569
|
cycleSeed: `pkg-${pkg.id}-${index}`,
|
|
19474
19570
|
hasSpecial
|
|
19475
19571
|
},
|
|
19476
|
-
descText && /* @__PURE__ */
|
|
19572
|
+
descText && /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-tertiary mt-1 line-clamp-2" }, descText)
|
|
19477
19573
|
);
|
|
19478
19574
|
},
|
|
19479
19575
|
[websitePhotos, companyInformation]
|
|
@@ -19481,7 +19577,7 @@ function ServiceMenuSection({
|
|
|
19481
19577
|
const renderServiceItemCard = useCallback10(
|
|
19482
19578
|
(si, index) => {
|
|
19483
19579
|
var _a, _b, _c, _d;
|
|
19484
|
-
return /* @__PURE__ */
|
|
19580
|
+
return /* @__PURE__ */ React61.createElement(
|
|
19485
19581
|
GridCardWithImage,
|
|
19486
19582
|
{
|
|
19487
19583
|
photoAttachments: si.photo_attachments,
|
|
@@ -19495,13 +19591,13 @@ function ServiceMenuSection({
|
|
|
19495
19591
|
cycleSeed: `service-item-${si.id}-${index}`,
|
|
19496
19592
|
hasSpecial: getActivePublicOffers(si.offers).length > 0
|
|
19497
19593
|
},
|
|
19498
|
-
formatPriceCents(si.price_cents) && /* @__PURE__ */
|
|
19594
|
+
formatPriceCents(si.price_cents) && /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm font-medium text-fg-primary mt-1" }, formatPriceCents(si.price_cents)),
|
|
19499
19595
|
(() => {
|
|
19500
19596
|
const desc = si.summary || si.description_markdown || si.service_summary || si.service_description_markdown;
|
|
19501
19597
|
if (!desc) return null;
|
|
19502
19598
|
const plain = desc.replace(/[#*`\[\]]/g, "").replace(/\n+/g, " ").trim();
|
|
19503
19599
|
const text = plain.length > 120 ? plain.slice(0, 120) + "\u2026" : plain;
|
|
19504
|
-
return /* @__PURE__ */
|
|
19600
|
+
return /* @__PURE__ */ React61.createElement("p", { className: "font-body text-sm text-tertiary mt-1 line-clamp-2" }, text);
|
|
19505
19601
|
})()
|
|
19506
19602
|
);
|
|
19507
19603
|
},
|
|
@@ -19509,7 +19605,7 @@ function ServiceMenuSection({
|
|
|
19509
19605
|
);
|
|
19510
19606
|
if (!hasAny) return null;
|
|
19511
19607
|
const modalTitle = detailItem ? detailItem.item.name : "";
|
|
19512
|
-
return /* @__PURE__ */
|
|
19608
|
+
return /* @__PURE__ */ React61.createElement("section", { className: variant === "page" ? "py-12 md:py-20" : "py-12 md:py-16" }, /* @__PURE__ */ React61.createElement("div", { className: "mx-auto max-w-container px-4 md:px-8" }, variant === "section" && /* @__PURE__ */ React61.createElement("div", { className: "mx-auto max-w-3xl text-center mb-12 md:mb-16" }, title && /* @__PURE__ */ React61.createElement("h2", { className: "font-display text-4xl font-normal text-fg-primary md:text-5xl" }, title), subtitle && /* @__PURE__ */ React61.createElement("p", { className: "mt-4 font-display text-lg leading-relaxed text-tertiary md:text-xl max-w-3xl mx-auto" }, subtitle)), /* @__PURE__ */ React61.createElement(
|
|
19513
19609
|
MenuBlock,
|
|
19514
19610
|
{
|
|
19515
19611
|
rowTitle: "Packages",
|
|
@@ -19517,7 +19613,7 @@ function ServiceMenuSection({
|
|
|
19517
19613
|
layout: packagesLayout,
|
|
19518
19614
|
renderItem: renderPackageCard
|
|
19519
19615
|
}
|
|
19520
|
-
), /* @__PURE__ */
|
|
19616
|
+
), /* @__PURE__ */ React61.createElement(
|
|
19521
19617
|
MenuBlock,
|
|
19522
19618
|
{
|
|
19523
19619
|
rowTitle: servicesRowTitle,
|
|
@@ -19525,8 +19621,8 @@ function ServiceMenuSection({
|
|
|
19525
19621
|
layout: serviceItemsLayout,
|
|
19526
19622
|
renderItem: renderServiceItemCard
|
|
19527
19623
|
}
|
|
19528
|
-
), variant === "section" && viewAllHref && viewAllText && /* @__PURE__ */
|
|
19529
|
-
/* @__PURE__ */
|
|
19624
|
+
), variant === "section" && viewAllHref && viewAllText && /* @__PURE__ */ React61.createElement("div", { className: "mt-12 text-center" }, /* @__PURE__ */ React61.createElement(Button2, { href: viewAllHref, color: "primary", size: "md" }, viewAllText))), portalTarget && detailItem && createPortal(
|
|
19625
|
+
/* @__PURE__ */ React61.createElement(
|
|
19530
19626
|
Modal,
|
|
19531
19627
|
{
|
|
19532
19628
|
isOpen: true,
|
|
@@ -19534,7 +19630,7 @@ function ServiceMenuSection({
|
|
|
19534
19630
|
title: modalTitle,
|
|
19535
19631
|
maxWidth: "2xl"
|
|
19536
19632
|
},
|
|
19537
|
-
/* @__PURE__ */
|
|
19633
|
+
/* @__PURE__ */ React61.createElement(
|
|
19538
19634
|
DetailModalContent,
|
|
19539
19635
|
{
|
|
19540
19636
|
detail: detailItem,
|
|
@@ -19554,9 +19650,9 @@ function createThemedExport2(componentName, BaseComponent) {
|
|
|
19554
19650
|
const { theme } = useTheme();
|
|
19555
19651
|
try {
|
|
19556
19652
|
const Component2 = getThemedComponent(componentName, theme);
|
|
19557
|
-
return
|
|
19653
|
+
return React62.createElement(Component2, props);
|
|
19558
19654
|
} catch (e) {
|
|
19559
|
-
return
|
|
19655
|
+
return React62.createElement(BaseComponent, props);
|
|
19560
19656
|
}
|
|
19561
19657
|
};
|
|
19562
19658
|
}
|
|
@@ -19599,108 +19695,65 @@ var HomeHeroComponent2 = createThemedExport2("home-hero-component", HomeHeroComp
|
|
|
19599
19695
|
// src/lib/actions.ts
|
|
19600
19696
|
var API_URL = process.env.API_URL || "http://localhost:3000/api/v1";
|
|
19601
19697
|
var API_KEY = process.env.API_KEY || "";
|
|
19602
|
-
|
|
19698
|
+
function extractFormFields(formData) {
|
|
19603
19699
|
var _a, _b, _c, _d, _e;
|
|
19700
|
+
return {
|
|
19701
|
+
firstName: ((_a = formData.get("firstName")) == null ? void 0 : _a.toString().trim()) || "",
|
|
19702
|
+
lastName: ((_b = formData.get("lastName")) == null ? void 0 : _b.toString().trim()) || "",
|
|
19703
|
+
email: ((_c = formData.get("email")) == null ? void 0 : _c.toString().trim()) || "",
|
|
19704
|
+
phone: ((_d = formData.get("phone")) == null ? void 0 : _d.toString().trim()) || "",
|
|
19705
|
+
message: ((_e = formData.get("message")) == null ? void 0 : _e.toString().trim()) || ""
|
|
19706
|
+
};
|
|
19707
|
+
}
|
|
19708
|
+
async function postFormSubmission(payload) {
|
|
19709
|
+
return fetch(`${API_URL}/public/form_submissions`, {
|
|
19710
|
+
method: "POST",
|
|
19711
|
+
headers: {
|
|
19712
|
+
"Content-Type": "application/json",
|
|
19713
|
+
"X-API-Key": API_KEY
|
|
19714
|
+
},
|
|
19715
|
+
body: JSON.stringify(payload)
|
|
19716
|
+
});
|
|
19717
|
+
}
|
|
19718
|
+
async function submitContactFormAction(formData) {
|
|
19719
|
+
const { firstName, lastName, email, phone, message } = extractFormFields(formData);
|
|
19720
|
+
if (!firstName || !lastName || !email || !message) {
|
|
19721
|
+
return { success: false, error: "Please fill in all required fields." };
|
|
19722
|
+
}
|
|
19604
19723
|
try {
|
|
19605
|
-
const
|
|
19606
|
-
|
|
19607
|
-
const email = ((_c = formData.get("email")) == null ? void 0 : _c.toString().trim()) || "";
|
|
19608
|
-
const phone = ((_d = formData.get("phone")) == null ? void 0 : _d.toString().trim()) || "";
|
|
19609
|
-
const message = ((_e = formData.get("message")) == null ? void 0 : _e.toString().trim()) || "";
|
|
19610
|
-
if (!firstName || !lastName || !email || !message) {
|
|
19611
|
-
return {
|
|
19612
|
-
success: false,
|
|
19613
|
-
error: "Please fill in all required fields."
|
|
19614
|
-
};
|
|
19615
|
-
}
|
|
19616
|
-
const name = `${firstName} ${lastName}`;
|
|
19617
|
-
const payload = {
|
|
19618
|
-
name,
|
|
19724
|
+
const res = await postFormSubmission({
|
|
19725
|
+
name: `${firstName} ${lastName}`,
|
|
19619
19726
|
email,
|
|
19620
19727
|
phone,
|
|
19621
19728
|
message,
|
|
19622
19729
|
source: "contact_form"
|
|
19623
|
-
};
|
|
19624
|
-
const response = await fetch(`${API_URL}/public/form_submissions`, {
|
|
19625
|
-
method: "POST",
|
|
19626
|
-
headers: {
|
|
19627
|
-
"Content-Type": "application/json",
|
|
19628
|
-
"X-API-Key": API_KEY
|
|
19629
|
-
},
|
|
19630
|
-
body: JSON.stringify(payload)
|
|
19631
19730
|
});
|
|
19632
|
-
const data = await
|
|
19633
|
-
if (!
|
|
19634
|
-
|
|
19635
|
-
|
|
19636
|
-
|
|
19637
|
-
};
|
|
19638
|
-
}
|
|
19639
|
-
return {
|
|
19640
|
-
success: true,
|
|
19641
|
-
message: data.message || "Thank you for contacting us! We'll get back to you soon."
|
|
19642
|
-
};
|
|
19643
|
-
} catch (error) {
|
|
19644
|
-
console.error("Contact form submission error:", error);
|
|
19645
|
-
return {
|
|
19646
|
-
success: false,
|
|
19647
|
-
error: "Network error. Please try again later."
|
|
19648
|
-
};
|
|
19731
|
+
const data = await res.json();
|
|
19732
|
+
if (!res.ok) return { success: false, error: data.error || "Failed to submit. Please try again." };
|
|
19733
|
+
return { success: true, message: data.message || "Thank you for contacting us! We'll get back to you soon." };
|
|
19734
|
+
} catch (e) {
|
|
19735
|
+
return { success: false, error: "Network error. Please try again later." };
|
|
19649
19736
|
}
|
|
19650
19737
|
}
|
|
19651
19738
|
async function submitLeadFormAction(formData) {
|
|
19652
|
-
var _a, _b
|
|
19739
|
+
var _a, _b;
|
|
19740
|
+
const { firstName, lastName, email, phone, message } = extractFormFields(formData);
|
|
19741
|
+
if (!firstName || !lastName || !email || !message) {
|
|
19742
|
+
return { success: false, error: "Please fill in all required fields." };
|
|
19743
|
+
}
|
|
19653
19744
|
try {
|
|
19654
|
-
const
|
|
19655
|
-
const
|
|
19656
|
-
|
|
19657
|
-
const
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
success: false,
|
|
19662
|
-
error: "Please fill in all required fields."
|
|
19663
|
-
};
|
|
19664
|
-
}
|
|
19665
|
-
const payload = {
|
|
19666
|
-
formType: "lead",
|
|
19667
|
-
firstName,
|
|
19668
|
-
lastName,
|
|
19669
|
-
email,
|
|
19670
|
-
phone,
|
|
19671
|
-
message
|
|
19672
|
-
};
|
|
19673
|
-
const response = await fetch(`${API_URL}/public/form_submissions`, {
|
|
19674
|
-
method: "POST",
|
|
19675
|
-
headers: {
|
|
19676
|
-
"Content-Type": "application/json",
|
|
19677
|
-
"X-API-Key": API_KEY
|
|
19678
|
-
},
|
|
19679
|
-
body: JSON.stringify(payload)
|
|
19680
|
-
});
|
|
19681
|
-
const data = await response.json();
|
|
19682
|
-
if (!response.ok) {
|
|
19683
|
-
return {
|
|
19684
|
-
success: false,
|
|
19685
|
-
error: data.error || "Failed to submit lead form. Please try again."
|
|
19686
|
-
};
|
|
19687
|
-
}
|
|
19688
|
-
const eventId = (_g = (_f = data.data) == null ? void 0 : _f.event_id) != null ? _g : void 0;
|
|
19689
|
-
return __spreadValues({
|
|
19690
|
-
success: true,
|
|
19691
|
-
message: data.message || "Thank you! We'll be in touch soon."
|
|
19692
|
-
}, eventId && { eventId });
|
|
19693
|
-
} catch (error) {
|
|
19694
|
-
console.error("Lead form submission error:", error);
|
|
19695
|
-
return {
|
|
19696
|
-
success: false,
|
|
19697
|
-
error: "Network error. Please try again later."
|
|
19698
|
-
};
|
|
19745
|
+
const res = await postFormSubmission({ formType: "lead", firstName, lastName, email, phone, message });
|
|
19746
|
+
const data = await res.json();
|
|
19747
|
+
if (!res.ok) return { success: false, error: data.error || "Failed to submit. Please try again." };
|
|
19748
|
+
const eventId = (_b = (_a = data.data) == null ? void 0 : _a.event_id) != null ? _b : void 0;
|
|
19749
|
+
return __spreadValues({ success: true, message: data.message || "Thank you! We'll be in touch soon." }, eventId && { eventId });
|
|
19750
|
+
} catch (e) {
|
|
19751
|
+
return { success: false, error: "Network error. Please try again later." };
|
|
19699
19752
|
}
|
|
19700
19753
|
}
|
|
19701
19754
|
|
|
19702
19755
|
// src/design_system/components/ChatWidget.tsx
|
|
19703
|
-
import
|
|
19756
|
+
import React63, { useState as useState33, useEffect as useEffect13, useRef as useRef19, useCallback as useCallback11 } from "react";
|
|
19704
19757
|
import { X, MessageChatSquare } from "@untitledui/icons";
|
|
19705
19758
|
var formatTime = (isoString) => {
|
|
19706
19759
|
const date = new Date(isoString);
|
|
@@ -19721,17 +19774,19 @@ function ChatWidget({
|
|
|
19721
19774
|
position = "bottom-right",
|
|
19722
19775
|
primaryColor,
|
|
19723
19776
|
sessionId: providedSessionId,
|
|
19777
|
+
contactId,
|
|
19724
19778
|
displayName: providedDisplayName,
|
|
19725
19779
|
teamMembers = []
|
|
19726
19780
|
}) {
|
|
19727
|
-
const [isOpen, setIsOpen] =
|
|
19728
|
-
const [messages, setMessages] =
|
|
19729
|
-
const [inputValue, setInputValue] =
|
|
19730
|
-
const [isLoading, setIsLoading] =
|
|
19731
|
-
const [sessionId, setSessionId] =
|
|
19732
|
-
const [waitingForReply, setWaitingForReply] =
|
|
19733
|
-
const messagesEndRef =
|
|
19734
|
-
|
|
19781
|
+
const [isOpen, setIsOpen] = useState33(false);
|
|
19782
|
+
const [messages, setMessages] = useState33([]);
|
|
19783
|
+
const [inputValue, setInputValue] = useState33("");
|
|
19784
|
+
const [isLoading, setIsLoading] = useState33(false);
|
|
19785
|
+
const [sessionId, setSessionId] = useState33("");
|
|
19786
|
+
const [waitingForReply, setWaitingForReply] = useState33(false);
|
|
19787
|
+
const messagesEndRef = useRef19(null);
|
|
19788
|
+
useEffect13(() => {
|
|
19789
|
+
if (contactId) return;
|
|
19735
19790
|
if (providedSessionId) {
|
|
19736
19791
|
setSessionId(providedSessionId);
|
|
19737
19792
|
} else {
|
|
@@ -19739,15 +19794,17 @@ function ChatWidget({
|
|
|
19739
19794
|
if (stored) {
|
|
19740
19795
|
setSessionId(stored);
|
|
19741
19796
|
} else {
|
|
19742
|
-
const newId = `session_${Date.now()}_${Math.random().toString(36).
|
|
19797
|
+
const newId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
19743
19798
|
localStorage.setItem("keystone_chat_session_id", newId);
|
|
19744
19799
|
setSessionId(newId);
|
|
19745
19800
|
}
|
|
19746
19801
|
}
|
|
19747
|
-
}, [providedSessionId]);
|
|
19802
|
+
}, [contactId, providedSessionId]);
|
|
19748
19803
|
const loadMessages = useCallback11(async () => {
|
|
19804
|
+
if (!contactId && !sessionId) return [];
|
|
19749
19805
|
try {
|
|
19750
|
-
const
|
|
19806
|
+
const query = contactId ? `contact_id=${encodeURIComponent(contactId)}` : `identifier=${encodeURIComponent(sessionId)}`;
|
|
19807
|
+
const response = await fetch(`/api/chat/?${query}`);
|
|
19751
19808
|
if (response.ok) {
|
|
19752
19809
|
const result = await response.json();
|
|
19753
19810
|
const newMessages = result.data || [];
|
|
@@ -19758,34 +19815,37 @@ function ChatWidget({
|
|
|
19758
19815
|
console.error("Failed to load messages:", error);
|
|
19759
19816
|
}
|
|
19760
19817
|
return [];
|
|
19761
|
-
}, [sessionId]);
|
|
19762
|
-
|
|
19763
|
-
if (isOpen && sessionId) {
|
|
19818
|
+
}, [contactId, sessionId]);
|
|
19819
|
+
useEffect13(() => {
|
|
19820
|
+
if (isOpen && (contactId || sessionId)) {
|
|
19764
19821
|
loadMessages();
|
|
19765
19822
|
}
|
|
19766
|
-
}, [isOpen, sessionId, loadMessages]);
|
|
19767
|
-
|
|
19823
|
+
}, [isOpen, contactId, sessionId, loadMessages]);
|
|
19824
|
+
useEffect13(() => {
|
|
19768
19825
|
var _a;
|
|
19769
19826
|
(_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
19770
19827
|
}, [messages]);
|
|
19828
|
+
const pollIntervalRef = useRef19(null);
|
|
19829
|
+
useEffect13(() => {
|
|
19830
|
+
return () => {
|
|
19831
|
+
if (pollIntervalRef.current !== null) {
|
|
19832
|
+
clearInterval(pollIntervalRef.current);
|
|
19833
|
+
}
|
|
19834
|
+
};
|
|
19835
|
+
}, []);
|
|
19771
19836
|
const pollForAgentReply = () => {
|
|
19772
19837
|
setWaitingForReply(true);
|
|
19773
19838
|
let attempts = 0;
|
|
19774
19839
|
const maxAttempts = 30;
|
|
19775
|
-
|
|
19840
|
+
pollIntervalRef.current = setInterval(async () => {
|
|
19776
19841
|
attempts++;
|
|
19777
19842
|
try {
|
|
19778
19843
|
const newMessages = await loadMessages();
|
|
19779
19844
|
const latest = newMessages[newMessages.length - 1];
|
|
19780
19845
|
const hasAgentReplyWithBody = (latest == null ? void 0 : latest.sender_type) === "agent" && (latest == null ? void 0 : latest.body) != null && String(latest.body).trim() !== "";
|
|
19781
|
-
if (hasAgentReplyWithBody) {
|
|
19782
|
-
clearInterval(
|
|
19783
|
-
|
|
19784
|
-
setIsLoading(false);
|
|
19785
|
-
return;
|
|
19786
|
-
}
|
|
19787
|
-
if (attempts >= maxAttempts) {
|
|
19788
|
-
clearInterval(pollInterval);
|
|
19846
|
+
if (hasAgentReplyWithBody || attempts >= maxAttempts) {
|
|
19847
|
+
clearInterval(pollIntervalRef.current);
|
|
19848
|
+
pollIntervalRef.current = null;
|
|
19789
19849
|
setWaitingForReply(false);
|
|
19790
19850
|
setIsLoading(false);
|
|
19791
19851
|
}
|
|
@@ -19796,7 +19856,7 @@ function ChatWidget({
|
|
|
19796
19856
|
};
|
|
19797
19857
|
const sendMessage = async () => {
|
|
19798
19858
|
var _a, _b, _c;
|
|
19799
|
-
if (!inputValue.trim() || !sessionId) return;
|
|
19859
|
+
if (!inputValue.trim() || !contactId && !sessionId) return;
|
|
19800
19860
|
const messageText = inputValue.trim();
|
|
19801
19861
|
setInputValue("");
|
|
19802
19862
|
setIsLoading(true);
|
|
@@ -19811,15 +19871,10 @@ function ChatWidget({
|
|
|
19811
19871
|
try {
|
|
19812
19872
|
const response = await fetch("/api/chat/", {
|
|
19813
19873
|
method: "POST",
|
|
19814
|
-
headers: {
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
identifier: sessionId,
|
|
19819
|
-
body: messageText,
|
|
19820
|
-
display_name: providedDisplayName,
|
|
19821
|
-
page_url: window.location.href
|
|
19822
|
-
})
|
|
19874
|
+
headers: { "Content-Type": "application/json" },
|
|
19875
|
+
body: JSON.stringify(
|
|
19876
|
+
contactId ? { contact_id: contactId, body: messageText, display_name: providedDisplayName, page_url: window.location.href } : { identifier: sessionId, body: messageText, display_name: providedDisplayName, page_url: window.location.href }
|
|
19877
|
+
)
|
|
19823
19878
|
});
|
|
19824
19879
|
if (response.ok) {
|
|
19825
19880
|
const result = await response.json();
|
|
@@ -19842,7 +19897,7 @@ function ChatWidget({
|
|
|
19842
19897
|
setIsLoading(false);
|
|
19843
19898
|
}
|
|
19844
19899
|
};
|
|
19845
|
-
const
|
|
19900
|
+
const handleKeyDown = (e) => {
|
|
19846
19901
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
19847
19902
|
e.preventDefault();
|
|
19848
19903
|
sendMessage();
|
|
@@ -19854,7 +19909,7 @@ function ChatWidget({
|
|
|
19854
19909
|
} : void 0;
|
|
19855
19910
|
const widgetBrandClass = primaryColor ? "bg-[var(--widget-primary)]" : "bg-brand-solid";
|
|
19856
19911
|
const widgetBrandHoverClass = primaryColor ? "hover:bg-[var(--widget-primary-hover)]" : "hover:bg-brand-solid_hover";
|
|
19857
|
-
return /* @__PURE__ */
|
|
19912
|
+
return /* @__PURE__ */ React63.createElement(
|
|
19858
19913
|
"div",
|
|
19859
19914
|
{
|
|
19860
19915
|
className: cx(
|
|
@@ -19863,7 +19918,7 @@ function ChatWidget({
|
|
|
19863
19918
|
),
|
|
19864
19919
|
style: customColorVars
|
|
19865
19920
|
},
|
|
19866
|
-
!isOpen && /* @__PURE__ */
|
|
19921
|
+
!isOpen && /* @__PURE__ */ React63.createElement(
|
|
19867
19922
|
"button",
|
|
19868
19923
|
{
|
|
19869
19924
|
onClick: () => setIsOpen(true),
|
|
@@ -19873,15 +19928,15 @@ function ChatWidget({
|
|
|
19873
19928
|
),
|
|
19874
19929
|
"aria-label": "Open chat"
|
|
19875
19930
|
},
|
|
19876
|
-
/* @__PURE__ */
|
|
19931
|
+
/* @__PURE__ */ React63.createElement(MessageChatSquare, { className: "size-6" })
|
|
19877
19932
|
),
|
|
19878
|
-
isOpen && /* @__PURE__ */
|
|
19933
|
+
isOpen && /* @__PURE__ */ React63.createElement("div", { className: "flex h-[500px] w-[380px] flex-col overflow-hidden rounded-xl bg-primary shadow-2xl ring-1 ring-secondary ring-inset" }, /* @__PURE__ */ React63.createElement("div", { className: cx(
|
|
19879
19934
|
"flex items-center justify-between gap-3 p-4 text-white",
|
|
19880
19935
|
widgetBrandClass
|
|
19881
|
-
) }, /* @__PURE__ */
|
|
19936
|
+
) }, /* @__PURE__ */ React63.createElement("div", { className: "flex min-w-0 flex-1 flex-col gap-2" }, /* @__PURE__ */ React63.createElement("h3", { className: "m-0 text-lg font-semibold leading-tight" }, "Chat with us"), (() => {
|
|
19882
19937
|
const membersWithPhotos = teamMembers.filter((m) => m.photo_url);
|
|
19883
19938
|
if (membersWithPhotos.length === 0) return null;
|
|
19884
|
-
return /* @__PURE__ */
|
|
19939
|
+
return /* @__PURE__ */ React63.createElement("div", { className: "flex -space-x-2" }, membersWithPhotos.slice(0, 5).map((member) => /* @__PURE__ */ React63.createElement(
|
|
19885
19940
|
Avatar,
|
|
19886
19941
|
{
|
|
19887
19942
|
key: member.id,
|
|
@@ -19890,28 +19945,28 @@ function ChatWidget({
|
|
|
19890
19945
|
alt: member.name,
|
|
19891
19946
|
className: "bg-white ring-2 ring-white"
|
|
19892
19947
|
}
|
|
19893
|
-
)), membersWithPhotos.length > 5 && /* @__PURE__ */
|
|
19948
|
+
)), membersWithPhotos.length > 5 && /* @__PURE__ */ React63.createElement(
|
|
19894
19949
|
Avatar,
|
|
19895
19950
|
{
|
|
19896
19951
|
size: "xs",
|
|
19897
19952
|
className: "bg-white ring-2 ring-white",
|
|
19898
|
-
placeholder: /* @__PURE__ */
|
|
19953
|
+
placeholder: /* @__PURE__ */ React63.createElement("span", { className: "flex items-center justify-center text-xs font-semibold text-tertiary" }, "+", membersWithPhotos.length - 5)
|
|
19899
19954
|
}
|
|
19900
19955
|
));
|
|
19901
|
-
})()), /* @__PURE__ */
|
|
19956
|
+
})()), /* @__PURE__ */ React63.createElement(
|
|
19902
19957
|
"button",
|
|
19903
19958
|
{
|
|
19904
19959
|
onClick: () => setIsOpen(false),
|
|
19905
19960
|
className: "cursor-pointer rounded-md p-1 text-white outline-none transition-colors hover:bg-white/10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white",
|
|
19906
19961
|
"aria-label": "Close chat"
|
|
19907
19962
|
},
|
|
19908
|
-
/* @__PURE__ */
|
|
19909
|
-
)), /* @__PURE__ */
|
|
19963
|
+
/* @__PURE__ */ React63.createElement(X, { className: "size-5" })
|
|
19964
|
+
)), /* @__PURE__ */ React63.createElement("ul", { className: "flex flex-1 flex-col gap-4 overflow-y-auto px-4 py-4" }, messages.length === 0 && /* @__PURE__ */ React63.createElement("li", { className: "mt-5 text-center text-sm text-quaternary" }, "Start a conversation! We're here to help."), messages.map((message, index) => {
|
|
19910
19965
|
var _a, _b, _c, _d;
|
|
19911
19966
|
const isUser = message.sender_type === "contact";
|
|
19912
19967
|
const prevMessage = index > 0 ? messages[index - 1] : null;
|
|
19913
19968
|
const showAvatar = !isUser && (!prevMessage || prevMessage.sender_type === "contact");
|
|
19914
|
-
return /* @__PURE__ */
|
|
19969
|
+
return /* @__PURE__ */ React63.createElement(
|
|
19915
19970
|
"li",
|
|
19916
19971
|
{
|
|
19917
19972
|
key: message.id,
|
|
@@ -19920,7 +19975,7 @@ function ChatWidget({
|
|
|
19920
19975
|
isUser ? "self-end pl-10" : "pr-8"
|
|
19921
19976
|
)
|
|
19922
19977
|
},
|
|
19923
|
-
!isUser && showAvatar && /* @__PURE__ */
|
|
19978
|
+
!isUser && showAvatar && /* @__PURE__ */ React63.createElement(
|
|
19924
19979
|
Avatar,
|
|
19925
19980
|
{
|
|
19926
19981
|
size: "sm",
|
|
@@ -19930,8 +19985,8 @@ function ChatWidget({
|
|
|
19930
19985
|
initials: ((_c = teamMembers[0]) == null ? void 0 : _c.photo_url) ? void 0 : (((_d = teamMembers[0]) == null ? void 0 : _d.name) || message.sender_display_name).split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2)
|
|
19931
19986
|
}
|
|
19932
19987
|
),
|
|
19933
|
-
!isUser && !showAvatar && /* @__PURE__ */
|
|
19934
|
-
/* @__PURE__ */
|
|
19988
|
+
!isUser && !showAvatar && /* @__PURE__ */ React63.createElement("div", { className: "size-8 shrink-0" }),
|
|
19989
|
+
/* @__PURE__ */ React63.createElement("article", { className: "flex min-w-0 flex-1 flex-col gap-1" }, /* @__PURE__ */ React63.createElement("header", { className: "flex items-center gap-2 px-0.5" }, /* @__PURE__ */ React63.createElement("cite", { className: "flex-1 truncate text-xs font-medium text-tertiary not-italic" }, isUser ? providedDisplayName || "You" : message.sender_display_name), /* @__PURE__ */ React63.createElement("time", { className: "text-xs text-quaternary", dateTime: message.created_at }, formatTime(message.created_at))), /* @__PURE__ */ React63.createElement(
|
|
19935
19990
|
"div",
|
|
19936
19991
|
{
|
|
19937
19992
|
className: cx(
|
|
@@ -19945,18 +20000,18 @@ function ChatWidget({
|
|
|
19945
20000
|
message.body
|
|
19946
20001
|
))
|
|
19947
20002
|
);
|
|
19948
|
-
}), waitingForReply && /* @__PURE__ */
|
|
20003
|
+
}), waitingForReply && /* @__PURE__ */ React63.createElement("li", { className: "relative flex items-start gap-3 pr-8" }, /* @__PURE__ */ React63.createElement(Avatar, { size: "sm", className: "shrink-0" }), /* @__PURE__ */ React63.createElement("article", { className: "flex min-w-0 flex-1 flex-col gap-1" }, /* @__PURE__ */ React63.createElement("div", { className: "flex h-9 w-16 items-center justify-center gap-1 rounded-xl rounded-tl-none bg-secondary ring-1 ring-inset ring-secondary" }, /* @__PURE__ */ React63.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-tertiary [animation-delay:-0.3s]" }), /* @__PURE__ */ React63.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-quaternary [animation-delay:-0.15s]" }), /* @__PURE__ */ React63.createElement("div", { className: "size-1.5 animate-bounce rounded-full bg-fg-tertiary" })))), /* @__PURE__ */ React63.createElement("div", { ref: messagesEndRef })), /* @__PURE__ */ React63.createElement("div", { className: "flex gap-2 border-t border-secondary p-3" }, /* @__PURE__ */ React63.createElement(
|
|
19949
20004
|
"input",
|
|
19950
20005
|
{
|
|
19951
20006
|
type: "text",
|
|
19952
20007
|
value: inputValue,
|
|
19953
20008
|
onChange: (e) => setInputValue(e.target.value),
|
|
19954
|
-
|
|
20009
|
+
onKeyDown: handleKeyDown,
|
|
19955
20010
|
placeholder: "Type a message...",
|
|
19956
20011
|
disabled: isLoading,
|
|
19957
20012
|
className: "flex-1 rounded-lg border border-secondary bg-primary px-3 py-2.5 text-sm text-primary outline-none ring-brand transition-colors placeholder:text-placeholder focus:border-brand focus:ring-1 disabled:cursor-not-allowed disabled:bg-disabled"
|
|
19958
20013
|
}
|
|
19959
|
-
), /* @__PURE__ */
|
|
20014
|
+
), /* @__PURE__ */ React63.createElement(
|
|
19960
20015
|
"button",
|
|
19961
20016
|
{
|
|
19962
20017
|
onClick: sendMessage,
|
|
@@ -20009,6 +20064,7 @@ export {
|
|
|
20009
20064
|
ComboBox,
|
|
20010
20065
|
ContactHome2 as ContactHome,
|
|
20011
20066
|
ContactSection5 as ContactSection,
|
|
20067
|
+
EmailSignupSection,
|
|
20012
20068
|
FAQGrid3 as FAQGrid,
|
|
20013
20069
|
FAQHero3 as FAQHero,
|
|
20014
20070
|
FAQHome4 as FAQHome,
|