keystone-design-bootstrap 1.0.86 → 1.0.87
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/company-information-C1pP-SvU.d.ts +50 -0
- package/dist/config-C_XBZixg.d.ts +21 -0
- package/dist/consumer-BWjQawiO.d.ts +48 -0
- package/dist/design_system/portal/index.d.ts +52 -0
- package/dist/design_system/portal/index.js +3113 -0
- package/dist/design_system/portal/index.js.map +1 -0
- package/dist/design_system/sections/index.d.ts +2 -1
- package/dist/index.d.ts +5 -24
- package/dist/lib/consumer-session.d.ts +16 -0
- package/dist/lib/consumer-session.js +85 -0
- package/dist/lib/consumer-session.js.map +1 -0
- package/dist/lib/cta-urls.d.ts +34 -0
- package/dist/lib/cta-urls.js +33 -0
- package/dist/lib/cta-urls.js.map +1 -0
- package/dist/lib/server-api.d.ts +2 -1
- package/dist/lib/server-api.js +1 -1
- package/dist/lib/server-api.js.map +1 -1
- package/dist/next/contexts/form-definitions.d.ts +17 -0
- package/dist/next/contexts/form-definitions.js +21 -0
- package/dist/next/contexts/form-definitions.js.map +1 -0
- package/dist/next/gallery/design-gallery.d.ts +103 -0
- package/dist/next/gallery/design-gallery.js +19301 -0
- package/dist/next/gallery/design-gallery.js.map +1 -0
- package/dist/next/layouts/root-layout.d.ts +55 -0
- package/dist/next/layouts/root-layout.js +19713 -0
- package/dist/next/layouts/root-layout.js.map +1 -0
- package/dist/next/legal/privacy-policy.d.ts +7 -0
- package/dist/next/legal/privacy-policy.js +18949 -0
- package/dist/next/legal/privacy-policy.js.map +1 -0
- package/dist/next/legal/terms-of-service.d.ts +7 -0
- package/dist/next/legal/terms-of-service.js +18949 -0
- package/dist/next/legal/terms-of-service.js.map +1 -0
- package/dist/next/providers/ssr-provider.d.ts +12 -0
- package/dist/next/providers/ssr-provider.js +12 -0
- package/dist/next/providers/ssr-provider.js.map +1 -0
- package/dist/next/routes/chat.d.ts +26 -0
- package/dist/next/routes/chat.js +160 -0
- package/dist/next/routes/chat.js.map +1 -0
- package/dist/next/routes/consumer-auth.d.ts +33 -0
- package/dist/next/routes/consumer-auth.js +254 -0
- package/dist/next/routes/consumer-auth.js.map +1 -0
- package/dist/next/routes/form.d.ts +37 -0
- package/dist/next/routes/form.js +97 -0
- package/dist/next/routes/form.js.map +1 -0
- package/dist/package-IU_GpDA0.d.ts +74 -0
- package/dist/types/index.d.ts +6 -68
- package/package.json +28 -28
- package/src/design_system/portal/PortalPage.tsx +3 -2
- package/src/lib/server-api.ts +2 -1
- package/src/types/rails-actioncable.d.ts +16 -0
- package/dist/package-DeHKpQp7.d.ts +0 -121
|
@@ -0,0 +1,3113 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __objRest = (source, exclude) => {
|
|
21
|
+
var target = {};
|
|
22
|
+
for (var prop in source)
|
|
23
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
24
|
+
target[prop] = source[prop];
|
|
25
|
+
if (source != null && __getOwnPropSymbols)
|
|
26
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
27
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
28
|
+
target[prop] = source[prop];
|
|
29
|
+
}
|
|
30
|
+
return target;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/design_system/portal/PortalPage.tsx
|
|
34
|
+
import React8 from "react";
|
|
35
|
+
import Link from "next/link";
|
|
36
|
+
import { cookies } from "next/headers";
|
|
37
|
+
|
|
38
|
+
// src/utils/cx.ts
|
|
39
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
40
|
+
var twMerge = extendTailwindMerge({
|
|
41
|
+
extend: {
|
|
42
|
+
theme: {
|
|
43
|
+
text: ["display-xs", "display-sm", "display-md", "display-lg", "display-xl", "display-2xl"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
var cx = twMerge;
|
|
48
|
+
|
|
49
|
+
// src/design_system/logo/keystone-logo-minimal.tsx
|
|
50
|
+
var KEYSTONE_APP_URL = "https://www.keystone.app";
|
|
51
|
+
var KeystoneLogoMinimal = (props) => {
|
|
52
|
+
return /* @__PURE__ */ React.createElement(
|
|
53
|
+
"svg",
|
|
54
|
+
__spreadProps(__spreadValues({
|
|
55
|
+
viewBox: "0 0 36 41",
|
|
56
|
+
fill: "none",
|
|
57
|
+
"aria-hidden": "true"
|
|
58
|
+
}, props), {
|
|
59
|
+
className: cx("text-[#6ECC8B]", props.className)
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ React.createElement(
|
|
62
|
+
"path",
|
|
63
|
+
{
|
|
64
|
+
d: "M36 13.6677L24.0031 20.4999L36 27.3321V40.9966L11.998 27.3288V13.671L36 0.0032959V13.6677Z",
|
|
65
|
+
fill: "currentColor"
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ React.createElement(
|
|
69
|
+
"path",
|
|
70
|
+
{
|
|
71
|
+
d: "M11.9971 27.3288V40.9958L0 34.1644V20.4973L11.9971 27.3288Z",
|
|
72
|
+
fill: "currentColor"
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
/* @__PURE__ */ React.createElement(
|
|
76
|
+
"path",
|
|
77
|
+
{
|
|
78
|
+
d: "M11.9971 13.671L0.00476074 20.4999L0 20.4973V6.83551L11.9971 0.00402832V13.671Z",
|
|
79
|
+
fill: "currentColor"
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/design_system/logo/keystone-wordmark.tsx
|
|
86
|
+
var KeystoneWordmark = (props) => {
|
|
87
|
+
return /* @__PURE__ */ React.createElement(
|
|
88
|
+
"svg",
|
|
89
|
+
__spreadProps(__spreadValues({
|
|
90
|
+
viewBox: "0 0 104 20",
|
|
91
|
+
fill: "none",
|
|
92
|
+
"aria-hidden": "true"
|
|
93
|
+
}, props), {
|
|
94
|
+
className: cx("text-[#6ECC8B]", props.className)
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ React.createElement(
|
|
97
|
+
"path",
|
|
98
|
+
{
|
|
99
|
+
d: "M3.14032 0L3.1612 8.23754H3.82871L9.47236 2.94575H13.0328V3.34299L6.45695 9.52024L13.2946 16.1684V16.5656H9.53903L3.8267 11.0811H3.15919V16.5584H0V0H3.14032Z",
|
|
100
|
+
fill: "currentColor"
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
/* @__PURE__ */ React.createElement(
|
|
104
|
+
"path",
|
|
105
|
+
{
|
|
106
|
+
d: "M18.4299 2.62219C22.4643 2.62219 24.8242 5.10147 24.8242 9.20756V10.6049H14.6121C14.6635 12.8878 16.0848 14.486 18.4167 14.486C20.7485 14.486 21.6835 13.2718 21.8892 12.243H24.5535V12.6403C24.1957 14.422 22.5534 16.8876 18.4552 16.8876C14.357 16.8876 11.7031 14.4462 11.7031 9.7505C11.7031 5.05478 14.3675 2.62662 18.4295 2.62662L18.4299 2.62219ZM21.9647 8.28025C21.939 6.42119 20.7365 4.9932 18.3914 4.9932C16.0463 4.9932 14.8181 6.45903 14.6506 8.28025H21.9667H21.9647Z",
|
|
107
|
+
fill: "currentColor"
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
/* @__PURE__ */ React.createElement(
|
|
111
|
+
"path",
|
|
112
|
+
{
|
|
113
|
+
d: "M27.5146 2.93809L31.2554 12.994H31.459L35.1865 2.93809H38.2369V3.33534L31.565 19.9996H28.5147V19.6024L29.7417 16.5942L24.3848 3.33292V2.93567H27.5142L27.5146 2.93809Z",
|
|
114
|
+
fill: "currentColor"
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
/* @__PURE__ */ React.createElement(
|
|
118
|
+
"path",
|
|
119
|
+
{
|
|
120
|
+
d: "M44.1191 2.63538C47.759 2.63538 49.8832 4.19177 50.1017 6.73947V7.13671H47.3345C47.2445 5.53847 45.965 4.98667 44.1319 4.98667C42.2989 4.98667 41.3129 5.54088 41.3129 6.5982C41.3129 7.65551 42.1338 7.9956 43.3002 8.18115L45.8103 8.57839C48.4747 9.0022 50.2796 10.1214 50.2796 12.6027C50.2796 15.084 48.3591 16.8767 44.4898 16.8767C40.6205 16.8767 38.3782 15.082 38.1855 12.6667V12.2695H41.0045C41.133 13.8677 42.5692 14.5258 44.4898 14.5258C46.4104 14.5258 47.3859 13.8391 47.3859 12.7706C47.3859 11.702 46.5521 11.2782 45.1332 11.0552L42.623 10.658C40.1129 10.2607 38.4324 9.12818 38.4324 6.66219C38.4324 4.1962 40.4944 2.6378 44.1215 2.6378H44.1195L44.1191 2.63538Z",
|
|
121
|
+
fill: "currentColor"
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ React.createElement(
|
|
125
|
+
"path",
|
|
126
|
+
{
|
|
127
|
+
d: "M54.2257 2.93729V0H57.2632V2.93729H62.247V5.35257H57.2632V13.7724L57.4668 13.9821H62.1679V16.5673H57.8765C55.5828 16.5673 54.2237 15.353 54.2237 13.0327V5.35217H50.2773V2.93689L54.2257 2.93729Z",
|
|
128
|
+
fill: "currentColor"
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
/* @__PURE__ */ React.createElement(
|
|
132
|
+
"path",
|
|
133
|
+
{
|
|
134
|
+
d: "M69.219 2.62219C73.3325 2.62219 76.2025 5.13004 76.2025 9.74608C76.2025 14.3621 73.3325 16.8832 69.219 16.8832C65.1056 16.8832 62.25 14.3754 62.25 9.74608C62.25 5.11676 65.1052 2.62219 69.219 2.62219ZM69.219 14.4442C71.6003 14.4442 73.1265 12.7687 73.1265 9.74648C73.1265 6.72426 71.6023 5.06202 69.219 5.06202C66.8358 5.06202 65.3241 6.73754 65.3241 9.74648C65.3241 12.7554 66.8482 14.4442 69.219 14.4442Z",
|
|
135
|
+
fill: "currentColor"
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
/* @__PURE__ */ React.createElement(
|
|
139
|
+
"path",
|
|
140
|
+
{
|
|
141
|
+
d: "M80.2535 2.94007V4.45661H80.4568C81.3785 3.16304 82.8641 2.64868 84.6606 2.64868C87.7989 2.64868 89.7472 4.29763 89.7472 7.66236V16.5681H86.7097V7.93846C86.7097 6.03755 85.698 5.15451 83.8907 5.15451C81.9315 5.15451 80.3539 6.30238 80.3539 8.95392V16.566H77.3164V2.94007H80.2535Z",
|
|
142
|
+
fill: "currentColor"
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
/* @__PURE__ */ React.createElement(
|
|
146
|
+
"path",
|
|
147
|
+
{
|
|
148
|
+
d: "M97.6065 2.62219C101.64 2.62219 104 5.10147 104 9.20756V10.6049H93.7886C93.838 12.8878 95.261 14.486 97.5933 14.486C99.9255 14.486 100.859 13.2718 101.065 12.243H103.729V12.6403C103.371 14.422 101.73 16.8876 97.6314 16.8876C93.5328 16.8876 90.8789 14.4462 90.8789 9.7505C90.8789 5.05478 93.5437 2.62662 97.6057 2.62662L97.6065 2.62219ZM101.141 8.28025C101.115 6.42119 99.9127 4.9932 97.568 4.9932C95.2233 4.9932 93.9943 6.45903 93.8272 8.28025H101.143H101.141Z",
|
|
149
|
+
fill: "currentColor"
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/design_system/logo/keystone-brand-lockup.tsx
|
|
156
|
+
function KeystoneBrandLockup(_a) {
|
|
157
|
+
var _b = _a, {
|
|
158
|
+
className,
|
|
159
|
+
iconClassName,
|
|
160
|
+
wordmarkClassName
|
|
161
|
+
} = _b, props = __objRest(_b, [
|
|
162
|
+
"className",
|
|
163
|
+
"iconClassName",
|
|
164
|
+
"wordmarkClassName"
|
|
165
|
+
]);
|
|
166
|
+
return /* @__PURE__ */ React.createElement("div", __spreadProps(__spreadValues({}, props), { className: cx("flex flex-col items-start gap-2", className) }), /* @__PURE__ */ React.createElement(KeystoneLogoMinimal, { className: cx("h-4 w-auto shrink-0", iconClassName) }), /* @__PURE__ */ React.createElement(KeystoneWordmark, { className: cx("h-4 w-auto shrink-0", wordmarkClassName) }));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/design_system/portal/LogoutButton.tsx
|
|
170
|
+
import { useRouter } from "next/navigation";
|
|
171
|
+
function LogoutButton() {
|
|
172
|
+
const router = useRouter();
|
|
173
|
+
const handleLogout = async () => {
|
|
174
|
+
await fetch("/api/consumer/logout", { method: "POST" });
|
|
175
|
+
router.refresh();
|
|
176
|
+
};
|
|
177
|
+
return /* @__PURE__ */ React.createElement(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
type: "button",
|
|
181
|
+
onClick: handleLogout,
|
|
182
|
+
className: "text-xs text-tertiary hover:text-primary transition-colors"
|
|
183
|
+
},
|
|
184
|
+
"Sign out"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/design_system/portal/LoginModalController.tsx
|
|
189
|
+
import React4, { useState as useState2, useEffect as useEffect3, useTransition } from "react";
|
|
190
|
+
import { useRouter as useRouter3 } from "next/navigation";
|
|
191
|
+
|
|
192
|
+
// src/design_system/elements/modal/modal.tsx
|
|
193
|
+
import React2, { useEffect, useRef } from "react";
|
|
194
|
+
var MAX_WIDTH_CLASSES = {
|
|
195
|
+
sm: "max-w-sm",
|
|
196
|
+
md: "max-w-md",
|
|
197
|
+
lg: "max-w-lg",
|
|
198
|
+
xl: "max-w-xl",
|
|
199
|
+
"2xl": "max-w-2xl",
|
|
200
|
+
"3xl": "max-w-3xl",
|
|
201
|
+
"4xl": "max-w-4xl",
|
|
202
|
+
"5xl": "max-w-5xl"
|
|
203
|
+
};
|
|
204
|
+
function Modal({
|
|
205
|
+
isOpen,
|
|
206
|
+
onClose,
|
|
207
|
+
title,
|
|
208
|
+
titleId = "modal-title",
|
|
209
|
+
children,
|
|
210
|
+
footer,
|
|
211
|
+
hideHeader = false,
|
|
212
|
+
ariaLabel,
|
|
213
|
+
overlayClassName,
|
|
214
|
+
panelClassName,
|
|
215
|
+
maxWidth = "md"
|
|
216
|
+
}) {
|
|
217
|
+
const overlayRef = useRef(null);
|
|
218
|
+
const closeButtonRef = useRef(null);
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
var _a;
|
|
221
|
+
if (!isOpen) return;
|
|
222
|
+
const previouslyFocused = document.activeElement;
|
|
223
|
+
(_a = closeButtonRef.current) == null ? void 0 : _a.focus();
|
|
224
|
+
const handleEscape = (e) => {
|
|
225
|
+
if (e.key === "Escape") onClose();
|
|
226
|
+
};
|
|
227
|
+
document.addEventListener("keydown", handleEscape);
|
|
228
|
+
document.body.style.overflow = "hidden";
|
|
229
|
+
return () => {
|
|
230
|
+
document.removeEventListener("keydown", handleEscape);
|
|
231
|
+
document.body.style.overflow = "";
|
|
232
|
+
previouslyFocused == null ? void 0 : previouslyFocused.focus();
|
|
233
|
+
};
|
|
234
|
+
}, [isOpen, onClose]);
|
|
235
|
+
if (!isOpen) return null;
|
|
236
|
+
const maxWidthClass = MAX_WIDTH_CLASSES[maxWidth];
|
|
237
|
+
return /* @__PURE__ */ React2.createElement(
|
|
238
|
+
"div",
|
|
239
|
+
{
|
|
240
|
+
ref: overlayRef,
|
|
241
|
+
className: overlayClassName != null ? overlayClassName : "fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/50",
|
|
242
|
+
role: "dialog",
|
|
243
|
+
"aria-modal": "true",
|
|
244
|
+
"aria-label": ariaLabel,
|
|
245
|
+
"aria-labelledby": !ariaLabel && title && !hideHeader ? titleId : void 0,
|
|
246
|
+
onClick: (e) => e.target === overlayRef.current && onClose()
|
|
247
|
+
},
|
|
248
|
+
/* @__PURE__ */ React2.createElement(
|
|
249
|
+
"div",
|
|
250
|
+
{
|
|
251
|
+
className: panelClassName != null ? panelClassName : `bg-primary border border-secondary rounded-lg shadow-xl w-full overflow-hidden ${maxWidthClass} max-h-[90vh] flex flex-col`,
|
|
252
|
+
onClick: (e) => e.stopPropagation()
|
|
253
|
+
},
|
|
254
|
+
!hideHeader && /* @__PURE__ */ React2.createElement("div", { className: "flex items-start justify-between gap-4 p-4 md:p-6 border-b border-secondary flex-shrink-0" }, title ? /* @__PURE__ */ React2.createElement(
|
|
255
|
+
"h2",
|
|
256
|
+
{
|
|
257
|
+
id: titleId,
|
|
258
|
+
className: "font-display text-lg font-normal text-fg-primary md:text-xl flex-1 min-w-0"
|
|
259
|
+
},
|
|
260
|
+
title
|
|
261
|
+
) : /* @__PURE__ */ React2.createElement("span", { className: "flex-1", "aria-hidden": true }), /* @__PURE__ */ React2.createElement(
|
|
262
|
+
"button",
|
|
263
|
+
{
|
|
264
|
+
ref: closeButtonRef,
|
|
265
|
+
type: "button",
|
|
266
|
+
onClick: onClose,
|
|
267
|
+
className: "shrink-0 p-1 text-fg-primary hover:text-brand-accent rounded focus:outline-none focus:ring-2 focus:ring-brand-accent",
|
|
268
|
+
"aria-label": "Close"
|
|
269
|
+
},
|
|
270
|
+
/* @__PURE__ */ React2.createElement(
|
|
271
|
+
"svg",
|
|
272
|
+
{
|
|
273
|
+
className: "w-5 h-5",
|
|
274
|
+
fill: "none",
|
|
275
|
+
stroke: "currentColor",
|
|
276
|
+
viewBox: "0 0 24 24",
|
|
277
|
+
"aria-hidden": true
|
|
278
|
+
},
|
|
279
|
+
/* @__PURE__ */ React2.createElement(
|
|
280
|
+
"path",
|
|
281
|
+
{
|
|
282
|
+
strokeLinecap: "round",
|
|
283
|
+
strokeLinejoin: "round",
|
|
284
|
+
strokeWidth: 2,
|
|
285
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
)),
|
|
290
|
+
/* @__PURE__ */ React2.createElement("div", { className: "overflow-y-auto flex-1 p-4 md:p-6" }, children),
|
|
291
|
+
footer && /* @__PURE__ */ React2.createElement("div", { className: "flex-shrink-0 border-t border-secondary" }, footer)
|
|
292
|
+
)
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/design_system/portal/LoginForm.tsx
|
|
297
|
+
import React3, { useEffect as useEffect2, useMemo, useState } from "react";
|
|
298
|
+
import { useRouter as useRouter2 } from "next/navigation";
|
|
299
|
+
|
|
300
|
+
// src/utils/countries.tsx
|
|
301
|
+
import Image from "next/image";
|
|
302
|
+
var countries = [
|
|
303
|
+
{
|
|
304
|
+
name: "Afghanistan",
|
|
305
|
+
code: "AF",
|
|
306
|
+
flag: "https://www.untitledui.com/images/flags/AF.svg",
|
|
307
|
+
phoneCode: "93",
|
|
308
|
+
phoneMask: "+93-##-###-####"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "Albania",
|
|
312
|
+
code: "AL",
|
|
313
|
+
flag: "https://www.untitledui.com/images/flags/AL.svg",
|
|
314
|
+
phoneCode: "355",
|
|
315
|
+
phoneMask: "+355 (###) ###-###"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "Algeria",
|
|
319
|
+
code: "DZ",
|
|
320
|
+
flag: "https://www.untitledui.com/images/flags/DZ.svg",
|
|
321
|
+
phoneCode: "213",
|
|
322
|
+
phoneMask: "+213-##-###-####"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "Andorra",
|
|
326
|
+
code: "AD",
|
|
327
|
+
flag: "https://www.untitledui.com/images/flags/AD.svg",
|
|
328
|
+
phoneCode: "376",
|
|
329
|
+
phoneMask: "+376-###-###"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "Angola",
|
|
333
|
+
code: "AO",
|
|
334
|
+
flag: "https://www.untitledui.com/images/flags/AO.svg",
|
|
335
|
+
phoneCode: "244",
|
|
336
|
+
phoneMask: "+244 (###) ###-###"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: "Antigua and Barbuda",
|
|
340
|
+
code: "AG",
|
|
341
|
+
flag: "https://www.untitledui.com/images/flags/AG.svg",
|
|
342
|
+
phoneCode: "+1-268",
|
|
343
|
+
phoneMask: "+1 (268) ###-####"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "Argentina",
|
|
347
|
+
code: "AR",
|
|
348
|
+
flag: "https://www.untitledui.com/images/flags/AR.svg",
|
|
349
|
+
phoneCode: "54",
|
|
350
|
+
phoneMask: "+54 (###) ###-####"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "Armenia",
|
|
354
|
+
code: "AM",
|
|
355
|
+
flag: "https://www.untitledui.com/images/flags/AM.svg",
|
|
356
|
+
phoneCode: "374",
|
|
357
|
+
phoneMask: "+374-##-###-###"
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "Australia",
|
|
361
|
+
code: "AU",
|
|
362
|
+
flag: "https://www.untitledui.com/images/flags/AU.svg",
|
|
363
|
+
phoneCode: "61",
|
|
364
|
+
phoneMask: "+61-#-####-####"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "Austria",
|
|
368
|
+
code: "AT",
|
|
369
|
+
flag: "https://www.untitledui.com/images/flags/AT.svg",
|
|
370
|
+
phoneCode: "43",
|
|
371
|
+
phoneMask: "+43 (###) ###-####"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: "Azerbaijan",
|
|
375
|
+
code: "AZ",
|
|
376
|
+
flag: "https://www.untitledui.com/images/flags/AZ.svg",
|
|
377
|
+
phoneCode: "994",
|
|
378
|
+
phoneMask: "+994-##-###-##-##"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "Bahamas",
|
|
382
|
+
code: "BS",
|
|
383
|
+
flag: "https://www.untitledui.com/images/flags/BS.svg",
|
|
384
|
+
phoneCode: "+1-242",
|
|
385
|
+
phoneMask: "+1 (242) ###-####"
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "Bahrain",
|
|
389
|
+
code: "BH",
|
|
390
|
+
flag: "https://www.untitledui.com/images/flags/BH.svg",
|
|
391
|
+
phoneCode: "973",
|
|
392
|
+
phoneMask: "+973-####-####"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: "Bangladesh",
|
|
396
|
+
code: "BD",
|
|
397
|
+
flag: "https://www.untitledui.com/images/flags/BD.svg",
|
|
398
|
+
phoneCode: "880",
|
|
399
|
+
phoneMask: "+880-##-###-###"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: "Barbados",
|
|
403
|
+
code: "BB",
|
|
404
|
+
flag: "https://www.untitledui.com/images/flags/BB.svg",
|
|
405
|
+
phoneCode: "+1-246",
|
|
406
|
+
phoneMask: "+1 (246) ###-####"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: "Belarus",
|
|
410
|
+
code: "BY",
|
|
411
|
+
flag: "https://www.untitledui.com/images/flags/BY.svg",
|
|
412
|
+
phoneCode: "375",
|
|
413
|
+
phoneMask: "+375 (##) ###-##-##"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "Belgium",
|
|
417
|
+
code: "BE",
|
|
418
|
+
flag: "https://www.untitledui.com/images/flags/BE.svg",
|
|
419
|
+
phoneCode: "32",
|
|
420
|
+
phoneMask: "+32 (###) ###-###"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "Belize",
|
|
424
|
+
code: "BZ",
|
|
425
|
+
flag: "https://www.untitledui.com/images/flags/BZ.svg",
|
|
426
|
+
phoneCode: "501",
|
|
427
|
+
phoneMask: "+501-###-####"
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "Benin",
|
|
431
|
+
code: "BJ",
|
|
432
|
+
flag: "https://www.untitledui.com/images/flags/BJ.svg",
|
|
433
|
+
phoneCode: "229",
|
|
434
|
+
phoneMask: "+229-##-##-####"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "Bhutan",
|
|
438
|
+
code: "BT",
|
|
439
|
+
flag: "https://www.untitledui.com/images/flags/BT.svg",
|
|
440
|
+
phoneCode: "975",
|
|
441
|
+
phoneMask: "+975-#-###-###"
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "Bolivia",
|
|
445
|
+
code: "BO",
|
|
446
|
+
flag: "https://www.untitledui.com/images/flags/BO.svg",
|
|
447
|
+
phoneCode: "591",
|
|
448
|
+
phoneMask: "+591-#-###-####"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "Bosnia and Herzegovina",
|
|
452
|
+
code: "BA",
|
|
453
|
+
flag: "https://www.untitledui.com/images/flags/BA.svg",
|
|
454
|
+
phoneCode: "387",
|
|
455
|
+
phoneMask: "+387-##-####"
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: "Botswana",
|
|
459
|
+
code: "BW",
|
|
460
|
+
flag: "https://www.untitledui.com/images/flags/BW.svg",
|
|
461
|
+
phoneCode: "267",
|
|
462
|
+
phoneMask: "+267-##-###-###"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "Brazil",
|
|
466
|
+
code: "BR",
|
|
467
|
+
flag: "https://www.untitledui.com/images/flags/BR.svg",
|
|
468
|
+
phoneCode: "55",
|
|
469
|
+
phoneMask: "+55 (##) 9####-####"
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "Brunei",
|
|
473
|
+
code: "BN",
|
|
474
|
+
flag: "https://www.untitledui.com/images/flags/BN.svg",
|
|
475
|
+
phoneCode: "673",
|
|
476
|
+
phoneMask: "+673-###-####"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: "Bulgaria",
|
|
480
|
+
code: "BG",
|
|
481
|
+
flag: "https://www.untitledui.com/images/flags/BG.svg",
|
|
482
|
+
phoneCode: "359",
|
|
483
|
+
phoneMask: "+359 (###) ###-###"
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "Burkina Faso",
|
|
487
|
+
code: "BF",
|
|
488
|
+
flag: "https://www.untitledui.com/images/flags/BF.svg",
|
|
489
|
+
phoneCode: "226",
|
|
490
|
+
phoneMask: "+226-##-##-####"
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: "Burundi",
|
|
494
|
+
code: "BI",
|
|
495
|
+
flag: "https://www.untitledui.com/images/flags/BI.svg",
|
|
496
|
+
phoneCode: "257",
|
|
497
|
+
phoneMask: "+257-##-##-####"
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
name: "Cambodia",
|
|
501
|
+
code: "KH",
|
|
502
|
+
flag: "https://www.untitledui.com/images/flags/KH.svg",
|
|
503
|
+
phoneCode: "855",
|
|
504
|
+
phoneMask: "+855-##-###-###"
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: "Cameroon",
|
|
508
|
+
code: "CM",
|
|
509
|
+
flag: "https://www.untitledui.com/images/flags/CM.svg",
|
|
510
|
+
phoneCode: "237",
|
|
511
|
+
phoneMask: "+237-####-####"
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "Canada",
|
|
515
|
+
code: "CA",
|
|
516
|
+
flag: "https://www.untitledui.com/images/flags/CA.svg",
|
|
517
|
+
phoneCode: "1",
|
|
518
|
+
phoneMask: "+1 (###) ###-####"
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: "Central African Republic",
|
|
522
|
+
code: "CF",
|
|
523
|
+
flag: "https://www.untitledui.com/images/flags/CF.svg",
|
|
524
|
+
phoneCode: "236",
|
|
525
|
+
phoneMask: "+236-##-##-####"
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
name: "Chad",
|
|
529
|
+
code: "TD",
|
|
530
|
+
flag: "https://www.untitledui.com/images/flags/TD.svg",
|
|
531
|
+
phoneCode: "235",
|
|
532
|
+
phoneMask: "+235-##-##-##-##"
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: "Chile",
|
|
536
|
+
code: "CL",
|
|
537
|
+
flag: "https://www.untitledui.com/images/flags/CL.svg",
|
|
538
|
+
phoneCode: "56",
|
|
539
|
+
phoneMask: "+56-#-####-####"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
name: "China",
|
|
543
|
+
code: "CN",
|
|
544
|
+
flag: "https://www.untitledui.com/images/flags/CN.svg",
|
|
545
|
+
phoneCode: "86",
|
|
546
|
+
phoneMask: "+86-##-#####-#####"
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: "Colombia",
|
|
550
|
+
code: "CO",
|
|
551
|
+
flag: "https://www.untitledui.com/images/flags/CO.svg",
|
|
552
|
+
phoneCode: "57",
|
|
553
|
+
phoneMask: "+57 (###) ###-####"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "Comoros",
|
|
557
|
+
code: "KM",
|
|
558
|
+
flag: "https://www.untitledui.com/images/flags/KM.svg",
|
|
559
|
+
phoneCode: "269",
|
|
560
|
+
phoneMask: "+269-##-#####"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "Costa Rica",
|
|
564
|
+
code: "CR",
|
|
565
|
+
flag: "https://www.untitledui.com/images/flags/CR.svg",
|
|
566
|
+
phoneCode: "506",
|
|
567
|
+
phoneMask: "+506-####-####"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "Croatia",
|
|
571
|
+
code: "HR",
|
|
572
|
+
flag: "https://www.untitledui.com/images/flags/HR.svg",
|
|
573
|
+
phoneCode: "385",
|
|
574
|
+
phoneMask: "+385-##-###-###"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: "Cuba",
|
|
578
|
+
code: "CU",
|
|
579
|
+
flag: "https://www.untitledui.com/images/flags/CU.svg",
|
|
580
|
+
phoneCode: "53",
|
|
581
|
+
phoneMask: "+53-#-###-####"
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "Cyprus",
|
|
585
|
+
code: "CY",
|
|
586
|
+
flag: "https://www.untitledui.com/images/flags/CY.svg",
|
|
587
|
+
phoneCode: "357",
|
|
588
|
+
phoneMask: "+357-##-###-###"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "Czech Republic",
|
|
592
|
+
code: "CZ",
|
|
593
|
+
flag: "https://www.untitledui.com/images/flags/CZ.svg",
|
|
594
|
+
phoneCode: "420",
|
|
595
|
+
phoneMask: "+420 (###) ###-###"
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: "Democratic Republic of the Congo",
|
|
599
|
+
code: "CD",
|
|
600
|
+
flag: "https://www.untitledui.com/images/flags/CD.svg",
|
|
601
|
+
phoneCode: "243",
|
|
602
|
+
phoneMask: "+243 (###) ###-###"
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: "Denmark",
|
|
606
|
+
code: "DK",
|
|
607
|
+
flag: "https://www.untitledui.com/images/flags/DK.svg",
|
|
608
|
+
phoneCode: "45",
|
|
609
|
+
phoneMask: "+45-##-##-##-##"
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
name: "Djibouti",
|
|
613
|
+
code: "DJ",
|
|
614
|
+
flag: "https://www.untitledui.com/images/flags/DJ.svg",
|
|
615
|
+
phoneCode: "253",
|
|
616
|
+
phoneMask: "+253-##-##-##-##"
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "Dominica",
|
|
620
|
+
code: "DM",
|
|
621
|
+
flag: "https://www.untitledui.com/images/flags/DM.svg",
|
|
622
|
+
phoneCode: "+1-767",
|
|
623
|
+
phoneMask: "+1 (767) ###-####"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: "Dominican Republic",
|
|
627
|
+
code: "DO",
|
|
628
|
+
flag: "https://www.untitledui.com/images/flags/DO.svg",
|
|
629
|
+
phoneCode: "+1-809",
|
|
630
|
+
phoneMask: "+1 (809) ###-####"
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
name: "East Timor",
|
|
634
|
+
code: "TL",
|
|
635
|
+
flag: "https://www.untitledui.com/images/flags/TL.svg",
|
|
636
|
+
phoneCode: "670",
|
|
637
|
+
phoneMask: "+670-78#-#####"
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: "Ecuador",
|
|
641
|
+
code: "EC",
|
|
642
|
+
flag: "https://www.untitledui.com/images/flags/EC.svg",
|
|
643
|
+
phoneCode: "593",
|
|
644
|
+
phoneMask: "+593-#-###-####"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: "Egypt",
|
|
648
|
+
code: "EG",
|
|
649
|
+
flag: "https://www.untitledui.com/images/flags/EG.svg",
|
|
650
|
+
phoneCode: "20",
|
|
651
|
+
phoneMask: "+20 (###) ###-####"
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "El Salvador",
|
|
655
|
+
code: "SV",
|
|
656
|
+
flag: "https://www.untitledui.com/images/flags/SV.svg",
|
|
657
|
+
phoneCode: "503",
|
|
658
|
+
phoneMask: "+503-##-##-####"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: "Equatorial Guinea",
|
|
662
|
+
code: "GQ",
|
|
663
|
+
flag: "https://www.untitledui.com/images/flags/GQ.svg",
|
|
664
|
+
phoneCode: "240",
|
|
665
|
+
phoneMask: "+240-##-###-####"
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
name: "Eritrea",
|
|
669
|
+
code: "ER",
|
|
670
|
+
flag: "https://www.untitledui.com/images/flags/ER.svg",
|
|
671
|
+
phoneCode: "291",
|
|
672
|
+
phoneMask: "+291-#-###-###"
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
name: "Estonia",
|
|
676
|
+
code: "EE",
|
|
677
|
+
flag: "https://www.untitledui.com/images/flags/EE.svg",
|
|
678
|
+
phoneCode: "372",
|
|
679
|
+
phoneMask: "+372-###-####"
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: "Eswatini",
|
|
683
|
+
code: "SZ",
|
|
684
|
+
flag: "https://www.untitledui.com/images/flags/SZ.svg",
|
|
685
|
+
phoneCode: "268",
|
|
686
|
+
phoneMask: "+268-##-##-####"
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
name: "Ethiopia",
|
|
690
|
+
code: "ET",
|
|
691
|
+
flag: "https://www.untitledui.com/images/flags/ET.svg",
|
|
692
|
+
phoneCode: "251",
|
|
693
|
+
phoneMask: "+251-##-###-####"
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: "Fiji",
|
|
697
|
+
code: "FJ",
|
|
698
|
+
flag: "https://www.untitledui.com/images/flags/FJ.svg",
|
|
699
|
+
phoneCode: "679",
|
|
700
|
+
phoneMask: "+679-##-#####"
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: "Finland",
|
|
704
|
+
code: "FI",
|
|
705
|
+
flag: "https://www.untitledui.com/images/flags/FI.svg",
|
|
706
|
+
phoneCode: "358",
|
|
707
|
+
phoneMask: "+358 (###) ###-##-##"
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
name: "France",
|
|
711
|
+
code: "FR",
|
|
712
|
+
flag: "https://www.untitledui.com/images/flags/FR.svg",
|
|
713
|
+
phoneCode: "33",
|
|
714
|
+
phoneMask: "+590 (###) ###-###"
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
name: "Gabon",
|
|
718
|
+
code: "GA",
|
|
719
|
+
flag: "https://www.untitledui.com/images/flags/GA.svg",
|
|
720
|
+
phoneCode: "241",
|
|
721
|
+
phoneMask: "+241-#-##-##-##"
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: "Gambia",
|
|
725
|
+
code: "GM",
|
|
726
|
+
flag: "https://www.untitledui.com/images/flags/GM.svg",
|
|
727
|
+
phoneCode: "220",
|
|
728
|
+
phoneMask: "+220 (###) ##-##"
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: "Georgia",
|
|
732
|
+
code: "GE",
|
|
733
|
+
flag: "https://www.untitledui.com/images/flags/GE.svg",
|
|
734
|
+
phoneCode: "995",
|
|
735
|
+
phoneMask: "+995 (###) ###-###"
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: "Germany",
|
|
739
|
+
code: "DE",
|
|
740
|
+
flag: "https://www.untitledui.com/images/flags/DE.svg",
|
|
741
|
+
phoneCode: "49",
|
|
742
|
+
phoneMask: "+49-###-###"
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "Ghana",
|
|
746
|
+
code: "GH",
|
|
747
|
+
flag: "https://www.untitledui.com/images/flags/GH.svg",
|
|
748
|
+
phoneCode: "233",
|
|
749
|
+
phoneMask: "+233 (###) ###-###"
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "Greece",
|
|
753
|
+
code: "GR",
|
|
754
|
+
flag: "https://www.untitledui.com/images/flags/GR.svg",
|
|
755
|
+
phoneCode: "30",
|
|
756
|
+
phoneMask: "+30 (###) ###-####"
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
name: "Grenada",
|
|
760
|
+
code: "GD",
|
|
761
|
+
flag: "https://www.untitledui.com/images/flags/GD.svg",
|
|
762
|
+
phoneCode: "+1-473",
|
|
763
|
+
phoneMask: "+1 (473) ###-####"
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: "Guatemala",
|
|
767
|
+
code: "GT",
|
|
768
|
+
flag: "https://www.untitledui.com/images/flags/GT.svg",
|
|
769
|
+
phoneCode: "502",
|
|
770
|
+
phoneMask: "+502-#-###-####"
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
name: "Guinea",
|
|
774
|
+
code: "GN",
|
|
775
|
+
flag: "https://www.untitledui.com/images/flags/GN.svg",
|
|
776
|
+
phoneCode: "224",
|
|
777
|
+
phoneMask: "+224-##-###-###"
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: "Guinea-Bissau",
|
|
781
|
+
code: "GW",
|
|
782
|
+
flag: "https://www.untitledui.com/images/flags/GW.svg",
|
|
783
|
+
phoneCode: "245",
|
|
784
|
+
phoneMask: "+245-#-######"
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
name: "Guyana",
|
|
788
|
+
code: "GY",
|
|
789
|
+
flag: "https://www.untitledui.com/images/flags/GY.svg",
|
|
790
|
+
phoneCode: "592",
|
|
791
|
+
phoneMask: "+592-###-####"
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: "Haiti",
|
|
795
|
+
code: "HT",
|
|
796
|
+
flag: "https://www.untitledui.com/images/flags/HT.svg",
|
|
797
|
+
phoneCode: "509",
|
|
798
|
+
phoneMask: "+509-##-##-####"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
name: "Honduras",
|
|
802
|
+
code: "HN",
|
|
803
|
+
flag: "https://www.untitledui.com/images/flags/HN.svg",
|
|
804
|
+
phoneCode: "504",
|
|
805
|
+
phoneMask: "+504-####-####"
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
name: "Hungary",
|
|
809
|
+
code: "HU",
|
|
810
|
+
flag: "https://www.untitledui.com/images/flags/HU.svg",
|
|
811
|
+
phoneCode: "36",
|
|
812
|
+
phoneMask: "+36 (###) ###-###"
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
name: "Iceland",
|
|
816
|
+
code: "IS",
|
|
817
|
+
flag: "https://www.untitledui.com/images/flags/IS.svg",
|
|
818
|
+
phoneCode: "354",
|
|
819
|
+
phoneMask: "+354-###-####"
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
name: "India",
|
|
823
|
+
code: "IN",
|
|
824
|
+
flag: "https://www.untitledui.com/images/flags/IN.svg",
|
|
825
|
+
phoneCode: "91",
|
|
826
|
+
phoneMask: "+91 (####) ###-###"
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
name: "Indonesia",
|
|
830
|
+
code: "ID",
|
|
831
|
+
flag: "https://www.untitledui.com/images/flags/ID.svg",
|
|
832
|
+
phoneCode: "62",
|
|
833
|
+
phoneMask: "+62 (8##) ###-##-###"
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: "Iran",
|
|
837
|
+
code: "IR",
|
|
838
|
+
flag: "https://www.untitledui.com/images/flags/IR.svg",
|
|
839
|
+
phoneCode: "98",
|
|
840
|
+
phoneMask: "+98 (###) ###-####"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "Iraq",
|
|
844
|
+
code: "IQ",
|
|
845
|
+
flag: "https://www.untitledui.com/images/flags/IQ.svg",
|
|
846
|
+
phoneCode: "964",
|
|
847
|
+
phoneMask: "+964 (###) ###-####"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
name: "Ireland",
|
|
851
|
+
code: "IE",
|
|
852
|
+
flag: "https://www.untitledui.com/images/flags/IE.svg",
|
|
853
|
+
phoneCode: "353",
|
|
854
|
+
phoneMask: "+353 (###) ###-###"
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
name: "Israel",
|
|
858
|
+
code: "IL",
|
|
859
|
+
flag: "https://www.untitledui.com/images/flags/IL.svg",
|
|
860
|
+
phoneCode: "972",
|
|
861
|
+
phoneMask: "+972-#-###-####"
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
name: "Italy",
|
|
865
|
+
code: "IT",
|
|
866
|
+
flag: "https://www.untitledui.com/images/flags/IT.svg",
|
|
867
|
+
phoneCode: "39",
|
|
868
|
+
phoneMask: "+39 (###) ####-###"
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
name: "Jamaica",
|
|
872
|
+
code: "JM",
|
|
873
|
+
flag: "https://www.untitledui.com/images/flags/JM.svg",
|
|
874
|
+
phoneCode: "+1-876",
|
|
875
|
+
phoneMask: "+1 (876) ###-####"
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
name: "Japan",
|
|
879
|
+
code: "JP",
|
|
880
|
+
flag: "https://www.untitledui.com/images/flags/JP.svg",
|
|
881
|
+
phoneCode: "81",
|
|
882
|
+
phoneMask: "+81 (###) ###-###"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: "Jordan",
|
|
886
|
+
code: "JO",
|
|
887
|
+
flag: "https://www.untitledui.com/images/flags/JO.svg",
|
|
888
|
+
phoneCode: "962",
|
|
889
|
+
phoneMask: "+962-#-####-####"
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
name: "Kazakhstan",
|
|
893
|
+
code: "KZ",
|
|
894
|
+
flag: "https://www.untitledui.com/images/flags/KZ.svg",
|
|
895
|
+
phoneCode: "7",
|
|
896
|
+
phoneMask: "+7 (7##) ###-##-##"
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
name: "Kenya",
|
|
900
|
+
code: "KE",
|
|
901
|
+
flag: "https://www.untitledui.com/images/flags/KE.svg",
|
|
902
|
+
phoneCode: "254",
|
|
903
|
+
phoneMask: "+254-###-######"
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
name: "Kiribati",
|
|
907
|
+
code: "KI",
|
|
908
|
+
flag: "https://www.untitledui.com/images/flags/KI.svg",
|
|
909
|
+
phoneCode: "686",
|
|
910
|
+
phoneMask: "+686-##-###"
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: "Kuwait",
|
|
914
|
+
code: "KW",
|
|
915
|
+
flag: "https://www.untitledui.com/images/flags/KW.svg",
|
|
916
|
+
phoneCode: "965",
|
|
917
|
+
phoneMask: "+965-####-####"
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "Kyrgyzstan",
|
|
921
|
+
code: "KG",
|
|
922
|
+
flag: "https://www.untitledui.com/images/flags/KG.svg",
|
|
923
|
+
phoneCode: "996",
|
|
924
|
+
phoneMask: "+996 (###) ###-###"
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
name: "Laos",
|
|
928
|
+
code: "LA",
|
|
929
|
+
flag: "https://www.untitledui.com/images/flags/LA.svg",
|
|
930
|
+
phoneCode: "856",
|
|
931
|
+
phoneMask: "+856-##-###-###"
|
|
932
|
+
},
|
|
933
|
+
{
|
|
934
|
+
name: "Latvia",
|
|
935
|
+
code: "LV",
|
|
936
|
+
flag: "https://www.untitledui.com/images/flags/LV.svg",
|
|
937
|
+
phoneCode: "371",
|
|
938
|
+
phoneMask: "+371-##-###-###"
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
name: "Lebanon",
|
|
942
|
+
code: "LB",
|
|
943
|
+
flag: "https://www.untitledui.com/images/flags/LB.svg",
|
|
944
|
+
phoneCode: "961",
|
|
945
|
+
phoneMask: "+961-#-###-###"
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: "Lesotho",
|
|
949
|
+
code: "LS",
|
|
950
|
+
flag: "https://www.untitledui.com/images/flags/LS.svg",
|
|
951
|
+
phoneCode: "266",
|
|
952
|
+
phoneMask: "+266-#-###-####"
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: "Liberia",
|
|
956
|
+
code: "LR",
|
|
957
|
+
flag: "https://www.untitledui.com/images/flags/LR.svg",
|
|
958
|
+
phoneCode: "231",
|
|
959
|
+
phoneMask: "+231-##-###-###"
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
name: "Libya",
|
|
963
|
+
code: "LY",
|
|
964
|
+
flag: "https://www.untitledui.com/images/flags/LY.svg",
|
|
965
|
+
phoneCode: "218",
|
|
966
|
+
phoneMask: "+218-21-###-####"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
name: "Liechtenstein",
|
|
970
|
+
code: "LI",
|
|
971
|
+
flag: "https://www.untitledui.com/images/flags/LI.svg",
|
|
972
|
+
phoneCode: "423",
|
|
973
|
+
phoneMask: "+423 (###) ###-####"
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
name: "Lithuania",
|
|
977
|
+
code: "LT",
|
|
978
|
+
flag: "https://www.untitledui.com/images/flags/LT.svg",
|
|
979
|
+
phoneCode: "370",
|
|
980
|
+
phoneMask: "+370 (###) ##-###"
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
name: "Luxembourg",
|
|
984
|
+
code: "LU",
|
|
985
|
+
flag: "https://www.untitledui.com/images/flags/LU.svg",
|
|
986
|
+
phoneCode: "352",
|
|
987
|
+
phoneMask: "+352 (###) ###-###"
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
name: "Madagascar",
|
|
991
|
+
code: "MG",
|
|
992
|
+
flag: "https://www.untitledui.com/images/flags/MG.svg",
|
|
993
|
+
phoneCode: "261",
|
|
994
|
+
phoneMask: "+261-##-##-#####"
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
name: "Malawi",
|
|
998
|
+
code: "MW",
|
|
999
|
+
flag: "https://www.untitledui.com/images/flags/MW.svg",
|
|
1000
|
+
phoneCode: "265",
|
|
1001
|
+
phoneMask: "+265-#-####-####"
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
name: "Malaysia",
|
|
1005
|
+
code: "MY",
|
|
1006
|
+
flag: "https://www.untitledui.com/images/flags/MY.svg",
|
|
1007
|
+
phoneCode: "60",
|
|
1008
|
+
phoneMask: "+60-#-###-###"
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
name: "Maldives",
|
|
1012
|
+
code: "MV",
|
|
1013
|
+
flag: "https://www.untitledui.com/images/flags/MV.svg",
|
|
1014
|
+
phoneCode: "960",
|
|
1015
|
+
phoneMask: "+960-###-####"
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
name: "Mali",
|
|
1019
|
+
code: "ML",
|
|
1020
|
+
flag: "https://www.untitledui.com/images/flags/ML.svg",
|
|
1021
|
+
phoneCode: "223",
|
|
1022
|
+
phoneMask: "+223-##-##-####"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "Malta",
|
|
1026
|
+
code: "MT",
|
|
1027
|
+
flag: "https://www.untitledui.com/images/flags/MT.svg",
|
|
1028
|
+
phoneCode: "356",
|
|
1029
|
+
phoneMask: "+356-####-####"
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
name: "Marshall Islands",
|
|
1033
|
+
code: "MH",
|
|
1034
|
+
flag: "https://www.untitledui.com/images/flags/MH.svg",
|
|
1035
|
+
phoneCode: "692",
|
|
1036
|
+
phoneMask: "+692-###-####"
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
name: "Mauritania",
|
|
1040
|
+
code: "MR",
|
|
1041
|
+
flag: "https://www.untitledui.com/images/flags/MR.svg",
|
|
1042
|
+
phoneCode: "222",
|
|
1043
|
+
phoneMask: "+222-##-##-####"
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
name: "Mauritius",
|
|
1047
|
+
code: "MU",
|
|
1048
|
+
flag: "https://www.untitledui.com/images/flags/MU.svg",
|
|
1049
|
+
phoneCode: "230",
|
|
1050
|
+
phoneMask: "+230-###-####"
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
name: "Mexico",
|
|
1054
|
+
code: "MX",
|
|
1055
|
+
flag: "https://www.untitledui.com/images/flags/MX.svg",
|
|
1056
|
+
phoneCode: "52",
|
|
1057
|
+
phoneMask: "+52-##-##-####"
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "Micronesia",
|
|
1061
|
+
code: "FM",
|
|
1062
|
+
flag: "https://www.untitledui.com/images/flags/FM.svg",
|
|
1063
|
+
phoneCode: "691",
|
|
1064
|
+
phoneMask: "+691-###-####"
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
name: "Moldova",
|
|
1068
|
+
code: "MD",
|
|
1069
|
+
flag: "https://www.untitledui.com/images/flags/MD.svg",
|
|
1070
|
+
phoneCode: "373",
|
|
1071
|
+
phoneMask: "+373-####-####"
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
name: "Monaco",
|
|
1075
|
+
code: "MC",
|
|
1076
|
+
flag: "https://www.untitledui.com/images/flags/MC.svg",
|
|
1077
|
+
phoneCode: "377",
|
|
1078
|
+
phoneMask: "+377-##-###-###"
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
name: "Mongolia",
|
|
1082
|
+
code: "MN",
|
|
1083
|
+
flag: "https://www.untitledui.com/images/flags/MN.svg",
|
|
1084
|
+
phoneCode: "976",
|
|
1085
|
+
phoneMask: "+976-##-##-####"
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
name: "Montenegro",
|
|
1089
|
+
code: "ME",
|
|
1090
|
+
flag: "https://www.untitledui.com/images/flags/ME.svg",
|
|
1091
|
+
phoneCode: "382",
|
|
1092
|
+
phoneMask: "+382-##-###-###"
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
name: "Morocco",
|
|
1096
|
+
code: "MA",
|
|
1097
|
+
flag: "https://www.untitledui.com/images/flags/MA.svg",
|
|
1098
|
+
phoneCode: "212",
|
|
1099
|
+
phoneMask: "+212-##-####-###"
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: "Mozambique",
|
|
1103
|
+
code: "MZ",
|
|
1104
|
+
flag: "https://www.untitledui.com/images/flags/MZ.svg",
|
|
1105
|
+
phoneCode: "258",
|
|
1106
|
+
phoneMask: "+258-##-###-###"
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
name: "Myanmar",
|
|
1110
|
+
code: "MM",
|
|
1111
|
+
flag: "https://www.untitledui.com/images/flags/MM.svg",
|
|
1112
|
+
phoneCode: "95",
|
|
1113
|
+
phoneMask: "+95-###-###"
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
name: "Namibia",
|
|
1117
|
+
code: "NA",
|
|
1118
|
+
flag: "https://www.untitledui.com/images/flags/NA.svg",
|
|
1119
|
+
phoneCode: "264",
|
|
1120
|
+
phoneMask: "+264-##-###-####"
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
name: "Nauru",
|
|
1124
|
+
code: "NR",
|
|
1125
|
+
flag: "https://www.untitledui.com/images/flags/NR.svg",
|
|
1126
|
+
phoneCode: "674",
|
|
1127
|
+
phoneMask: "+674-###-####"
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: "Nepal",
|
|
1131
|
+
code: "NP",
|
|
1132
|
+
flag: "https://www.untitledui.com/images/flags/NP.svg",
|
|
1133
|
+
phoneCode: "977",
|
|
1134
|
+
phoneMask: "+977-##-###-###"
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
name: "Netherlands",
|
|
1138
|
+
code: "NL",
|
|
1139
|
+
flag: "https://www.untitledui.com/images/flags/NL.svg",
|
|
1140
|
+
phoneCode: "31",
|
|
1141
|
+
phoneMask: "+31-##-###-####"
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
name: "New Zealand",
|
|
1145
|
+
code: "NZ",
|
|
1146
|
+
flag: "https://www.untitledui.com/images/flags/NZ.svg",
|
|
1147
|
+
phoneCode: "64",
|
|
1148
|
+
phoneMask: "+64 (###) ###-####"
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
name: "Nicaragua",
|
|
1152
|
+
code: "NI",
|
|
1153
|
+
flag: "https://www.untitledui.com/images/flags/NI.svg",
|
|
1154
|
+
phoneCode: "505",
|
|
1155
|
+
phoneMask: "+505-####-####"
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
name: "Niger",
|
|
1159
|
+
code: "NE",
|
|
1160
|
+
flag: "https://www.untitledui.com/images/flags/NE.svg",
|
|
1161
|
+
phoneCode: "227",
|
|
1162
|
+
phoneMask: "+227-##-##-####"
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
name: "Nigeria",
|
|
1166
|
+
code: "NG",
|
|
1167
|
+
flag: "https://www.untitledui.com/images/flags/NG.svg",
|
|
1168
|
+
phoneCode: "234",
|
|
1169
|
+
phoneMask: "+234 (###) ###-####"
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
name: "North Korea",
|
|
1173
|
+
code: "KP",
|
|
1174
|
+
flag: "https://www.untitledui.com/images/flags/KP.svg",
|
|
1175
|
+
phoneCode: "850",
|
|
1176
|
+
phoneMask: "+850-####-#############"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
name: "North Macedonia",
|
|
1180
|
+
code: "MK",
|
|
1181
|
+
flag: "https://www.untitledui.com/images/flags/MK.svg",
|
|
1182
|
+
phoneCode: "389",
|
|
1183
|
+
phoneMask: "+389-##-###-###"
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
name: "Norway",
|
|
1187
|
+
code: "NO",
|
|
1188
|
+
flag: "https://www.untitledui.com/images/flags/NO.svg",
|
|
1189
|
+
phoneCode: "47",
|
|
1190
|
+
phoneMask: "+47 (###) ##-###"
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
name: "Oman",
|
|
1194
|
+
code: "OM",
|
|
1195
|
+
flag: "https://www.untitledui.com/images/flags/OM.svg",
|
|
1196
|
+
phoneCode: "968",
|
|
1197
|
+
phoneMask: "+968-##-###-###"
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
name: "Pakistan",
|
|
1201
|
+
code: "PK",
|
|
1202
|
+
flag: "https://www.untitledui.com/images/flags/PK.svg",
|
|
1203
|
+
phoneCode: "92",
|
|
1204
|
+
phoneMask: "+92 (###) ###-####"
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
name: "Palau",
|
|
1208
|
+
code: "PW",
|
|
1209
|
+
flag: "https://www.untitledui.com/images/flags/PW.svg",
|
|
1210
|
+
phoneCode: "680",
|
|
1211
|
+
phoneMask: "+680-###-####"
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
name: "Panama",
|
|
1215
|
+
code: "PA",
|
|
1216
|
+
flag: "https://www.untitledui.com/images/flags/PA.svg",
|
|
1217
|
+
phoneCode: "507",
|
|
1218
|
+
phoneMask: "+507-###-####"
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
name: "Papua New Guinea",
|
|
1222
|
+
code: "PG",
|
|
1223
|
+
flag: "https://www.untitledui.com/images/flags/PG.svg",
|
|
1224
|
+
phoneCode: "675",
|
|
1225
|
+
phoneMask: "+675 (###) ##-###"
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
name: "Paraguay",
|
|
1229
|
+
code: "PY",
|
|
1230
|
+
flag: "https://www.untitledui.com/images/flags/PY.svg",
|
|
1231
|
+
phoneCode: "595",
|
|
1232
|
+
phoneMask: "+595 (###) ###-###"
|
|
1233
|
+
},
|
|
1234
|
+
{
|
|
1235
|
+
name: "Peru",
|
|
1236
|
+
code: "PE",
|
|
1237
|
+
flag: "https://www.untitledui.com/images/flags/PE.svg",
|
|
1238
|
+
phoneCode: "51",
|
|
1239
|
+
phoneMask: "+51 (###) ###-###"
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
name: "Philippines",
|
|
1243
|
+
code: "PH",
|
|
1244
|
+
flag: "https://www.untitledui.com/images/flags/PH.svg",
|
|
1245
|
+
phoneCode: "63",
|
|
1246
|
+
phoneMask: "+63 (###) ###-####"
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
name: "Poland",
|
|
1250
|
+
code: "PL",
|
|
1251
|
+
flag: "https://www.untitledui.com/images/flags/PL.svg",
|
|
1252
|
+
phoneCode: "48",
|
|
1253
|
+
phoneMask: "+48 (###) ###-###"
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
name: "Portugal",
|
|
1257
|
+
code: "PT",
|
|
1258
|
+
flag: "https://www.untitledui.com/images/flags/PT.svg",
|
|
1259
|
+
phoneCode: "351",
|
|
1260
|
+
phoneMask: "+351-##-###-####"
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
name: "Qatar",
|
|
1264
|
+
code: "QA",
|
|
1265
|
+
flag: "https://www.untitledui.com/images/flags/QA.svg",
|
|
1266
|
+
phoneCode: "974",
|
|
1267
|
+
phoneMask: "+974-####-####"
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
name: "Romania",
|
|
1271
|
+
code: "RO",
|
|
1272
|
+
flag: "https://www.untitledui.com/images/flags/RO.svg",
|
|
1273
|
+
phoneCode: "40",
|
|
1274
|
+
phoneMask: "+40-##-###-####"
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
name: "Russia",
|
|
1278
|
+
code: "RU",
|
|
1279
|
+
flag: "https://www.untitledui.com/images/flags/RU.svg",
|
|
1280
|
+
phoneCode: "7",
|
|
1281
|
+
phoneMask: "+7 (###) ###-##-##"
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
name: "Rwanda",
|
|
1285
|
+
code: "RW",
|
|
1286
|
+
flag: "https://www.untitledui.com/images/flags/RW.svg",
|
|
1287
|
+
phoneCode: "250",
|
|
1288
|
+
phoneMask: "+250 (###) ###-###"
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
name: "Saint Kitts and Nevis",
|
|
1292
|
+
code: "KN",
|
|
1293
|
+
flag: "https://www.untitledui.com/images/flags/KN.svg",
|
|
1294
|
+
phoneCode: "+1-869",
|
|
1295
|
+
phoneMask: "+1 (869) ###-####"
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
name: "Saint Lucia",
|
|
1299
|
+
code: "LC",
|
|
1300
|
+
flag: "https://www.untitledui.com/images/flags/LC.svg",
|
|
1301
|
+
phoneCode: "+1-758",
|
|
1302
|
+
phoneMask: "+1 (758) ###-####"
|
|
1303
|
+
},
|
|
1304
|
+
{
|
|
1305
|
+
name: "Saint Vincent and the Grenadines",
|
|
1306
|
+
code: "VC",
|
|
1307
|
+
flag: "https://www.untitledui.com/images/flags/VC.svg",
|
|
1308
|
+
phoneCode: "+1-784",
|
|
1309
|
+
phoneMask: "+1 (784) ###-####"
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
name: "Samoa",
|
|
1313
|
+
code: "WS",
|
|
1314
|
+
flag: "https://www.untitledui.com/images/flags/WS.svg",
|
|
1315
|
+
phoneCode: "685",
|
|
1316
|
+
phoneMask: "+685-##-####"
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
name: "San Marino",
|
|
1320
|
+
code: "SM",
|
|
1321
|
+
flag: "https://www.untitledui.com/images/flags/SM.svg",
|
|
1322
|
+
phoneCode: "378",
|
|
1323
|
+
phoneMask: "+378-####-######"
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
name: "Sao Tome and Principe",
|
|
1327
|
+
code: "ST",
|
|
1328
|
+
flag: "https://www.untitledui.com/images/flags/ST.svg",
|
|
1329
|
+
phoneCode: "239",
|
|
1330
|
+
phoneMask: "+239-##-#####"
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
name: "Saudi Arabia",
|
|
1334
|
+
code: "SA",
|
|
1335
|
+
flag: "https://www.untitledui.com/images/flags/SA.svg",
|
|
1336
|
+
phoneCode: "966",
|
|
1337
|
+
phoneMask: "+966-#-###-####"
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
name: "Senegal",
|
|
1341
|
+
code: "SN",
|
|
1342
|
+
flag: "https://www.untitledui.com/images/flags/SN.svg",
|
|
1343
|
+
phoneCode: "221",
|
|
1344
|
+
phoneMask: "+221-##-###-####"
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
name: "Serbia",
|
|
1348
|
+
code: "RS",
|
|
1349
|
+
flag: "https://www.untitledui.com/images/flags/RS.svg",
|
|
1350
|
+
phoneCode: "381",
|
|
1351
|
+
phoneMask: "+381-##-###-####"
|
|
1352
|
+
},
|
|
1353
|
+
{
|
|
1354
|
+
name: "Seychelles",
|
|
1355
|
+
code: "SC",
|
|
1356
|
+
flag: "https://www.untitledui.com/images/flags/SC.svg",
|
|
1357
|
+
phoneCode: "248",
|
|
1358
|
+
phoneMask: "+248-#-###-###"
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
name: "Sierra Leone",
|
|
1362
|
+
code: "SL",
|
|
1363
|
+
flag: "https://www.untitledui.com/images/flags/SL.svg",
|
|
1364
|
+
phoneCode: "232",
|
|
1365
|
+
phoneMask: "+232-##-######"
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
name: "Singapore",
|
|
1369
|
+
code: "SG",
|
|
1370
|
+
flag: "https://www.untitledui.com/images/flags/SG.svg",
|
|
1371
|
+
phoneCode: "65",
|
|
1372
|
+
phoneMask: "+65-####-####"
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
name: "Slovakia",
|
|
1376
|
+
code: "SK",
|
|
1377
|
+
flag: "https://www.untitledui.com/images/flags/SK.svg",
|
|
1378
|
+
phoneCode: "421",
|
|
1379
|
+
phoneMask: "+421 (###) ###-###"
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
name: "Slovenia",
|
|
1383
|
+
code: "SI",
|
|
1384
|
+
flag: "https://www.untitledui.com/images/flags/SI.svg",
|
|
1385
|
+
phoneCode: "386",
|
|
1386
|
+
phoneMask: "+386-##-###-###"
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
name: "Solomon Islands",
|
|
1390
|
+
code: "SB",
|
|
1391
|
+
flag: "https://www.untitledui.com/images/flags/SB.svg",
|
|
1392
|
+
phoneCode: "677",
|
|
1393
|
+
phoneMask: "+677-#####"
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
name: "Somalia",
|
|
1397
|
+
code: "SO",
|
|
1398
|
+
flag: "https://www.untitledui.com/images/flags/SO.svg",
|
|
1399
|
+
phoneCode: "252",
|
|
1400
|
+
phoneMask: "+252-#-###-###"
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
name: "South Africa",
|
|
1404
|
+
code: "ZA",
|
|
1405
|
+
flag: "https://www.untitledui.com/images/flags/ZA.svg",
|
|
1406
|
+
phoneCode: "27",
|
|
1407
|
+
phoneMask: "+27-##-###-####"
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: "South Korea",
|
|
1411
|
+
code: "KR",
|
|
1412
|
+
flag: "https://www.untitledui.com/images/flags/KR.svg",
|
|
1413
|
+
phoneCode: "82",
|
|
1414
|
+
phoneMask: "+82-##-###-####"
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
name: "South Sudan",
|
|
1418
|
+
code: "SS",
|
|
1419
|
+
flag: "https://www.untitledui.com/images/flags/SS.svg",
|
|
1420
|
+
phoneCode: "211",
|
|
1421
|
+
phoneMask: "+211-##-###-####"
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
name: "Spain",
|
|
1425
|
+
code: "ES",
|
|
1426
|
+
flag: "https://www.untitledui.com/images/flags/ES.svg",
|
|
1427
|
+
phoneCode: "34",
|
|
1428
|
+
phoneMask: "+34 (###) ###-###"
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: "Sri Lanka",
|
|
1432
|
+
code: "LK",
|
|
1433
|
+
flag: "https://www.untitledui.com/images/flags/LK.svg",
|
|
1434
|
+
phoneCode: "94",
|
|
1435
|
+
phoneMask: "+94-##-###-####"
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
name: "Suriname",
|
|
1439
|
+
code: "SR",
|
|
1440
|
+
flag: "https://www.untitledui.com/images/flags/SR.svg",
|
|
1441
|
+
phoneCode: "597",
|
|
1442
|
+
phoneMask: "+597-###-###"
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
name: "Sweden",
|
|
1446
|
+
code: "SE",
|
|
1447
|
+
flag: "https://www.untitledui.com/images/flags/SE.svg",
|
|
1448
|
+
phoneCode: "46",
|
|
1449
|
+
phoneMask: "+46-##-###-####"
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
name: "Switzerland",
|
|
1453
|
+
code: "CH",
|
|
1454
|
+
flag: "https://www.untitledui.com/images/flags/CH.svg",
|
|
1455
|
+
phoneCode: "41",
|
|
1456
|
+
phoneMask: "+41-##-###-####"
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: "Syria",
|
|
1460
|
+
code: "SY",
|
|
1461
|
+
flag: "https://www.untitledui.com/images/flags/SY.svg",
|
|
1462
|
+
phoneCode: "963",
|
|
1463
|
+
phoneMask: "+963-##-####-###"
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
name: "Tajikistan",
|
|
1467
|
+
code: "TJ",
|
|
1468
|
+
flag: "https://www.untitledui.com/images/flags/TJ.svg",
|
|
1469
|
+
phoneCode: "992",
|
|
1470
|
+
phoneMask: "+992-##-###-####"
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "Tanzania",
|
|
1474
|
+
code: "TZ",
|
|
1475
|
+
flag: "https://www.untitledui.com/images/flags/TZ.svg",
|
|
1476
|
+
phoneCode: "255",
|
|
1477
|
+
phoneMask: "+255-##-###-####"
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
name: "Thailand",
|
|
1481
|
+
code: "TH",
|
|
1482
|
+
flag: "https://www.untitledui.com/images/flags/TH.svg",
|
|
1483
|
+
phoneCode: "66",
|
|
1484
|
+
phoneMask: "+66-##-###-###"
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
name: "Togo",
|
|
1488
|
+
code: "TG",
|
|
1489
|
+
flag: "https://www.untitledui.com/images/flags/TG.svg",
|
|
1490
|
+
phoneCode: "228",
|
|
1491
|
+
phoneMask: "+228-##-###-###"
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
name: "Tonga",
|
|
1495
|
+
code: "TO",
|
|
1496
|
+
flag: "https://www.untitledui.com/images/flags/TO.svg",
|
|
1497
|
+
phoneCode: "676",
|
|
1498
|
+
phoneMask: "+676-#####"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
name: "Trinidad and Tobago",
|
|
1502
|
+
code: "TT",
|
|
1503
|
+
flag: "https://www.untitledui.com/images/flags/TT.svg",
|
|
1504
|
+
phoneCode: "+1-868",
|
|
1505
|
+
phoneMask: "+1 (868) ###-####"
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
name: "Tunisia",
|
|
1509
|
+
code: "TN",
|
|
1510
|
+
flag: "https://www.untitledui.com/images/flags/TN.svg",
|
|
1511
|
+
phoneCode: "216",
|
|
1512
|
+
phoneMask: "+216-##-###-###"
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
name: "Turkey",
|
|
1516
|
+
code: "TR",
|
|
1517
|
+
flag: "https://www.untitledui.com/images/flags/TR.svg",
|
|
1518
|
+
phoneCode: "90",
|
|
1519
|
+
phoneMask: "+90 (###) ###-####"
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
name: "Turkmenistan",
|
|
1523
|
+
code: "TM",
|
|
1524
|
+
flag: "https://www.untitledui.com/images/flags/TM.svg",
|
|
1525
|
+
phoneCode: "993",
|
|
1526
|
+
phoneMask: "+993-#-###-####"
|
|
1527
|
+
},
|
|
1528
|
+
{
|
|
1529
|
+
name: "Tuvalu",
|
|
1530
|
+
code: "TV",
|
|
1531
|
+
flag: "https://www.untitledui.com/images/flags/TV.svg",
|
|
1532
|
+
phoneCode: "688",
|
|
1533
|
+
phoneMask: "+688-2####"
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
name: "Uganda",
|
|
1537
|
+
code: "UG",
|
|
1538
|
+
flag: "https://www.untitledui.com/images/flags/UG.svg",
|
|
1539
|
+
phoneCode: "256",
|
|
1540
|
+
phoneMask: "+256 (###) ###-###"
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
name: "Ukraine",
|
|
1544
|
+
code: "UA",
|
|
1545
|
+
flag: "https://www.untitledui.com/images/flags/UA.svg",
|
|
1546
|
+
phoneCode: "380",
|
|
1547
|
+
phoneMask: "+380 (##) ###-##-##"
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
name: "United Arab Emirates",
|
|
1551
|
+
code: "AE",
|
|
1552
|
+
flag: "https://www.untitledui.com/images/flags/AE.svg",
|
|
1553
|
+
phoneCode: "971",
|
|
1554
|
+
phoneMask: "+971-#-###-####"
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
name: "United Kingdom",
|
|
1558
|
+
code: "GB",
|
|
1559
|
+
flag: "https://www.untitledui.com/images/flags/GB.svg",
|
|
1560
|
+
phoneCode: "44"
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
name: "United States",
|
|
1564
|
+
code: "US",
|
|
1565
|
+
flag: "https://www.untitledui.com/images/flags/US.svg",
|
|
1566
|
+
phoneCode: "1",
|
|
1567
|
+
phoneMask: "+1 (###) ###-####"
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
name: "Uruguay",
|
|
1571
|
+
code: "UY",
|
|
1572
|
+
flag: "https://www.untitledui.com/images/flags/UY.svg",
|
|
1573
|
+
phoneCode: "598",
|
|
1574
|
+
phoneMask: "+598-#-###-##-##"
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
name: "Uzbekistan",
|
|
1578
|
+
code: "UZ",
|
|
1579
|
+
flag: "https://www.untitledui.com/images/flags/UZ.svg",
|
|
1580
|
+
phoneCode: "998",
|
|
1581
|
+
phoneMask: "+998-##-###-####"
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
name: "Vanuatu",
|
|
1585
|
+
code: "VU",
|
|
1586
|
+
flag: "https://www.untitledui.com/images/flags/VU.svg",
|
|
1587
|
+
phoneCode: "678",
|
|
1588
|
+
phoneMask: "+678-#####"
|
|
1589
|
+
},
|
|
1590
|
+
{
|
|
1591
|
+
name: "Venezuela",
|
|
1592
|
+
code: "VE",
|
|
1593
|
+
flag: "https://www.untitledui.com/images/flags/VE.svg",
|
|
1594
|
+
phoneCode: "58",
|
|
1595
|
+
phoneMask: "+58 (###) ###-####"
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
name: "Vietnam",
|
|
1599
|
+
code: "VN",
|
|
1600
|
+
flag: "https://www.untitledui.com/images/flags/VN.svg",
|
|
1601
|
+
phoneCode: "84",
|
|
1602
|
+
phoneMask: "+84 (###) ####-###"
|
|
1603
|
+
},
|
|
1604
|
+
{
|
|
1605
|
+
name: "Yemen",
|
|
1606
|
+
code: "YE",
|
|
1607
|
+
flag: "https://www.untitledui.com/images/flags/YE.svg",
|
|
1608
|
+
phoneCode: "967",
|
|
1609
|
+
phoneMask: "+967-##-###-###"
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
name: "Zambia",
|
|
1613
|
+
code: "ZM",
|
|
1614
|
+
flag: "https://www.untitledui.com/images/flags/ZM.svg",
|
|
1615
|
+
phoneCode: "260",
|
|
1616
|
+
phoneMask: "+260-##-###-####"
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
name: "Zimbabwe",
|
|
1620
|
+
code: "ZW",
|
|
1621
|
+
flag: "https://www.untitledui.com/images/flags/ZW.svg",
|
|
1622
|
+
phoneCode: "263",
|
|
1623
|
+
phoneMask: "+263-#-######"
|
|
1624
|
+
}
|
|
1625
|
+
];
|
|
1626
|
+
var phoneCodeOptions = countries.map((country) => ({
|
|
1627
|
+
id: country.code,
|
|
1628
|
+
label: country.code
|
|
1629
|
+
}));
|
|
1630
|
+
var countriesOptions = countries.map((country) => ({
|
|
1631
|
+
id: country.code,
|
|
1632
|
+
label: country.name,
|
|
1633
|
+
icon: (props) => {
|
|
1634
|
+
const restProps = __objRest(props, []);
|
|
1635
|
+
return /* @__PURE__ */ React.createElement(Image, __spreadProps(__spreadValues({}, restProps), { src: country.flag, alt: `${country.name} flag`, width: 20, height: 15 }));
|
|
1636
|
+
}
|
|
1637
|
+
}));
|
|
1638
|
+
|
|
1639
|
+
// src/utils/phone-helpers.ts
|
|
1640
|
+
function getNationalMask(country) {
|
|
1641
|
+
if (!(country == null ? void 0 : country.phoneMask)) return "";
|
|
1642
|
+
const code = country.phoneCode.startsWith("+") ? country.phoneCode : `+${country.phoneCode}`;
|
|
1643
|
+
const escaped = code.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1644
|
+
return country.phoneMask.replace(new RegExp(`^\\s*${escaped}[\\s-]*`), "").trim();
|
|
1645
|
+
}
|
|
1646
|
+
function formatDigitsToMask(digits, mask) {
|
|
1647
|
+
if (digits.length === 0) return "";
|
|
1648
|
+
let i = 0;
|
|
1649
|
+
let out = "";
|
|
1650
|
+
for (const c of mask) {
|
|
1651
|
+
if (c === "#") {
|
|
1652
|
+
if (i < digits.length) out += digits[i++];
|
|
1653
|
+
else break;
|
|
1654
|
+
} else if (i < digits.length) {
|
|
1655
|
+
out += c;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return out;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// src/tracking/firePixelEvent.ts
|
|
1662
|
+
var STORAGE_KEY = "ks_pud";
|
|
1663
|
+
async function sha256Hex(str) {
|
|
1664
|
+
const buffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(str));
|
|
1665
|
+
return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1666
|
+
}
|
|
1667
|
+
function getFbq() {
|
|
1668
|
+
if (typeof window === "undefined") return void 0;
|
|
1669
|
+
return window.fbq;
|
|
1670
|
+
}
|
|
1671
|
+
function getRegisteredPixelIds() {
|
|
1672
|
+
var _a;
|
|
1673
|
+
return (_a = window.__ks_pixel_ids) != null ? _a : [];
|
|
1674
|
+
}
|
|
1675
|
+
function applyStoredUserData(fbq) {
|
|
1676
|
+
try {
|
|
1677
|
+
const raw = sessionStorage.getItem(STORAGE_KEY);
|
|
1678
|
+
if (!raw) return;
|
|
1679
|
+
const hashed = JSON.parse(raw);
|
|
1680
|
+
if (Object.keys(hashed).length === 0) return;
|
|
1681
|
+
getRegisteredPixelIds().forEach((id) => fbq("init", id, hashed));
|
|
1682
|
+
} catch (e) {
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
async function setPixelUserData(userData) {
|
|
1686
|
+
const hashed = {};
|
|
1687
|
+
if (userData.email) {
|
|
1688
|
+
hashed.em = await sha256Hex(userData.email.trim().toLowerCase());
|
|
1689
|
+
}
|
|
1690
|
+
if (userData.phone) {
|
|
1691
|
+
const digits = userData.phone.replace(/\D/g, "");
|
|
1692
|
+
if (digits) hashed.ph = await sha256Hex(digits);
|
|
1693
|
+
}
|
|
1694
|
+
if (Object.keys(hashed).length === 0) return;
|
|
1695
|
+
try {
|
|
1696
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(hashed));
|
|
1697
|
+
} catch (e) {
|
|
1698
|
+
}
|
|
1699
|
+
const fbq = getFbq();
|
|
1700
|
+
if (fbq) {
|
|
1701
|
+
getRegisteredPixelIds().forEach((id) => fbq("init", id, hashed));
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
function firePixelEvent(event, params, eventId) {
|
|
1705
|
+
const fbq = getFbq();
|
|
1706
|
+
if (!fbq) {
|
|
1707
|
+
console.debug("[MetaPixel] skipped \u2014 fbq not loaded", { event });
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
applyStoredUserData(fbq);
|
|
1711
|
+
const normalized = {};
|
|
1712
|
+
if (params == null ? void 0 : params.contentName) normalized.content_name = params.contentName;
|
|
1713
|
+
if (params == null ? void 0 : params.contentCategory) normalized.content_category = params.contentCategory;
|
|
1714
|
+
const customData = Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
1715
|
+
const eventData = eventId ? { eventID: eventId } : void 0;
|
|
1716
|
+
console.debug("[MetaPixel]", event, normalized, eventId ? { eventID: eventId } : "");
|
|
1717
|
+
fbq("track", event, customData, eventData);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/tracking/captureEvent.ts
|
|
1721
|
+
import posthog from "posthog-js";
|
|
1722
|
+
function captureEvent(event, ...args) {
|
|
1723
|
+
posthog.capture(event, args[0]);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/design_system/portal/LoginForm.tsx
|
|
1727
|
+
var inputClass = "block w-full rounded-input border border-primary bg-primary px-3 py-2.5 text-base text-primary placeholder-quaternary focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand transition-colors";
|
|
1728
|
+
var labelClass = "block text-sm text-secondary mb-1";
|
|
1729
|
+
function LoginForm({ onSuccess, onClose }) {
|
|
1730
|
+
const router = useRouter2();
|
|
1731
|
+
const [step, setStep] = useState("identifier");
|
|
1732
|
+
const [phoneValue, setPhoneValue] = useState("");
|
|
1733
|
+
const [selectedCountry, setSelectedCountry] = useState("US");
|
|
1734
|
+
const [verificationCode, setVerificationCode] = useState("");
|
|
1735
|
+
const [verificationToken, setVerificationToken] = useState(null);
|
|
1736
|
+
const [firstName, setFirstName] = useState("");
|
|
1737
|
+
const [lastName, setLastName] = useState("");
|
|
1738
|
+
const [email, setEmail] = useState("");
|
|
1739
|
+
const [resendAvailableAt, setResendAvailableAt] = useState(null);
|
|
1740
|
+
const [secondsUntilResend, setSecondsUntilResend] = useState(0);
|
|
1741
|
+
const [error, setError] = useState(null);
|
|
1742
|
+
const [loading, setLoading] = useState(false);
|
|
1743
|
+
useEffect2(() => {
|
|
1744
|
+
captureEvent("portal_login_started");
|
|
1745
|
+
}, []);
|
|
1746
|
+
useEffect2(() => {
|
|
1747
|
+
const funnelStep = step === "identifier" ? "identifier" : step === "verify" ? "signin" : "signup";
|
|
1748
|
+
captureEvent("portal_login_step_viewed", { step: funnelStep });
|
|
1749
|
+
}, [step]);
|
|
1750
|
+
const country = useMemo(() => countries.find((c) => c.code === selectedCountry), [selectedCountry]);
|
|
1751
|
+
const phoneCode = country ? country.phoneCode.startsWith("+") ? country.phoneCode : `+${country.phoneCode}` : "+1";
|
|
1752
|
+
const nationalMask = useMemo(() => getNationalMask(country), [country]);
|
|
1753
|
+
const nationalPlaceholder = nationalMask ? nationalMask.replace(/#/g, "0") : "(555) 000-0000";
|
|
1754
|
+
const countryOptions = useMemo(
|
|
1755
|
+
() => countries.map((c) => ({ label: c.phoneCode.startsWith("+") ? c.phoneCode : `+${c.phoneCode}`, value: c.code })),
|
|
1756
|
+
[]
|
|
1757
|
+
);
|
|
1758
|
+
const phoneDigits = phoneValue.replace(/\D/g, "");
|
|
1759
|
+
const fullPhone = phoneDigits.length > 0 ? `${phoneCode}${phoneDigits}` : null;
|
|
1760
|
+
useEffect2(() => {
|
|
1761
|
+
if (!resendAvailableAt) {
|
|
1762
|
+
setSecondsUntilResend(0);
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const tick = () => {
|
|
1766
|
+
const remaining = Math.max(0, Math.ceil((resendAvailableAt - Date.now()) / 1e3));
|
|
1767
|
+
setSecondsUntilResend(remaining);
|
|
1768
|
+
};
|
|
1769
|
+
tick();
|
|
1770
|
+
const interval = window.setInterval(tick, 1e3);
|
|
1771
|
+
return () => window.clearInterval(interval);
|
|
1772
|
+
}, [resendAvailableAt]);
|
|
1773
|
+
const phoneInput = (value, onChange, required = false) => /* @__PURE__ */ React3.createElement("div", { className: "flex rounded-input border border-primary overflow-hidden focus-within:border-brand focus-within:ring-1 focus-within:ring-brand transition-colors" }, /* @__PURE__ */ React3.createElement(
|
|
1774
|
+
"select",
|
|
1775
|
+
{
|
|
1776
|
+
value: selectedCountry,
|
|
1777
|
+
onChange: (e) => {
|
|
1778
|
+
setSelectedCountry(e.target.value);
|
|
1779
|
+
onChange("");
|
|
1780
|
+
},
|
|
1781
|
+
className: "border-r border-secondary bg-secondary px-2 py-2.5 text-base text-secondary focus:outline-none",
|
|
1782
|
+
"aria-label": "Country code"
|
|
1783
|
+
},
|
|
1784
|
+
countryOptions.map((opt) => /* @__PURE__ */ React3.createElement("option", { key: opt.value, value: opt.value }, opt.label))
|
|
1785
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1786
|
+
"input",
|
|
1787
|
+
{
|
|
1788
|
+
type: "tel",
|
|
1789
|
+
value,
|
|
1790
|
+
onChange: (e) => {
|
|
1791
|
+
const digits = e.target.value.replace(/\D/g, "");
|
|
1792
|
+
onChange(nationalMask ? formatDigitsToMask(digits, nationalMask) : digits);
|
|
1793
|
+
},
|
|
1794
|
+
placeholder: nationalPlaceholder,
|
|
1795
|
+
className: "flex-1 px-3 py-2.5 text-base text-primary placeholder-quaternary bg-transparent focus:outline-none",
|
|
1796
|
+
autoComplete: "tel",
|
|
1797
|
+
required
|
|
1798
|
+
}
|
|
1799
|
+
));
|
|
1800
|
+
const finishPasswordlessAuth = async ({
|
|
1801
|
+
token,
|
|
1802
|
+
firstNameValue,
|
|
1803
|
+
lastNameValue,
|
|
1804
|
+
emailValue
|
|
1805
|
+
}) => {
|
|
1806
|
+
const res = await fetch("/api/consumer/passwordless_auth", {
|
|
1807
|
+
method: "POST",
|
|
1808
|
+
headers: { "Content-Type": "application/json" },
|
|
1809
|
+
credentials: "include",
|
|
1810
|
+
body: JSON.stringify({
|
|
1811
|
+
phone: fullPhone,
|
|
1812
|
+
verification_token: token,
|
|
1813
|
+
first_name: firstNameValue.trim(),
|
|
1814
|
+
last_name: lastNameValue.trim(),
|
|
1815
|
+
email: emailValue.trim()
|
|
1816
|
+
})
|
|
1817
|
+
});
|
|
1818
|
+
const result = await res.json().catch(() => ({}));
|
|
1819
|
+
if (!res.ok) {
|
|
1820
|
+
setError(result.error || "Authentication failed. Please try again.");
|
|
1821
|
+
captureEvent("portal_login_failed", { step: "signup", reason: result.error || "passwordless_auth_failed" });
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
firePixelEvent("Lead");
|
|
1825
|
+
captureEvent("portal_login_step_advanced", { from_step: "signup", to_step: "authenticated", reason: "passwordless_auth_success" });
|
|
1826
|
+
captureEvent("portal_login_completed", { flow: "signup" });
|
|
1827
|
+
setResendAvailableAt(null);
|
|
1828
|
+
setSecondsUntilResend(0);
|
|
1829
|
+
if (onSuccess) onSuccess();
|
|
1830
|
+
else router.refresh();
|
|
1831
|
+
return true;
|
|
1832
|
+
};
|
|
1833
|
+
const handleSendCode = async (e) => {
|
|
1834
|
+
e.preventDefault();
|
|
1835
|
+
if (!fullPhone) {
|
|
1836
|
+
setError("Enter your phone number to continue.");
|
|
1837
|
+
captureEvent("portal_login_failed", { step: "identifier", reason: "validation_missing_phone" });
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
setLoading(true);
|
|
1841
|
+
setError(null);
|
|
1842
|
+
captureEvent("portal_login_step_submitted", { step: "identifier" });
|
|
1843
|
+
try {
|
|
1844
|
+
const res = await fetch("/api/consumer/send_code", {
|
|
1845
|
+
method: "POST",
|
|
1846
|
+
headers: { "Content-Type": "application/json" },
|
|
1847
|
+
credentials: "include",
|
|
1848
|
+
body: JSON.stringify({ phone: fullPhone })
|
|
1849
|
+
});
|
|
1850
|
+
const result = await res.json().catch(() => ({}));
|
|
1851
|
+
if (!res.ok) {
|
|
1852
|
+
if (result.code === "cooldown_active" && typeof result.retry_in_seconds === "number") {
|
|
1853
|
+
setResendAvailableAt(Date.now() + result.retry_in_seconds * 1e3);
|
|
1854
|
+
}
|
|
1855
|
+
setError(result.error || "Could not send verification code.");
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
await setPixelUserData({ phone: fullPhone });
|
|
1859
|
+
firePixelEvent("Lead");
|
|
1860
|
+
setResendAvailableAt(result.resend_available_at ? new Date(result.resend_available_at).getTime() : Date.now() + 5 * 60 * 1e3);
|
|
1861
|
+
setStep("verify");
|
|
1862
|
+
captureEvent("portal_login_step_advanced", { from_step: "identifier", to_step: "signin", reason: "verification_code_sent" });
|
|
1863
|
+
} catch (e2) {
|
|
1864
|
+
setError("Could not send verification code.");
|
|
1865
|
+
captureEvent("portal_login_failed", { step: "identifier", reason: "network_error" });
|
|
1866
|
+
} finally {
|
|
1867
|
+
setLoading(false);
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
const handleVerifyCode = async (e) => {
|
|
1871
|
+
e.preventDefault();
|
|
1872
|
+
const code = verificationCode.replace(/\D/g, "");
|
|
1873
|
+
if (!fullPhone || code.length !== 4) {
|
|
1874
|
+
setError("Enter the 4-digit code sent to your phone.");
|
|
1875
|
+
captureEvent("portal_login_failed", { step: "signin", reason: "validation_invalid_code" });
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
setLoading(true);
|
|
1879
|
+
setError(null);
|
|
1880
|
+
captureEvent("portal_login_step_submitted", { step: "signin" });
|
|
1881
|
+
try {
|
|
1882
|
+
const res = await fetch("/api/consumer/verify_code", {
|
|
1883
|
+
method: "POST",
|
|
1884
|
+
headers: { "Content-Type": "application/json" },
|
|
1885
|
+
credentials: "include",
|
|
1886
|
+
body: JSON.stringify({ phone: fullPhone, code })
|
|
1887
|
+
});
|
|
1888
|
+
const result = await res.json().catch(() => ({}));
|
|
1889
|
+
if (!res.ok || !result.verification_token) {
|
|
1890
|
+
setError(result.error || "That code is invalid or expired.");
|
|
1891
|
+
captureEvent("portal_login_failed", { step: "signin", reason: result.code || "verification_failed" });
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
setVerificationToken(result.verification_token);
|
|
1895
|
+
setFirstName(result.first_name || "");
|
|
1896
|
+
setLastName(result.last_name || "");
|
|
1897
|
+
setEmail(result.email || "");
|
|
1898
|
+
const hasCompleteExistingProfile = typeof result.first_name === "string" && result.first_name.trim().length > 0 && typeof result.last_name === "string" && result.last_name.trim().length > 0 && typeof result.email === "string" && result.email.trim().length > 0;
|
|
1899
|
+
if (hasCompleteExistingProfile) {
|
|
1900
|
+
captureEvent("portal_login_step_advanced", {
|
|
1901
|
+
from_step: "signin",
|
|
1902
|
+
to_step: "authenticated",
|
|
1903
|
+
reason: "verification_success_profile_complete"
|
|
1904
|
+
});
|
|
1905
|
+
await finishPasswordlessAuth({
|
|
1906
|
+
token: result.verification_token,
|
|
1907
|
+
firstNameValue: result.first_name,
|
|
1908
|
+
lastNameValue: result.last_name,
|
|
1909
|
+
emailValue: result.email
|
|
1910
|
+
});
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
setStep("profile");
|
|
1914
|
+
captureEvent("portal_login_step_advanced", { from_step: "signin", to_step: "signup", reason: "verification_success" });
|
|
1915
|
+
} catch (e2) {
|
|
1916
|
+
setError("That code is invalid or expired.");
|
|
1917
|
+
captureEvent("portal_login_failed", { step: "signin", reason: "network_error" });
|
|
1918
|
+
} finally {
|
|
1919
|
+
setLoading(false);
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
const handleResendCode = async () => {
|
|
1923
|
+
if (!fullPhone || loading || secondsUntilResend > 0) return;
|
|
1924
|
+
setLoading(true);
|
|
1925
|
+
setError(null);
|
|
1926
|
+
try {
|
|
1927
|
+
const res = await fetch("/api/consumer/send_code", {
|
|
1928
|
+
method: "POST",
|
|
1929
|
+
headers: { "Content-Type": "application/json" },
|
|
1930
|
+
credentials: "include",
|
|
1931
|
+
body: JSON.stringify({ phone: fullPhone })
|
|
1932
|
+
});
|
|
1933
|
+
const result = await res.json().catch(() => ({}));
|
|
1934
|
+
if (!res.ok) {
|
|
1935
|
+
if (result.code === "cooldown_active" && typeof result.retry_in_seconds === "number") {
|
|
1936
|
+
setResendAvailableAt(Date.now() + result.retry_in_seconds * 1e3);
|
|
1937
|
+
}
|
|
1938
|
+
setError(result.error || "Could not request a new code yet.");
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
setVerificationCode("");
|
|
1942
|
+
setResendAvailableAt(result.resend_available_at ? new Date(result.resend_available_at).getTime() : Date.now() + 5 * 60 * 1e3);
|
|
1943
|
+
} catch (e) {
|
|
1944
|
+
setError("Could not request a new code yet.");
|
|
1945
|
+
} finally {
|
|
1946
|
+
setLoading(false);
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
const handleProfileSubmit = async (e) => {
|
|
1950
|
+
e.preventDefault();
|
|
1951
|
+
if (!fullPhone || !verificationToken) {
|
|
1952
|
+
setError("Verification expired. Please request a new code.");
|
|
1953
|
+
setStep("identifier");
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
if (!firstName.trim() || !lastName.trim() || !email.trim()) {
|
|
1957
|
+
setError("Please enter your first name, last name, and email.");
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
setLoading(true);
|
|
1961
|
+
setError(null);
|
|
1962
|
+
captureEvent("portal_login_step_submitted", { step: "signup" });
|
|
1963
|
+
try {
|
|
1964
|
+
await finishPasswordlessAuth({
|
|
1965
|
+
token: verificationToken,
|
|
1966
|
+
firstNameValue: firstName,
|
|
1967
|
+
lastNameValue: lastName,
|
|
1968
|
+
emailValue: email
|
|
1969
|
+
});
|
|
1970
|
+
} catch (e2) {
|
|
1971
|
+
setError("Authentication failed. Please try again.");
|
|
1972
|
+
captureEvent("portal_login_failed", { step: "signup", reason: "network_error" });
|
|
1973
|
+
} finally {
|
|
1974
|
+
setLoading(false);
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
const goBackToPhone = () => {
|
|
1978
|
+
setStep("identifier");
|
|
1979
|
+
setVerificationCode("");
|
|
1980
|
+
setVerificationToken(null);
|
|
1981
|
+
setFirstName("");
|
|
1982
|
+
setLastName("");
|
|
1983
|
+
setEmail("");
|
|
1984
|
+
setResendAvailableAt(null);
|
|
1985
|
+
setError(null);
|
|
1986
|
+
};
|
|
1987
|
+
const formatCountdown = (totalSeconds) => {
|
|
1988
|
+
const mins = Math.floor(totalSeconds / 60);
|
|
1989
|
+
const secs = totalSeconds % 60;
|
|
1990
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
1991
|
+
};
|
|
1992
|
+
const headerTitle = step === "identifier" ? "Welcome" : step === "verify" ? "Verify your phone" : "Last step";
|
|
1993
|
+
const headerSubtitle = step === "identifier" ? "Enter your phone number to get started." : step === "verify" ? "We sent a 4-digit verification code by text." : "Enter your first name, last name, and email.";
|
|
1994
|
+
return /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("div", { className: "flex items-start justify-between gap-4 mb-5" }, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("h3", { className: "text-lg font-normal text-primary" }, headerTitle), /* @__PURE__ */ React3.createElement("p", { className: "mt-0.5 text-sm text-tertiary" }, headerSubtitle)), onClose && /* @__PURE__ */ React3.createElement(
|
|
1995
|
+
"button",
|
|
1996
|
+
{
|
|
1997
|
+
type: "button",
|
|
1998
|
+
onClick: onClose,
|
|
1999
|
+
className: "shrink-0 p-1 text-tertiary hover:text-primary transition-colors",
|
|
2000
|
+
"aria-label": "Close"
|
|
2001
|
+
},
|
|
2002
|
+
/* @__PURE__ */ React3.createElement("svg", { className: "size-5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5 }, /* @__PURE__ */ React3.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }))
|
|
2003
|
+
)), error && /* @__PURE__ */ React3.createElement("div", { className: "mb-4 rounded-input border border-error bg-error-primary px-3 py-2 text-sm text-error-primary" }, error), /* @__PURE__ */ React3.createElement("div", { className: "relative overflow-hidden transition-all duration-300 ease-out" }, step === "identifier" && /* @__PURE__ */ React3.createElement("form", { onSubmit: handleSendCode, className: "space-y-3" }, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { className: labelClass }, "Phone number"), phoneInput(phoneValue, setPhoneValue, true)), /* @__PURE__ */ React3.createElement("div", { className: "pt-1" }, /* @__PURE__ */ React3.createElement(
|
|
2004
|
+
"button",
|
|
2005
|
+
{
|
|
2006
|
+
type: "submit",
|
|
2007
|
+
disabled: loading,
|
|
2008
|
+
className: "w-full rounded-interactive bg-brand-solid px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-solid_hover transition-colors disabled:opacity-50"
|
|
2009
|
+
},
|
|
2010
|
+
loading ? "Sending code\u2026" : "Continue"
|
|
2011
|
+
))), step === "verify" && /* @__PURE__ */ React3.createElement("form", { onSubmit: handleVerifyCode, className: "space-y-3" }, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { className: labelClass }, "Verification code"), /* @__PURE__ */ React3.createElement(
|
|
2012
|
+
"input",
|
|
2013
|
+
{
|
|
2014
|
+
type: "text",
|
|
2015
|
+
value: verificationCode,
|
|
2016
|
+
onChange: (e) => setVerificationCode(e.target.value.replace(/\D/g, "").slice(0, 4)),
|
|
2017
|
+
placeholder: "0000",
|
|
2018
|
+
className: inputClass,
|
|
2019
|
+
autoComplete: "one-time-code",
|
|
2020
|
+
inputMode: "numeric",
|
|
2021
|
+
required: true
|
|
2022
|
+
}
|
|
2023
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "pt-1" }, /* @__PURE__ */ React3.createElement(
|
|
2024
|
+
"button",
|
|
2025
|
+
{
|
|
2026
|
+
type: "submit",
|
|
2027
|
+
disabled: loading,
|
|
2028
|
+
className: "w-full rounded-interactive bg-brand-solid px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-solid_hover transition-colors disabled:opacity-50"
|
|
2029
|
+
},
|
|
2030
|
+
loading ? "Verifying\u2026" : "Verify code"
|
|
2031
|
+
)), /* @__PURE__ */ React3.createElement(
|
|
2032
|
+
"button",
|
|
2033
|
+
{
|
|
2034
|
+
type: "button",
|
|
2035
|
+
onClick: handleResendCode,
|
|
2036
|
+
disabled: loading || secondsUntilResend > 0,
|
|
2037
|
+
className: "w-full text-center text-sm text-tertiary hover:text-primary transition-colors disabled:opacity-50"
|
|
2038
|
+
},
|
|
2039
|
+
secondsUntilResend > 0 ? `Request new code in ${formatCountdown(secondsUntilResend)}` : "Request new code"
|
|
2040
|
+
), /* @__PURE__ */ React3.createElement(
|
|
2041
|
+
"button",
|
|
2042
|
+
{
|
|
2043
|
+
type: "button",
|
|
2044
|
+
onClick: goBackToPhone,
|
|
2045
|
+
className: "w-full text-center text-sm text-tertiary hover:text-primary transition-colors"
|
|
2046
|
+
},
|
|
2047
|
+
"\u2190 Use a different phone number"
|
|
2048
|
+
)), step === "profile" && /* @__PURE__ */ React3.createElement("form", { onSubmit: handleProfileSubmit, className: "space-y-3" }, /* @__PURE__ */ React3.createElement("div", { className: "grid grid-cols-2 gap-3" }, /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { className: labelClass }, "First name"), /* @__PURE__ */ React3.createElement(
|
|
2049
|
+
"input",
|
|
2050
|
+
{
|
|
2051
|
+
type: "text",
|
|
2052
|
+
value: firstName,
|
|
2053
|
+
onChange: (e) => setFirstName(e.target.value),
|
|
2054
|
+
placeholder: "Jane",
|
|
2055
|
+
className: inputClass,
|
|
2056
|
+
autoComplete: "given-name",
|
|
2057
|
+
required: true
|
|
2058
|
+
}
|
|
2059
|
+
)), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { className: labelClass }, "Last name"), /* @__PURE__ */ React3.createElement(
|
|
2060
|
+
"input",
|
|
2061
|
+
{
|
|
2062
|
+
type: "text",
|
|
2063
|
+
value: lastName,
|
|
2064
|
+
onChange: (e) => setLastName(e.target.value),
|
|
2065
|
+
placeholder: "Smith",
|
|
2066
|
+
className: inputClass,
|
|
2067
|
+
autoComplete: "family-name",
|
|
2068
|
+
required: true
|
|
2069
|
+
}
|
|
2070
|
+
))), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("label", { className: labelClass }, "Email address"), /* @__PURE__ */ React3.createElement(
|
|
2071
|
+
"input",
|
|
2072
|
+
{
|
|
2073
|
+
type: "email",
|
|
2074
|
+
value: email,
|
|
2075
|
+
onChange: (e) => setEmail(e.target.value),
|
|
2076
|
+
placeholder: "you@example.com",
|
|
2077
|
+
className: inputClass,
|
|
2078
|
+
autoComplete: "email",
|
|
2079
|
+
required: true
|
|
2080
|
+
}
|
|
2081
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "pt-1" }, /* @__PURE__ */ React3.createElement(
|
|
2082
|
+
"button",
|
|
2083
|
+
{
|
|
2084
|
+
type: "submit",
|
|
2085
|
+
disabled: loading,
|
|
2086
|
+
className: "w-full rounded-interactive bg-brand-solid px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-solid_hover transition-colors disabled:opacity-50"
|
|
2087
|
+
},
|
|
2088
|
+
loading ? "Finishing\u2026" : "Continue"
|
|
2089
|
+
)), /* @__PURE__ */ React3.createElement(
|
|
2090
|
+
"button",
|
|
2091
|
+
{
|
|
2092
|
+
type: "button",
|
|
2093
|
+
onClick: goBackToPhone,
|
|
2094
|
+
className: "w-full text-center text-sm text-tertiary hover:text-primary transition-colors"
|
|
2095
|
+
},
|
|
2096
|
+
"\u2190 Use a different phone number"
|
|
2097
|
+
))));
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// src/design_system/portal/LoginModalController.tsx
|
|
2101
|
+
var keystoneFooter = /* @__PURE__ */ React4.createElement("div", { className: "px-6 py-4 bg-secondary flex flex-col items-center gap-1" }, /* @__PURE__ */ React4.createElement("a", { href: KEYSTONE_APP_URL, target: "_blank", rel: "noopener noreferrer", "aria-label": "Visit Keystone website" }, /* @__PURE__ */ React4.createElement(KeystoneLogoMinimal, { className: "h-5 w-auto shrink-0" })), /* @__PURE__ */ React4.createElement(
|
|
2102
|
+
"a",
|
|
2103
|
+
{
|
|
2104
|
+
href: KEYSTONE_APP_URL,
|
|
2105
|
+
target: "_blank",
|
|
2106
|
+
rel: "noopener noreferrer",
|
|
2107
|
+
className: "text-xs text-quaternary hover:text-secondary transition-colors"
|
|
2108
|
+
},
|
|
2109
|
+
"Powered by Keystone Universal Login"
|
|
2110
|
+
));
|
|
2111
|
+
function LoginModalController() {
|
|
2112
|
+
const [open, setOpen] = useState2(false);
|
|
2113
|
+
const [redirectUrl, setRedirectUrl] = useState2(null);
|
|
2114
|
+
const [refreshing, setRefreshing] = useState2(false);
|
|
2115
|
+
const [isPending, startTransition] = useTransition();
|
|
2116
|
+
const router = useRouter3();
|
|
2117
|
+
useEffect3(() => {
|
|
2118
|
+
const handleClick = (e) => {
|
|
2119
|
+
const trigger = e.target.closest("[data-open-login-modal]");
|
|
2120
|
+
if (trigger) {
|
|
2121
|
+
e.preventDefault();
|
|
2122
|
+
setRedirectUrl(trigger.getAttribute("data-login-redirect"));
|
|
2123
|
+
setOpen(true);
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
document.addEventListener("click", handleClick);
|
|
2127
|
+
return () => document.removeEventListener("click", handleClick);
|
|
2128
|
+
}, []);
|
|
2129
|
+
const handleSuccess = () => {
|
|
2130
|
+
setRefreshing(true);
|
|
2131
|
+
startTransition(() => {
|
|
2132
|
+
if (redirectUrl) {
|
|
2133
|
+
router.push(redirectUrl);
|
|
2134
|
+
setRedirectUrl(null);
|
|
2135
|
+
} else {
|
|
2136
|
+
router.refresh();
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2139
|
+
};
|
|
2140
|
+
useEffect3(() => {
|
|
2141
|
+
if (!(refreshing && !isPending)) return;
|
|
2142
|
+
const timeout = window.setTimeout(() => {
|
|
2143
|
+
setOpen(false);
|
|
2144
|
+
setRefreshing(false);
|
|
2145
|
+
}, 0);
|
|
2146
|
+
return () => window.clearTimeout(timeout);
|
|
2147
|
+
}, [refreshing, isPending]);
|
|
2148
|
+
return /* @__PURE__ */ React4.createElement(
|
|
2149
|
+
Modal,
|
|
2150
|
+
{
|
|
2151
|
+
isOpen: open,
|
|
2152
|
+
onClose: refreshing ? () => {
|
|
2153
|
+
} : () => setOpen(false),
|
|
2154
|
+
hideHeader: true,
|
|
2155
|
+
ariaLabel: "Sign in or create account",
|
|
2156
|
+
footer: refreshing ? void 0 : keystoneFooter
|
|
2157
|
+
},
|
|
2158
|
+
refreshing ? /* @__PURE__ */ React4.createElement("div", { className: "flex flex-col items-center justify-center py-12 gap-3" }, /* @__PURE__ */ React4.createElement("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-brand border-t-transparent" }), /* @__PURE__ */ React4.createElement("p", { className: "text-sm text-tertiary" }, "Signing you in\u2026")) : /* @__PURE__ */ React4.createElement(LoginForm, { onSuccess: handleSuccess, onClose: () => setOpen(false) })
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/design_system/portal/MessageComposer.tsx
|
|
2163
|
+
import React5, { useState as useState3, useTransition as useTransition2, useRef as useRef3, useEffect as useEffect5, useCallback as useCallback2 } from "react";
|
|
2164
|
+
import { useRouter as useRouter4 } from "next/navigation";
|
|
2165
|
+
|
|
2166
|
+
// src/design_system/chat/useRealtimeReplyOrchestrator.ts
|
|
2167
|
+
import { useCallback, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
2168
|
+
import { createConsumer } from "@rails/actioncable";
|
|
2169
|
+
function useRealtimeReplyOrchestrator({
|
|
2170
|
+
debugLabel,
|
|
2171
|
+
fetchRealtimeData,
|
|
2172
|
+
loadLatestHasReply,
|
|
2173
|
+
onReplyResolved,
|
|
2174
|
+
onAgentThinking
|
|
2175
|
+
}) {
|
|
2176
|
+
const cableRef = useRef2(null);
|
|
2177
|
+
const subscriptionRef = useRef2(null);
|
|
2178
|
+
const subscribedContactRef = useRef2(null);
|
|
2179
|
+
const clearRealtime = useCallback(() => {
|
|
2180
|
+
if (subscriptionRef.current) {
|
|
2181
|
+
subscriptionRef.current.unsubscribe();
|
|
2182
|
+
subscriptionRef.current = null;
|
|
2183
|
+
}
|
|
2184
|
+
if (cableRef.current) {
|
|
2185
|
+
cableRef.current.disconnect();
|
|
2186
|
+
cableRef.current = null;
|
|
2187
|
+
}
|
|
2188
|
+
subscribedContactRef.current = null;
|
|
2189
|
+
}, []);
|
|
2190
|
+
const resolveReply = useCallback(() => {
|
|
2191
|
+
onReplyResolved();
|
|
2192
|
+
}, [onReplyResolved]);
|
|
2193
|
+
const subscribeRealtime = useCallback((realtime) => {
|
|
2194
|
+
const token = realtime.token;
|
|
2195
|
+
const contactIdForStream = realtime.contact_id;
|
|
2196
|
+
const cableUrl = realtime.cable_url;
|
|
2197
|
+
if (!token || !contactIdForStream || !cableUrl) return;
|
|
2198
|
+
if (subscribedContactRef.current === contactIdForStream && subscriptionRef.current && cableRef.current) {
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
clearRealtime();
|
|
2202
|
+
const cable = createConsumer(`${cableUrl}?token=${encodeURIComponent(token)}`);
|
|
2203
|
+
cableRef.current = cable;
|
|
2204
|
+
subscribedContactRef.current = contactIdForStream;
|
|
2205
|
+
subscriptionRef.current = cable.subscriptions.create(
|
|
2206
|
+
{ channel: "ContactCommunicationsChannel", contact_id: String(contactIdForStream) },
|
|
2207
|
+
{
|
|
2208
|
+
connected: () => {
|
|
2209
|
+
console.info(`[${debugLabel}] realtime connected contact_id=${contactIdForStream}`);
|
|
2210
|
+
},
|
|
2211
|
+
disconnected: () => {
|
|
2212
|
+
console.warn(`[${debugLabel}] realtime disconnected contact_id=${contactIdForStream}`);
|
|
2213
|
+
},
|
|
2214
|
+
rejected: () => {
|
|
2215
|
+
console.warn(`[${debugLabel}] realtime rejected contact_id=${contactIdForStream}`);
|
|
2216
|
+
},
|
|
2217
|
+
received: async (data) => {
|
|
2218
|
+
var _a;
|
|
2219
|
+
console.info(`[${debugLabel}] realtime received type=${(_a = data == null ? void 0 : data.type) != null ? _a : "unknown"} contact_id=${contactIdForStream}`);
|
|
2220
|
+
if ((data == null ? void 0 : data.type) === "agent_thinking") {
|
|
2221
|
+
onAgentThinking == null ? void 0 : onAgentThinking();
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2224
|
+
if ((data == null ? void 0 : data.type) !== "new_communication") return;
|
|
2225
|
+
try {
|
|
2226
|
+
const hasReply = await loadLatestHasReply();
|
|
2227
|
+
if (hasReply) {
|
|
2228
|
+
resolveReply();
|
|
2229
|
+
}
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
console.error(`[${debugLabel}] realtime receive handling error`, error);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
);
|
|
2236
|
+
}, [clearRealtime, debugLabel, loadLatestHasReply, onAgentThinking, resolveReply]);
|
|
2237
|
+
const ensureRealtimeSubscription = useCallback(async () => {
|
|
2238
|
+
try {
|
|
2239
|
+
const realtime = await fetchRealtimeData();
|
|
2240
|
+
if (!realtime) return;
|
|
2241
|
+
subscribeRealtime(realtime);
|
|
2242
|
+
} catch (e) {
|
|
2243
|
+
}
|
|
2244
|
+
}, [fetchRealtimeData, subscribeRealtime]);
|
|
2245
|
+
const beginReplyWait = useCallback((options) => {
|
|
2246
|
+
if (options == null ? void 0 : options.realtimeData) {
|
|
2247
|
+
subscribeRealtime(options.realtimeData);
|
|
2248
|
+
} else {
|
|
2249
|
+
void ensureRealtimeSubscription();
|
|
2250
|
+
}
|
|
2251
|
+
}, [ensureRealtimeSubscription, subscribeRealtime]);
|
|
2252
|
+
useEffect4(() => {
|
|
2253
|
+
return () => {
|
|
2254
|
+
clearRealtime();
|
|
2255
|
+
};
|
|
2256
|
+
}, [clearRealtime]);
|
|
2257
|
+
return {
|
|
2258
|
+
ensureRealtimeSubscription,
|
|
2259
|
+
subscribeRealtime,
|
|
2260
|
+
beginReplyWait
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// src/design_system/portal/MessageComposer.tsx
|
|
2265
|
+
function MessageComposer({ contactId }) {
|
|
2266
|
+
const [body, setBody] = useState3("");
|
|
2267
|
+
const [error, setError] = useState3(null);
|
|
2268
|
+
const [isPending, startTransition] = useTransition2();
|
|
2269
|
+
const textareaRef = useRef3(null);
|
|
2270
|
+
const router = useRouter4();
|
|
2271
|
+
const hasAgentReplyWithBody = useCallback2(async () => {
|
|
2272
|
+
const response = await fetch(`/api/chat/?contact_id=${encodeURIComponent(contactId)}`);
|
|
2273
|
+
if (!response.ok) return false;
|
|
2274
|
+
const result = await response.json();
|
|
2275
|
+
const messages = (result == null ? void 0 : result.data) || [];
|
|
2276
|
+
const latest = messages[messages.length - 1];
|
|
2277
|
+
return (latest == null ? void 0 : latest.sender_type) === "agent" && (latest == null ? void 0 : latest.body) != null && String(latest.body).trim() !== "";
|
|
2278
|
+
}, [contactId]);
|
|
2279
|
+
const fetchRealtimeData = useCallback2(async () => {
|
|
2280
|
+
var _a, _b, _c;
|
|
2281
|
+
const response = await fetch(`/api/chat/?action=realtime_token&contact_id=${encodeURIComponent(contactId)}`);
|
|
2282
|
+
if (!response.ok) return null;
|
|
2283
|
+
const result = await response.json();
|
|
2284
|
+
const token = (_a = result == null ? void 0 : result.data) == null ? void 0 : _a.token;
|
|
2285
|
+
const streamContactId = (_b = result == null ? void 0 : result.data) == null ? void 0 : _b.contact_id;
|
|
2286
|
+
const cableUrl = (_c = result == null ? void 0 : result.data) == null ? void 0 : _c.cable_url;
|
|
2287
|
+
if (!token || !streamContactId || !cableUrl) return null;
|
|
2288
|
+
return { token, contact_id: streamContactId, cable_url: cableUrl };
|
|
2289
|
+
}, [contactId]);
|
|
2290
|
+
const {
|
|
2291
|
+
beginReplyWait,
|
|
2292
|
+
ensureRealtimeSubscription
|
|
2293
|
+
} = useRealtimeReplyOrchestrator({
|
|
2294
|
+
debugLabel: "MessageComposer",
|
|
2295
|
+
fetchRealtimeData,
|
|
2296
|
+
loadLatestHasReply: hasAgentReplyWithBody,
|
|
2297
|
+
onReplyResolved: () => {
|
|
2298
|
+
router.refresh();
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2301
|
+
useEffect5(() => {
|
|
2302
|
+
const el = textareaRef.current;
|
|
2303
|
+
if (!el) return;
|
|
2304
|
+
el.style.height = "auto";
|
|
2305
|
+
el.style.height = `${Math.min(el.scrollHeight, 120)}px`;
|
|
2306
|
+
}, [body]);
|
|
2307
|
+
useEffect5(() => {
|
|
2308
|
+
void ensureRealtimeSubscription();
|
|
2309
|
+
}, [ensureRealtimeSubscription]);
|
|
2310
|
+
function submit() {
|
|
2311
|
+
if (!body.trim() || isPending) return;
|
|
2312
|
+
setError(null);
|
|
2313
|
+
startTransition(async () => {
|
|
2314
|
+
var _a, _b, _c, _d;
|
|
2315
|
+
try {
|
|
2316
|
+
const res = await fetch("/api/chat", {
|
|
2317
|
+
method: "POST",
|
|
2318
|
+
headers: { "Content-Type": "application/json" },
|
|
2319
|
+
body: JSON.stringify({ contact_id: contactId, body: body.trim() })
|
|
2320
|
+
});
|
|
2321
|
+
const result = await res.json().catch(() => ({}));
|
|
2322
|
+
if (!res.ok) {
|
|
2323
|
+
setError(result.error || "Failed to send message.");
|
|
2324
|
+
} else {
|
|
2325
|
+
setBody("");
|
|
2326
|
+
router.refresh();
|
|
2327
|
+
const hasBackgroundReplyJob = Boolean((_a = result == null ? void 0 : result.data) == null ? void 0 : _a.job_id);
|
|
2328
|
+
if (hasBackgroundReplyJob) {
|
|
2329
|
+
const realtimeFromSend = ((_b = result == null ? void 0 : result.data) == null ? void 0 : _b.realtime_token) && ((_c = result == null ? void 0 : result.data) == null ? void 0 : _c.contact_id) && ((_d = result == null ? void 0 : result.data) == null ? void 0 : _d.cable_url) ? {
|
|
2330
|
+
token: result.data.realtime_token,
|
|
2331
|
+
contact_id: result.data.contact_id,
|
|
2332
|
+
cable_url: result.data.cable_url
|
|
2333
|
+
} : null;
|
|
2334
|
+
beginReplyWait({ realtimeData: realtimeFromSend });
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
} catch (e) {
|
|
2338
|
+
setError("Failed to send message.");
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
function handleSubmit(e) {
|
|
2343
|
+
e.preventDefault();
|
|
2344
|
+
submit();
|
|
2345
|
+
}
|
|
2346
|
+
function handleKeyDown(e) {
|
|
2347
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
2348
|
+
e.preventDefault();
|
|
2349
|
+
submit();
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return /* @__PURE__ */ React5.createElement("form", { onSubmit: handleSubmit, className: "border-t border-secondary px-4 py-3" }, error && /* @__PURE__ */ React5.createElement("p", { className: "mb-2 text-xs text-red-600" }, error), /* @__PURE__ */ React5.createElement("div", { className: "flex items-end gap-2" }, /* @__PURE__ */ React5.createElement(
|
|
2353
|
+
"textarea",
|
|
2354
|
+
{
|
|
2355
|
+
ref: textareaRef,
|
|
2356
|
+
value: body,
|
|
2357
|
+
onChange: (e) => setBody(e.target.value),
|
|
2358
|
+
onKeyDown: handleKeyDown,
|
|
2359
|
+
placeholder: "Type a message\u2026 (Enter to send)",
|
|
2360
|
+
rows: 1,
|
|
2361
|
+
disabled: isPending,
|
|
2362
|
+
className: "flex-1 resize-none rounded-input border border-primary bg-primary px-3 py-2 text-sm text-primary placeholder-quaternary focus:border-brand focus:outline-none focus:ring-1 focus:ring-brand transition-colors disabled:opacity-50"
|
|
2363
|
+
}
|
|
2364
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2365
|
+
"button",
|
|
2366
|
+
{
|
|
2367
|
+
type: "submit",
|
|
2368
|
+
disabled: !body.trim() || isPending,
|
|
2369
|
+
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-interactive bg-brand-solid text-white transition-colors hover:bg-brand-solid_hover disabled:opacity-40 disabled:cursor-not-allowed",
|
|
2370
|
+
"aria-label": "Send message"
|
|
2371
|
+
},
|
|
2372
|
+
isPending ? /* @__PURE__ */ React5.createElement("svg", { className: "size-4 animate-spin", fill: "none", viewBox: "0 0 24 24" }, /* @__PURE__ */ React5.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ React5.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" })) : /* @__PURE__ */ React5.createElement("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 }, /* @__PURE__ */ React5.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.269 20.876L5.999 12zm0 0h7.5" }))
|
|
2373
|
+
)));
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/design_system/portal/RowThumbnail.tsx
|
|
2377
|
+
import React6 from "react";
|
|
2378
|
+
|
|
2379
|
+
// src/lib/hooks/use-image-cycle.ts
|
|
2380
|
+
import { useState as useState4, useEffect as useEffect6, useMemo as useMemo2 } from "react";
|
|
2381
|
+
var CYCLE_INTERVAL_MIN_MS = 6e3;
|
|
2382
|
+
var CYCLE_INTERVAL_MAX_MS = 8e3;
|
|
2383
|
+
var CROSSFADE_DURATION_MS = 600;
|
|
2384
|
+
function seedToUnit(seed) {
|
|
2385
|
+
let h = 2166136261 >>> 0;
|
|
2386
|
+
for (let i = 0; i < seed.length; i++) {
|
|
2387
|
+
h ^= seed.charCodeAt(i);
|
|
2388
|
+
h = Math.imul(h, 16777619) >>> 0 >>> 0;
|
|
2389
|
+
}
|
|
2390
|
+
return (h >>> 0) / 4294967296;
|
|
2391
|
+
}
|
|
2392
|
+
function shuffleWithSeed(array, seed) {
|
|
2393
|
+
if (array.length <= 1) return array;
|
|
2394
|
+
const arr = [...array];
|
|
2395
|
+
let h = 2166136261 >>> 0;
|
|
2396
|
+
for (let i = 0; i < seed.length; i++) {
|
|
2397
|
+
h ^= seed.charCodeAt(i);
|
|
2398
|
+
h = Math.imul(h, 16777619) >>> 0 >>> 0;
|
|
2399
|
+
}
|
|
2400
|
+
const next = (step) => {
|
|
2401
|
+
h = Math.imul(1664525, h + step >>> 0) + 1013904223 >>> 0;
|
|
2402
|
+
return (h >>> 0) / 4294967296;
|
|
2403
|
+
};
|
|
2404
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
2405
|
+
const j = Math.floor(next(i) * (i + 1));
|
|
2406
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
2407
|
+
}
|
|
2408
|
+
return arr;
|
|
2409
|
+
}
|
|
2410
|
+
function photoUrlFromAttachment(pa) {
|
|
2411
|
+
var _a, _b, _c;
|
|
2412
|
+
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);
|
|
2413
|
+
}
|
|
2414
|
+
function photoAltFromAttachment(pa) {
|
|
2415
|
+
var _a, _b;
|
|
2416
|
+
return ((_a = pa.photo) == null ? void 0 : _a.alt_text) || ((_b = pa.photo) == null ? void 0 : _b.title) || "";
|
|
2417
|
+
}
|
|
2418
|
+
function useImageCycle(photoAttachments, seed) {
|
|
2419
|
+
const list = useMemo2(() => {
|
|
2420
|
+
const arr = Array.isArray(photoAttachments) && photoAttachments.length > 0 ? photoAttachments : [];
|
|
2421
|
+
if (arr.length === 0) return [];
|
|
2422
|
+
return shuffleWithSeed(arr, seed).map((pa) => {
|
|
2423
|
+
var _a;
|
|
2424
|
+
return { url: (_a = photoUrlFromAttachment(pa)) != null ? _a : "", alt: photoAltFromAttachment(pa) };
|
|
2425
|
+
}).filter((x) => x.url);
|
|
2426
|
+
}, [photoAttachments, seed]);
|
|
2427
|
+
const [currentIndex, setCurrentIndex] = useState4(0);
|
|
2428
|
+
const [transitioning, setTransitioning] = useState4(false);
|
|
2429
|
+
const intervalMs = useMemo2(
|
|
2430
|
+
() => CYCLE_INTERVAL_MIN_MS + Math.floor(seedToUnit(seed) * (CYCLE_INTERVAL_MAX_MS - CYCLE_INTERVAL_MIN_MS + 1)),
|
|
2431
|
+
[seed]
|
|
2432
|
+
);
|
|
2433
|
+
useEffect6(() => {
|
|
2434
|
+
if (list.length <= 1) return;
|
|
2435
|
+
const id = setInterval(() => setTransitioning(true), intervalMs);
|
|
2436
|
+
return () => clearInterval(id);
|
|
2437
|
+
}, [list.length, intervalMs]);
|
|
2438
|
+
useEffect6(() => {
|
|
2439
|
+
if (!transitioning || list.length <= 1) return;
|
|
2440
|
+
const t = setTimeout(() => {
|
|
2441
|
+
setCurrentIndex((i) => (i + 1) % list.length);
|
|
2442
|
+
setTransitioning(false);
|
|
2443
|
+
}, CROSSFADE_DURATION_MS);
|
|
2444
|
+
return () => clearTimeout(t);
|
|
2445
|
+
}, [transitioning, list.length]);
|
|
2446
|
+
const nextIndex = list.length > 1 ? (currentIndex + 1) % list.length : 0;
|
|
2447
|
+
return { list, currentIndex, nextIndex, transitioning };
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// src/design_system/portal/RowThumbnail.tsx
|
|
2451
|
+
var CROSSFADE_STYLE = { transitionDuration: `${CROSSFADE_DURATION_MS}ms` };
|
|
2452
|
+
function RowThumbnail({
|
|
2453
|
+
photoAttachments,
|
|
2454
|
+
seed,
|
|
2455
|
+
alt,
|
|
2456
|
+
sizeClassName = "w-14 h-14"
|
|
2457
|
+
}) {
|
|
2458
|
+
var _a, _b, _c, _d, _e;
|
|
2459
|
+
const { list, currentIndex, nextIndex, transitioning } = useImageCycle(photoAttachments, seed);
|
|
2460
|
+
const displayAlt = ((_a = list[currentIndex]) == null ? void 0 : _a.alt) || alt;
|
|
2461
|
+
if (list.length === 0) {
|
|
2462
|
+
return /* @__PURE__ */ React6.createElement("div", { className: `${sizeClassName} shrink-0 rounded-component bg-secondary border border-tertiary flex items-center justify-center` }, /* @__PURE__ */ React6.createElement("svg", { className: "size-5 text-quaternary", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5 }, /* @__PURE__ */ React6.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" })));
|
|
2463
|
+
}
|
|
2464
|
+
if (list.length === 1) {
|
|
2465
|
+
return /* @__PURE__ */ React6.createElement("div", { className: `${sizeClassName} shrink-0 rounded-component overflow-hidden` }, /* @__PURE__ */ React6.createElement(
|
|
2466
|
+
"img",
|
|
2467
|
+
{
|
|
2468
|
+
src: list[0].url,
|
|
2469
|
+
alt: list[0].alt || alt,
|
|
2470
|
+
className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
2471
|
+
}
|
|
2472
|
+
));
|
|
2473
|
+
}
|
|
2474
|
+
return /* @__PURE__ */ React6.createElement("div", { className: `${sizeClassName} shrink-0 rounded-component overflow-hidden relative` }, /* @__PURE__ */ React6.createElement(
|
|
2475
|
+
"div",
|
|
2476
|
+
{
|
|
2477
|
+
className: `absolute inset-0 ${transitioning ? "transition-opacity ease-in-out" : "transition-none"}`,
|
|
2478
|
+
style: __spreadValues({ opacity: transitioning ? 0 : 1 }, transitioning ? CROSSFADE_STYLE : {})
|
|
2479
|
+
},
|
|
2480
|
+
/* @__PURE__ */ React6.createElement(
|
|
2481
|
+
"img",
|
|
2482
|
+
{
|
|
2483
|
+
src: list[currentIndex].url,
|
|
2484
|
+
alt: (_c = (_b = list[currentIndex]) == null ? void 0 : _b.alt) != null ? _c : displayAlt,
|
|
2485
|
+
className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
2486
|
+
}
|
|
2487
|
+
)
|
|
2488
|
+
), /* @__PURE__ */ React6.createElement(
|
|
2489
|
+
"div",
|
|
2490
|
+
{
|
|
2491
|
+
className: `absolute inset-0 ${transitioning ? "transition-opacity ease-in-out" : "transition-none"}`,
|
|
2492
|
+
style: __spreadValues({ opacity: transitioning ? 1 : 0 }, transitioning ? CROSSFADE_STYLE : {})
|
|
2493
|
+
},
|
|
2494
|
+
/* @__PURE__ */ React6.createElement(
|
|
2495
|
+
"img",
|
|
2496
|
+
{
|
|
2497
|
+
src: list[nextIndex].url,
|
|
2498
|
+
alt: (_e = (_d = list[nextIndex]) == null ? void 0 : _d.alt) != null ? _e : displayAlt,
|
|
2499
|
+
className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
2500
|
+
}
|
|
2501
|
+
)
|
|
2502
|
+
));
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/design_system/portal/PortalTabTracker.tsx
|
|
2506
|
+
import { useEffect as useEffect7 } from "react";
|
|
2507
|
+
function PortalTabTracker({ event, params, tab }) {
|
|
2508
|
+
useEffect7(() => {
|
|
2509
|
+
firePixelEvent(event, params);
|
|
2510
|
+
captureEvent("portal_tab_viewed", { tab });
|
|
2511
|
+
}, []);
|
|
2512
|
+
return null;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// src/design_system/portal/BookIframePanel.tsx
|
|
2516
|
+
import React7, { useState as useState5, useEffect as useEffect8, useRef as useRef4 } from "react";
|
|
2517
|
+
function BookIframePanel({ bookingHref, businessName }) {
|
|
2518
|
+
const [hasOpened, setHasOpened] = useState5(false);
|
|
2519
|
+
const [modalOpen, setModalOpen] = useState5(false);
|
|
2520
|
+
const [isVisible, setIsVisible] = useState5(false);
|
|
2521
|
+
const closeTimerRef = useRef4(null);
|
|
2522
|
+
const openModal = () => {
|
|
2523
|
+
setHasOpened(true);
|
|
2524
|
+
setModalOpen(true);
|
|
2525
|
+
};
|
|
2526
|
+
const closeModal = () => {
|
|
2527
|
+
setIsVisible(false);
|
|
2528
|
+
closeTimerRef.current = setTimeout(() => setModalOpen(false), 300);
|
|
2529
|
+
};
|
|
2530
|
+
useEffect8(() => {
|
|
2531
|
+
return () => {
|
|
2532
|
+
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
2533
|
+
};
|
|
2534
|
+
}, []);
|
|
2535
|
+
useEffect8(() => {
|
|
2536
|
+
if (!modalOpen) return;
|
|
2537
|
+
let cancelled = false;
|
|
2538
|
+
const id = requestAnimationFrame(
|
|
2539
|
+
() => requestAnimationFrame(() => {
|
|
2540
|
+
if (!cancelled) setIsVisible(true);
|
|
2541
|
+
})
|
|
2542
|
+
);
|
|
2543
|
+
return () => {
|
|
2544
|
+
cancelled = true;
|
|
2545
|
+
cancelAnimationFrame(id);
|
|
2546
|
+
};
|
|
2547
|
+
}, [modalOpen]);
|
|
2548
|
+
useEffect8(() => {
|
|
2549
|
+
document.body.style.overflow = modalOpen ? "hidden" : "";
|
|
2550
|
+
return () => {
|
|
2551
|
+
document.body.style.overflow = "";
|
|
2552
|
+
};
|
|
2553
|
+
}, [modalOpen]);
|
|
2554
|
+
return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("div", { className: "relative rounded-component border border-secondary overflow-hidden", style: { height: "70vh" } }, /* @__PURE__ */ React7.createElement(
|
|
2555
|
+
"iframe",
|
|
2556
|
+
{
|
|
2557
|
+
src: bookingHref,
|
|
2558
|
+
className: "w-full h-full pointer-events-none select-none",
|
|
2559
|
+
title: "Booking preview",
|
|
2560
|
+
tabIndex: -1,
|
|
2561
|
+
"aria-hidden": "true"
|
|
2562
|
+
}
|
|
2563
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "absolute inset-0 flex flex-col items-center justify-center px-6 py-8 backdrop-blur-[3.9px] bg-primary/70" }, /* @__PURE__ */ React7.createElement("div", { className: "w-full max-w-sm rounded-component border border-secondary bg-primary px-6 py-6 text-center shadow-sm" }, /* @__PURE__ */ React7.createElement("p", { className: "text-xl font-bold text-primary" }, "Booking Instructions"), /* @__PURE__ */ React7.createElement("p", { className: "mt-5 text-sm text-secondary leading-relaxed" }, "We use a separate platform that may ask for an additional sign in or intake details to fully create your profile and collect your appointment information."), /* @__PURE__ */ React7.createElement("div", { className: "mt-5 flex flex-wrap items-center justify-center gap-3" }, /* @__PURE__ */ React7.createElement(
|
|
2564
|
+
"button",
|
|
2565
|
+
{
|
|
2566
|
+
onClick: openModal,
|
|
2567
|
+
className: "inline-flex items-center gap-2 rounded-interactive bg-brand-solid px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-brand-solid_hover"
|
|
2568
|
+
},
|
|
2569
|
+
"Start Booking"
|
|
2570
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2571
|
+
"a",
|
|
2572
|
+
{
|
|
2573
|
+
href: bookingHref,
|
|
2574
|
+
target: "_blank",
|
|
2575
|
+
rel: "noopener noreferrer",
|
|
2576
|
+
className: "inline-flex items-center gap-1.5 rounded-interactive border border-secondary bg-secondary px-5 py-2.5 text-sm font-semibold text-primary transition-colors hover:bg-secondary_hover"
|
|
2577
|
+
},
|
|
2578
|
+
"Open in New Tab",
|
|
2579
|
+
/* @__PURE__ */ React7.createElement("svg", { className: "size-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 }, /* @__PURE__ */ React7.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" }))
|
|
2580
|
+
))))), hasOpened && /* @__PURE__ */ React7.createElement(
|
|
2581
|
+
"div",
|
|
2582
|
+
{
|
|
2583
|
+
className: `fixed inset-0 z-50 flex flex-col bg-primary transition-[opacity,transform] duration-300 ease-out ${modalOpen && isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-3 pointer-events-none"}`
|
|
2584
|
+
},
|
|
2585
|
+
/* @__PURE__ */ React7.createElement("div", { className: "flex shrink-0 items-center justify-between border-b border-secondary bg-primary px-4 py-3" }, /* @__PURE__ */ React7.createElement("span", { className: "text-sm font-semibold text-primary" }, businessName, " Booking"), /* @__PURE__ */ React7.createElement(
|
|
2586
|
+
"button",
|
|
2587
|
+
{
|
|
2588
|
+
onClick: closeModal,
|
|
2589
|
+
className: "inline-flex items-center gap-2 rounded-interactive bg-brand-solid px-4 py-1.5 text-sm font-semibold text-white transition-colors hover:bg-brand-solid_hover"
|
|
2590
|
+
},
|
|
2591
|
+
/* @__PURE__ */ React7.createElement("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2.5 }, /* @__PURE__ */ React7.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })),
|
|
2592
|
+
"Close"
|
|
2593
|
+
)),
|
|
2594
|
+
/* @__PURE__ */ React7.createElement(
|
|
2595
|
+
"iframe",
|
|
2596
|
+
{
|
|
2597
|
+
src: bookingHref,
|
|
2598
|
+
className: "min-h-0 flex-1 w-full",
|
|
2599
|
+
title: "Book appointment",
|
|
2600
|
+
allow: "payment"
|
|
2601
|
+
}
|
|
2602
|
+
)
|
|
2603
|
+
));
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// src/lib/consumer-session.ts
|
|
2607
|
+
var CONSUMER_TOKEN_COOKIE = "ks_consumer_token";
|
|
2608
|
+
function getApiUrl() {
|
|
2609
|
+
return process.env.API_URL || "http://localhost:3000/api/v1";
|
|
2610
|
+
}
|
|
2611
|
+
async function consumerFetch(path, token) {
|
|
2612
|
+
var _a;
|
|
2613
|
+
try {
|
|
2614
|
+
const res = await fetch(`${getApiUrl()}${path}`, {
|
|
2615
|
+
headers: {
|
|
2616
|
+
"Content-Type": "application/json",
|
|
2617
|
+
Authorization: `Bearer ${token}`
|
|
2618
|
+
},
|
|
2619
|
+
cache: "no-store"
|
|
2620
|
+
});
|
|
2621
|
+
if (!res.ok) return null;
|
|
2622
|
+
const json = await res.json();
|
|
2623
|
+
return (_a = json.data) != null ? _a : json;
|
|
2624
|
+
} catch (e) {
|
|
2625
|
+
return null;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
async function fetchConsumerMe(token) {
|
|
2629
|
+
return consumerFetch("/consumer/me", token);
|
|
2630
|
+
}
|
|
2631
|
+
async function fetchConsumerConversations(token) {
|
|
2632
|
+
var _a;
|
|
2633
|
+
const apiKey = process.env.API_KEY;
|
|
2634
|
+
const headers = __spreadValues({
|
|
2635
|
+
"Content-Type": "application/json",
|
|
2636
|
+
Authorization: `Bearer ${token}`
|
|
2637
|
+
}, apiKey ? { "X-API-Key": apiKey } : {});
|
|
2638
|
+
try {
|
|
2639
|
+
const res = await fetch(`${getApiUrl()}/consumer/me/conversations`, { headers, cache: "no-store" });
|
|
2640
|
+
if (!res.ok) return [];
|
|
2641
|
+
const json = await res.json();
|
|
2642
|
+
return (_a = json.data) != null ? _a : [];
|
|
2643
|
+
} catch (e) {
|
|
2644
|
+
return [];
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
async function fetchConsumerMessages(token, contactId) {
|
|
2648
|
+
var _a, _b;
|
|
2649
|
+
try {
|
|
2650
|
+
const res = await fetch(
|
|
2651
|
+
`${getApiUrl()}/consumer/me/contacts/${contactId}/messages?per_page=100`,
|
|
2652
|
+
{
|
|
2653
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
2654
|
+
cache: "no-store"
|
|
2655
|
+
}
|
|
2656
|
+
);
|
|
2657
|
+
if (!res.ok) return { messages: [], contact: null };
|
|
2658
|
+
const json = await res.json();
|
|
2659
|
+
return {
|
|
2660
|
+
messages: (_a = json.data) != null ? _a : [],
|
|
2661
|
+
contact: (_b = json.contact) != null ? _b : null
|
|
2662
|
+
};
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
return { messages: [], contact: null };
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// src/lib/server-api.ts
|
|
2669
|
+
var API_URL = process.env.API_URL || "http://localhost:3000/api/v1";
|
|
2670
|
+
var API_KEY = process.env.API_KEY || "";
|
|
2671
|
+
async function serverFetch(endpoint, options = {}) {
|
|
2672
|
+
var _a;
|
|
2673
|
+
const url = `${API_URL}${endpoint}`;
|
|
2674
|
+
try {
|
|
2675
|
+
const fetchOptions = {
|
|
2676
|
+
headers: {
|
|
2677
|
+
"X-API-Key": API_KEY,
|
|
2678
|
+
"Content-Type": "application/json"
|
|
2679
|
+
},
|
|
2680
|
+
cache: options.cache
|
|
2681
|
+
};
|
|
2682
|
+
if (options.revalidate) {
|
|
2683
|
+
fetchOptions.next = { revalidate: options.revalidate };
|
|
2684
|
+
}
|
|
2685
|
+
const response = await fetch(url, fetchOptions);
|
|
2686
|
+
if (!response.ok) {
|
|
2687
|
+
console.error(`[Server API] Error ${response.status} for ${endpoint}`);
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
const json = await response.json();
|
|
2691
|
+
return (_a = json.data) != null ? _a : json;
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
console.error(`[Server API] Failed to fetch ${endpoint}:`, error);
|
|
2694
|
+
return null;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
var defaultOptions = { revalidate: 60 };
|
|
2698
|
+
async function getCompanyInformation() {
|
|
2699
|
+
return serverFetch("/public/company_information", defaultOptions);
|
|
2700
|
+
}
|
|
2701
|
+
async function getServices() {
|
|
2702
|
+
return serverFetch("/public/services", defaultOptions);
|
|
2703
|
+
}
|
|
2704
|
+
async function getPackages() {
|
|
2705
|
+
return serverFetch("/public/packages", defaultOptions);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// src/design_system/portal/PortalPage.tsx
|
|
2709
|
+
var ALL_TABS = ["services", "packages", "specials", "messages", "book"];
|
|
2710
|
+
var DEFAULT_TABS = ["book", "services", "packages", "specials", "messages"];
|
|
2711
|
+
async function checkIframeAllowed(url) {
|
|
2712
|
+
var _a;
|
|
2713
|
+
try {
|
|
2714
|
+
const fetchOptions = {
|
|
2715
|
+
method: "HEAD",
|
|
2716
|
+
redirect: "follow",
|
|
2717
|
+
// Cache for 1 hour — booking platforms rarely change their embedding policy.
|
|
2718
|
+
next: { revalidate: 3600 }
|
|
2719
|
+
};
|
|
2720
|
+
const res = await fetch(url, fetchOptions);
|
|
2721
|
+
const xfo = (_a = res.headers.get("x-frame-options")) == null ? void 0 : _a.toUpperCase();
|
|
2722
|
+
if (xfo === "DENY" || xfo === "SAMEORIGIN") return false;
|
|
2723
|
+
const csp = res.headers.get("content-security-policy");
|
|
2724
|
+
if (csp) {
|
|
2725
|
+
const match = csp.match(/frame-ancestors\s+([^;]+)/i);
|
|
2726
|
+
if (match) {
|
|
2727
|
+
const policy = match[1].trim();
|
|
2728
|
+
if (policy === "'none'") return false;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
return true;
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
return false;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
function formatCents(cents) {
|
|
2737
|
+
return new Intl.NumberFormat("en-US", {
|
|
2738
|
+
style: "currency",
|
|
2739
|
+
currency: "USD",
|
|
2740
|
+
minimumFractionDigits: 0
|
|
2741
|
+
}).format(cents / 100);
|
|
2742
|
+
}
|
|
2743
|
+
function formatTime(iso) {
|
|
2744
|
+
return new Date(iso).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
|
|
2745
|
+
}
|
|
2746
|
+
function getInitials(str) {
|
|
2747
|
+
return str.split(" ").map((w) => w[0]).join("").slice(0, 2).toUpperCase();
|
|
2748
|
+
}
|
|
2749
|
+
function aggregateSpecials(services, packages) {
|
|
2750
|
+
var _a, _b, _c;
|
|
2751
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2752
|
+
const specials = [];
|
|
2753
|
+
for (const service of services) {
|
|
2754
|
+
for (const item of (_a = service.service_items) != null ? _a : []) {
|
|
2755
|
+
for (const offer of (_b = item.offers) != null ? _b : []) {
|
|
2756
|
+
if (offer.active === false || offer.expired) continue;
|
|
2757
|
+
if (seen.has(offer.id)) continue;
|
|
2758
|
+
seen.add(offer.id);
|
|
2759
|
+
specials.push({
|
|
2760
|
+
id: offer.id,
|
|
2761
|
+
name: offer.name,
|
|
2762
|
+
value_terms: offer.value_terms,
|
|
2763
|
+
expires_at: offer.expires_at,
|
|
2764
|
+
parentName: item.name,
|
|
2765
|
+
parentType: "service",
|
|
2766
|
+
photoAttachments: item.photo_attachments
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
for (const pkg of packages) {
|
|
2772
|
+
for (const offer of (_c = pkg.offers) != null ? _c : []) {
|
|
2773
|
+
if (offer.active === false || offer.expired) continue;
|
|
2774
|
+
if (seen.has(offer.id)) continue;
|
|
2775
|
+
seen.add(offer.id);
|
|
2776
|
+
specials.push({
|
|
2777
|
+
id: offer.id,
|
|
2778
|
+
name: offer.name,
|
|
2779
|
+
value_terms: offer.value_terms,
|
|
2780
|
+
expires_at: offer.expires_at,
|
|
2781
|
+
parentName: pkg.name,
|
|
2782
|
+
parentType: "package",
|
|
2783
|
+
photoAttachments: pkg.photo_attachments
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
return specials;
|
|
2788
|
+
}
|
|
2789
|
+
function LockButton({ label = "View price" }) {
|
|
2790
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2791
|
+
"button",
|
|
2792
|
+
{
|
|
2793
|
+
"data-open-login-modal": true,
|
|
2794
|
+
className: "cursor-pointer rounded px-1.5 py-0.5 text-xs text-quaternary underline underline-offset-2 transition-colors hover:text-secondary"
|
|
2795
|
+
},
|
|
2796
|
+
label
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
2799
|
+
function LoginWall({ message, cta }) {
|
|
2800
|
+
return /* @__PURE__ */ React8.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ React8.createElement("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-secondary" }, /* @__PURE__ */ React8.createElement("svg", { className: "size-5 text-quaternary", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5 }, /* @__PURE__ */ React8.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" }))), /* @__PURE__ */ React8.createElement("p", { className: "text-sm font-medium text-primary" }, message), /* @__PURE__ */ React8.createElement(
|
|
2801
|
+
"button",
|
|
2802
|
+
{
|
|
2803
|
+
"data-open-login-modal": true,
|
|
2804
|
+
className: "mt-4 cursor-pointer rounded-interactive bg-brand-solid px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-brand-solid_hover"
|
|
2805
|
+
},
|
|
2806
|
+
cta
|
|
2807
|
+
));
|
|
2808
|
+
}
|
|
2809
|
+
function EmptyState({ message }) {
|
|
2810
|
+
return /* @__PURE__ */ React8.createElement("div", { className: "rounded-component border border-secondary bg-secondary py-16 text-center" }, /* @__PURE__ */ React8.createElement("p", { className: "text-sm text-tertiary" }, message));
|
|
2811
|
+
}
|
|
2812
|
+
function ServiceItemRow({
|
|
2813
|
+
item,
|
|
2814
|
+
isLoggedIn,
|
|
2815
|
+
specialsEnabled,
|
|
2816
|
+
specialsHref
|
|
2817
|
+
}) {
|
|
2818
|
+
var _a;
|
|
2819
|
+
const activeOffers = ((_a = item.offers) != null ? _a : []).filter((o) => o.active !== false && !o.expired);
|
|
2820
|
+
return /* @__PURE__ */ React8.createElement("div", { className: "group flex items-center gap-3 px-4 py-3" }, /* @__PURE__ */ React8.createElement(
|
|
2821
|
+
RowThumbnail,
|
|
2822
|
+
{
|
|
2823
|
+
photoAttachments: item.photo_attachments,
|
|
2824
|
+
seed: `item-${item.id}`,
|
|
2825
|
+
alt: item.name
|
|
2826
|
+
}
|
|
2827
|
+
), /* @__PURE__ */ React8.createElement("div", { className: "flex-1 min-w-0 flex items-center justify-between gap-3" }, /* @__PURE__ */ React8.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React8.createElement("p", { className: "text-base font-medium text-primary" }, item.name), item.summary && /* @__PURE__ */ React8.createElement("p", { className: "mt-0.5 text-sm text-tertiary" }, item.summary), item.duration_minutes != null && item.duration_minutes > 0 && /* @__PURE__ */ React8.createElement("p", { className: "mt-0.5 text-xs text-quaternary" }, item.duration_minutes, " min"), specialsEnabled && activeOffers.length > 0 && /* @__PURE__ */ React8.createElement("div", { className: "mt-1.5 flex flex-wrap gap-1" }, activeOffers.map(
|
|
2828
|
+
(offer) => isLoggedIn ? /* @__PURE__ */ React8.createElement(
|
|
2829
|
+
Link,
|
|
2830
|
+
{
|
|
2831
|
+
key: offer.id,
|
|
2832
|
+
href: specialsHref,
|
|
2833
|
+
className: "inline-flex items-center rounded-badge bg-secondary border border-brand px-2 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors"
|
|
2834
|
+
},
|
|
2835
|
+
"Special: ",
|
|
2836
|
+
offer.name
|
|
2837
|
+
) : /* @__PURE__ */ React8.createElement(
|
|
2838
|
+
"button",
|
|
2839
|
+
{
|
|
2840
|
+
key: offer.id,
|
|
2841
|
+
"data-open-login-modal": true,
|
|
2842
|
+
"data-login-redirect": specialsHref,
|
|
2843
|
+
className: "inline-flex items-center rounded-badge bg-secondary border border-brand px-2 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors cursor-pointer"
|
|
2844
|
+
},
|
|
2845
|
+
"Special: ",
|
|
2846
|
+
offer.name
|
|
2847
|
+
)
|
|
2848
|
+
))), /* @__PURE__ */ React8.createElement("div", { className: "shrink-0 text-right" }, isLoggedIn ? item.price_cents != null ? /* @__PURE__ */ React8.createElement("span", { className: "text-sm font-semibold text-primary" }, formatCents(item.price_cents)) : item.pricing_info ? /* @__PURE__ */ React8.createElement("span", { className: "text-sm text-secondary" }, item.pricing_info) : null : item.price_cents != null || item.pricing_info ? /* @__PURE__ */ React8.createElement(LockButton, null) : null)));
|
|
2849
|
+
}
|
|
2850
|
+
function ServicesPanel({
|
|
2851
|
+
services,
|
|
2852
|
+
isLoggedIn,
|
|
2853
|
+
specialsEnabled,
|
|
2854
|
+
portalHref
|
|
2855
|
+
}) {
|
|
2856
|
+
const activeServices = services.filter((s) => {
|
|
2857
|
+
var _a, _b;
|
|
2858
|
+
return ((_b = (_a = s.service_items) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
|
|
2859
|
+
});
|
|
2860
|
+
const specialsHref = `${portalHref}?tab=specials`;
|
|
2861
|
+
if (activeServices.length === 0) {
|
|
2862
|
+
return /* @__PURE__ */ React8.createElement(EmptyState, { message: "No services listed yet." });
|
|
2863
|
+
}
|
|
2864
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(PortalTabTracker, { event: "ViewContent", params: { contentName: "Services", contentCategory: "Services" }, tab: "services" }), /* @__PURE__ */ React8.createElement("div", { className: "divide-y divide-tertiary rounded-component border border-secondary bg-primary overflow-hidden" }, activeServices.map((service) => {
|
|
2865
|
+
var _a, _b;
|
|
2866
|
+
return /* @__PURE__ */ React8.createElement("details", { key: service.id, className: "group", open: true }, /* @__PURE__ */ React8.createElement("summary", { className: "flex cursor-pointer list-none items-center justify-between px-5 py-4 hover:bg-secondary transition-colors" }, /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("span", { className: "font-medium text-primary" }, service.name), service.summary && /* @__PURE__ */ React8.createElement("p", { className: "mt-0.5 text-sm text-tertiary" }, service.summary)), /* @__PURE__ */ React8.createElement("div", { className: "ml-4 flex items-center gap-2 shrink-0" }, /* @__PURE__ */ React8.createElement("span", { className: "text-xs text-quaternary" }, (_b = (_a = service.service_items) == null ? void 0 : _a.length) != null ? _b : 0, " items"), /* @__PURE__ */ React8.createElement("svg", { className: "size-4 text-quaternary transition-transform group-open:rotate-180", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 }, /* @__PURE__ */ React8.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" })))), service.service_items && service.service_items.length > 0 && /* @__PURE__ */ React8.createElement("div", { className: "border-t border-tertiary bg-secondary divide-y divide-tertiary" }, service.service_items.map((item) => /* @__PURE__ */ React8.createElement(
|
|
2867
|
+
ServiceItemRow,
|
|
2868
|
+
{
|
|
2869
|
+
key: item.id,
|
|
2870
|
+
item,
|
|
2871
|
+
isLoggedIn,
|
|
2872
|
+
specialsEnabled,
|
|
2873
|
+
specialsHref
|
|
2874
|
+
}
|
|
2875
|
+
))));
|
|
2876
|
+
})));
|
|
2877
|
+
}
|
|
2878
|
+
function PackagesPanel({
|
|
2879
|
+
packages,
|
|
2880
|
+
isLoggedIn,
|
|
2881
|
+
specialsEnabled,
|
|
2882
|
+
portalHref
|
|
2883
|
+
}) {
|
|
2884
|
+
const specialsHref = `${portalHref}?tab=specials`;
|
|
2885
|
+
if (packages.length === 0) return /* @__PURE__ */ React8.createElement(EmptyState, { message: "No packages available yet." });
|
|
2886
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(PortalTabTracker, { event: "ViewContent", params: { contentName: "Packages", contentCategory: "Packages" }, tab: "packages" }), /* @__PURE__ */ React8.createElement("div", { className: "grid gap-4 sm:grid-cols-2" }, packages.map((pkg) => {
|
|
2887
|
+
var _a;
|
|
2888
|
+
const activeOffers = ((_a = pkg.offers) != null ? _a : []).filter((o) => o.active !== false && !o.expired);
|
|
2889
|
+
return /* @__PURE__ */ React8.createElement("div", { key: pkg.id, className: "group rounded-component border border-secondary bg-primary p-4 flex flex-col gap-3" }, /* @__PURE__ */ React8.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React8.createElement(
|
|
2890
|
+
RowThumbnail,
|
|
2891
|
+
{
|
|
2892
|
+
photoAttachments: pkg.photo_attachments,
|
|
2893
|
+
seed: `pkg-${pkg.id}`,
|
|
2894
|
+
alt: pkg.name,
|
|
2895
|
+
sizeClassName: "w-16 h-16"
|
|
2896
|
+
}
|
|
2897
|
+
), /* @__PURE__ */ React8.createElement("div", { className: "flex-1 min-w-0 flex items-start justify-between gap-2" }, /* @__PURE__ */ React8.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React8.createElement("h4", { className: "font-semibold text-primary" }, pkg.name), pkg.summary && /* @__PURE__ */ React8.createElement("p", { className: "mt-0.5 text-sm text-tertiary" }, pkg.summary)), /* @__PURE__ */ React8.createElement("div", { className: "shrink-0 text-right" }, isLoggedIn ? pkg.price_cents != null ? /* @__PURE__ */ React8.createElement("span", { className: "text-xl font-bold text-primary" }, formatCents(pkg.price_cents)) : pkg.pricing_info ? /* @__PURE__ */ React8.createElement("span", { className: "text-sm font-medium text-secondary" }, pkg.pricing_info) : null : pkg.price_cents != null || pkg.pricing_info ? /* @__PURE__ */ React8.createElement(LockButton, null) : null))), pkg.package_items && pkg.package_items.length > 0 && /* @__PURE__ */ React8.createElement("ul", { className: "space-y-1.5" }, pkg.package_items.map((item, idx) => {
|
|
2898
|
+
var _a2, _b;
|
|
2899
|
+
return /* @__PURE__ */ React8.createElement("li", { key: idx, className: "flex items-center gap-2 text-sm text-secondary" }, /* @__PURE__ */ React8.createElement("span", { className: "size-1.5 rounded-full bg-quaternary shrink-0" }), item.quantity > 1 && /* @__PURE__ */ React8.createElement("span", { className: "font-medium text-primary" }, item.quantity, "\xD7"), (_b = (_a2 = item.service_item) == null ? void 0 : _a2.name) != null ? _b : "\u2014");
|
|
2900
|
+
})), specialsEnabled && activeOffers.length > 0 && /* @__PURE__ */ React8.createElement("div", { className: "flex flex-wrap gap-1.5" }, activeOffers.map(
|
|
2901
|
+
(offer) => isLoggedIn ? /* @__PURE__ */ React8.createElement(
|
|
2902
|
+
Link,
|
|
2903
|
+
{
|
|
2904
|
+
key: offer.id,
|
|
2905
|
+
href: specialsHref,
|
|
2906
|
+
className: "inline-flex items-center rounded-badge bg-secondary border border-brand px-2.5 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors"
|
|
2907
|
+
},
|
|
2908
|
+
"Special: ",
|
|
2909
|
+
offer.name
|
|
2910
|
+
) : /* @__PURE__ */ React8.createElement(
|
|
2911
|
+
"button",
|
|
2912
|
+
{
|
|
2913
|
+
key: offer.id,
|
|
2914
|
+
"data-open-login-modal": true,
|
|
2915
|
+
"data-login-redirect": specialsHref,
|
|
2916
|
+
className: "inline-flex items-center rounded-badge bg-secondary border border-brand px-2.5 py-0.5 text-xs font-medium text-brand-secondary hover:bg-secondary_hover transition-colors cursor-pointer"
|
|
2917
|
+
},
|
|
2918
|
+
"Special: ",
|
|
2919
|
+
offer.name
|
|
2920
|
+
)
|
|
2921
|
+
)));
|
|
2922
|
+
})));
|
|
2923
|
+
}
|
|
2924
|
+
function SpecialsPanel({ specials }) {
|
|
2925
|
+
if (specials.length === 0) {
|
|
2926
|
+
return /* @__PURE__ */ React8.createElement(EmptyState, { message: "No active specials right now. Check back soon." });
|
|
2927
|
+
}
|
|
2928
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(PortalTabTracker, { event: "ViewContent", params: { contentName: "Specials", contentCategory: "Specials" }, tab: "specials" }), /* @__PURE__ */ React8.createElement("div", { className: "space-y-3" }, specials.map((special) => /* @__PURE__ */ React8.createElement("div", { key: special.id, className: "group flex items-start gap-3 rounded-component border border-secondary bg-primary px-4 py-4" }, /* @__PURE__ */ React8.createElement(
|
|
2929
|
+
RowThumbnail,
|
|
2930
|
+
{
|
|
2931
|
+
photoAttachments: special.photoAttachments,
|
|
2932
|
+
seed: `special-${special.id}`,
|
|
2933
|
+
alt: special.parentName
|
|
2934
|
+
}
|
|
2935
|
+
), /* @__PURE__ */ React8.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React8.createElement("p", { className: "font-semibold text-primary" }, special.name), special.value_terms && /* @__PURE__ */ React8.createElement("p", { className: "mt-0.5 text-sm text-secondary" }, special.value_terms), /* @__PURE__ */ React8.createElement("p", { className: "mt-1 text-xs text-quaternary" }, special.parentType === "service" ? "Service" : "Package", ": ", special.parentName), special.expires_at && /* @__PURE__ */ React8.createElement("p", { className: "mt-0.5 text-xs text-quaternary" }, "Expires ", new Date(special.expires_at).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })))))));
|
|
2936
|
+
}
|
|
2937
|
+
function MessagesPanel({
|
|
2938
|
+
messages,
|
|
2939
|
+
contactSummary,
|
|
2940
|
+
contactId,
|
|
2941
|
+
businessName
|
|
2942
|
+
}) {
|
|
2943
|
+
var _a, _b;
|
|
2944
|
+
const threadBusiness = ((_a = contactSummary == null ? void 0 : contactSummary.business) == null ? void 0 : _a.company_name) || ((_b = contactSummary == null ? void 0 : contactSummary.business) == null ? void 0 : _b.name) || businessName;
|
|
2945
|
+
return /* @__PURE__ */ React8.createElement("div", { className: "flex flex-col rounded-component border border-secondary bg-primary overflow-hidden" }, /* @__PURE__ */ React8.createElement("div", { className: "flex items-center gap-2.5 border-b border-secondary px-4 py-3 shrink-0" }, /* @__PURE__ */ React8.createElement("div", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-brand-solid text-xs font-semibold text-white shrink-0" }, getInitials(threadBusiness)), /* @__PURE__ */ React8.createElement("span", { className: "text-sm font-semibold text-primary" }, threadBusiness)), /* @__PURE__ */ React8.createElement("div", { className: "overflow-y-auto px-4 py-4 space-y-3", style: { height: "70vh" } }, messages.length === 0 ? /* @__PURE__ */ React8.createElement("div", { className: "flex flex-col items-center justify-center h-full py-16 text-center" }, /* @__PURE__ */ React8.createElement("div", { className: "mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-secondary" }, /* @__PURE__ */ React8.createElement("svg", { className: "size-5 text-quaternary", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5 }, /* @__PURE__ */ React8.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" }))), /* @__PURE__ */ React8.createElement("p", { className: "text-sm font-medium text-primary" }, "No messages yet"), /* @__PURE__ */ React8.createElement("p", { className: "mt-1 text-xs text-tertiary" }, "Send a message to start the conversation.")) : messages.map((m) => {
|
|
2946
|
+
const isOutbound = m.direction === "outbound";
|
|
2947
|
+
return /* @__PURE__ */ React8.createElement("div", { key: m.id, className: `flex ${isOutbound ? "justify-start" : "justify-end"}` }, /* @__PURE__ */ React8.createElement("div", { className: `max-w-[80%] rounded-component px-4 py-2.5 text-sm ${isOutbound ? "bg-secondary text-primary" : "bg-brand-solid text-white"}` }, m.sender_display_name && /* @__PURE__ */ React8.createElement("p", { className: "mb-0.5 text-xs font-medium opacity-60" }, m.sender_display_name), /* @__PURE__ */ React8.createElement("p", { className: "whitespace-pre-wrap leading-relaxed" }, m.body || "\u2014"), /* @__PURE__ */ React8.createElement("p", { className: "mt-1 text-right text-[10px] opacity-50" }, formatTime(m.created_at))));
|
|
2948
|
+
})), contactId && /* @__PURE__ */ React8.createElement(MessageComposer, { contactId }));
|
|
2949
|
+
}
|
|
2950
|
+
function BookPanel({
|
|
2951
|
+
bookingHref,
|
|
2952
|
+
bookingLabel,
|
|
2953
|
+
bookingAllowsIframe,
|
|
2954
|
+
isLoggedIn,
|
|
2955
|
+
businessName
|
|
2956
|
+
}) {
|
|
2957
|
+
if (!isLoggedIn) {
|
|
2958
|
+
return /* @__PURE__ */ React8.createElement(LoginWall, { message: "Continue to view booking options.", cta: "View Booking Options" });
|
|
2959
|
+
}
|
|
2960
|
+
if (bookingAllowsIframe) {
|
|
2961
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(PortalTabTracker, { event: "InitiateCheckout", tab: "booking" }), /* @__PURE__ */ React8.createElement(BookIframePanel, { bookingHref, businessName }));
|
|
2962
|
+
}
|
|
2963
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(PortalTabTracker, { event: "InitiateCheckout", tab: "booking" }), /* @__PURE__ */ React8.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ React8.createElement("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-secondary" }, /* @__PURE__ */ React8.createElement("svg", { className: "size-5 text-quaternary", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5 }, /* @__PURE__ */ React8.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 9v7.5" }))), /* @__PURE__ */ React8.createElement("p", { className: "text-sm font-medium text-primary" }, "Ready to book?"), /* @__PURE__ */ React8.createElement("p", { className: "mt-1 text-sm text-tertiary" }, "You'll be taken to our booking system."), /* @__PURE__ */ React8.createElement(
|
|
2964
|
+
"a",
|
|
2965
|
+
{
|
|
2966
|
+
href: bookingHref,
|
|
2967
|
+
target: "_blank",
|
|
2968
|
+
rel: "noopener noreferrer",
|
|
2969
|
+
className: "mt-5 inline-flex items-center gap-2 rounded-interactive bg-brand-solid px-6 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-brand-solid_hover"
|
|
2970
|
+
},
|
|
2971
|
+
bookingLabel,
|
|
2972
|
+
/* @__PURE__ */ React8.createElement("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 }, /* @__PURE__ */ React8.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" }))
|
|
2973
|
+
)));
|
|
2974
|
+
}
|
|
2975
|
+
async function PortalPage({
|
|
2976
|
+
searchParams,
|
|
2977
|
+
portalHref = "/portal",
|
|
2978
|
+
bookingHref,
|
|
2979
|
+
bookingLabel = "Book Now",
|
|
2980
|
+
enabledTabs,
|
|
2981
|
+
tabLabels,
|
|
2982
|
+
forceExternalBooking = false,
|
|
2983
|
+
contactHref = "/contact"
|
|
2984
|
+
}) {
|
|
2985
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2986
|
+
const params = (_a = await searchParams) != null ? _a : {};
|
|
2987
|
+
const contactIdParsed = params.contact ? parseInt(params.contact, 10) : NaN;
|
|
2988
|
+
const contactId = Number.isFinite(contactIdParsed) ? contactIdParsed : null;
|
|
2989
|
+
const cookieStore = await cookies();
|
|
2990
|
+
const token = (_c = (_b = cookieStore.get(CONSUMER_TOKEN_COOKIE)) == null ? void 0 : _b.value) != null ? _c : null;
|
|
2991
|
+
const [services, packages, companyInformation, consumer, conversations] = await Promise.all([
|
|
2992
|
+
getServices(),
|
|
2993
|
+
getPackages(),
|
|
2994
|
+
getCompanyInformation(),
|
|
2995
|
+
token ? fetchConsumerMe(token) : Promise.resolve(null),
|
|
2996
|
+
token ? fetchConsumerConversations(token) : Promise.resolve([])
|
|
2997
|
+
]);
|
|
2998
|
+
const resolvedBookingHref = (_d = bookingHref != null ? bookingHref : companyInformation == null ? void 0 : companyInformation.external_management_url) != null ? _d : null;
|
|
2999
|
+
const hasCustomTabOrder = Array.isArray(enabledTabs) && enabledTabs.length > 0;
|
|
3000
|
+
const requestedTabs = (enabledTabs != null ? enabledTabs : DEFAULT_TABS).filter((tab2, index, arr) => ALL_TABS.includes(tab2) && arr.indexOf(tab2) === index);
|
|
3001
|
+
const normalizedTabs = requestedTabs.length > 0 ? requestedTabs : DEFAULT_TABS;
|
|
3002
|
+
const nonBookTabs = normalizedTabs.filter((tab2) => tab2 !== "book");
|
|
3003
|
+
const includesBookTab = normalizedTabs.includes("book");
|
|
3004
|
+
const labelForTab = (tab2) => {
|
|
3005
|
+
var _a2, _b2, _c2, _d2, _e2;
|
|
3006
|
+
if (tab2 === "book") return (_a2 = tabLabels == null ? void 0 : tabLabels.book) != null ? _a2 : bookingLabel;
|
|
3007
|
+
if (tab2 === "services") return (_b2 = tabLabels == null ? void 0 : tabLabels.services) != null ? _b2 : "Services";
|
|
3008
|
+
if (tab2 === "packages") return (_c2 = tabLabels == null ? void 0 : tabLabels.packages) != null ? _c2 : "Packages";
|
|
3009
|
+
if (tab2 === "specials") return (_d2 = tabLabels == null ? void 0 : tabLabels.specials) != null ? _d2 : "Specials";
|
|
3010
|
+
return (_e2 = tabLabels == null ? void 0 : tabLabels.messages) != null ? _e2 : "Messages";
|
|
3011
|
+
};
|
|
3012
|
+
const tabs = hasCustomTabOrder ? normalizedTabs.map((tab2) => ({ id: tab2, label: labelForTab(tab2) })) : [
|
|
3013
|
+
...includesBookTab && resolvedBookingHref ? [{ id: "book", label: labelForTab("book") }] : [],
|
|
3014
|
+
...nonBookTabs.map((tab2) => ({ id: tab2, label: labelForTab(tab2) })),
|
|
3015
|
+
...includesBookTab && !resolvedBookingHref ? [{ id: "book", label: labelForTab("book") }] : []
|
|
3016
|
+
];
|
|
3017
|
+
const selectableTabIds = tabs.map((tab2) => tab2.id);
|
|
3018
|
+
const tabParam = selectableTabIds.includes(params.tab) ? params.tab : null;
|
|
3019
|
+
const tab = tabParam != null ? tabParam : tabs[0].id;
|
|
3020
|
+
const bookingAllowsIframe = !forceExternalBooking && tab === "book" && resolvedBookingHref ? await checkIframeAllowed(resolvedBookingHref) : false;
|
|
3021
|
+
const businessName = (companyInformation == null ? void 0 : companyInformation.company_name) || "Member Portal";
|
|
3022
|
+
const isLoggedIn = !!consumer;
|
|
3023
|
+
const resolvedContactId = (_f = contactId != null ? contactId : (_e = conversations[0]) == null ? void 0 : _e.contact_id) != null ? _f : null;
|
|
3024
|
+
const businessContact = (_g = consumer == null ? void 0 : consumer.contacts) == null ? void 0 : _g.find((c) => c.id === resolvedContactId);
|
|
3025
|
+
const consumerDisplayName = (businessContact == null ? void 0 : businessContact.display_name) || ((_i = (_h = consumer == null ? void 0 : consumer.contacts) == null ? void 0 : _h[0]) == null ? void 0 : _i.display_name) || (consumer == null ? void 0 : consumer.primary_identifier) || (consumer == null ? void 0 : consumer.email) || (consumer == null ? void 0 : consumer.phone) || null;
|
|
3026
|
+
const messageData = token && tab === "messages" && resolvedContactId ? await fetchConsumerMessages(token, resolvedContactId) : { messages: [], contact: null };
|
|
3027
|
+
const serviceList = services != null ? services : [];
|
|
3028
|
+
const packageList = packages != null ? packages : [];
|
|
3029
|
+
const specials = aggregateSpecials(serviceList, packageList);
|
|
3030
|
+
const specialsEnabled = selectableTabIds.includes("specials");
|
|
3031
|
+
return /* @__PURE__ */ React8.createElement("div", { className: "min-h-screen bg-primary" }, /* @__PURE__ */ React8.createElement("div", { className: "border-b border-secondary bg-primary sticky top-0 z-40 shrink-0" }, /* @__PURE__ */ React8.createElement("div", { className: "mx-auto max-w-4xl px-4 py-4" }, /* @__PURE__ */ React8.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React8.createElement("div", null, /* @__PURE__ */ React8.createElement("p", { className: "text-[10px] font-semibold uppercase tracking-widest text-quaternary" }, "Member Portal"), /* @__PURE__ */ React8.createElement("h1", { className: "text-xl font-bold text-primary leading-tight" }, businessName)), /* @__PURE__ */ React8.createElement("div", { className: "flex items-center gap-3" }, isLoggedIn ? /* @__PURE__ */ React8.createElement("div", { className: "flex items-center gap-3" }, consumerDisplayName && /* @__PURE__ */ React8.createElement("div", { className: "hidden sm:flex items-center gap-2 rounded-full border border-secondary bg-secondary px-3 py-1.5" }, /* @__PURE__ */ React8.createElement("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-brand-solid text-[9px] font-bold text-white shrink-0" }, getInitials(consumerDisplayName)), /* @__PURE__ */ React8.createElement("span", { className: "text-xs font-medium text-secondary max-w-[120px] truncate" }, consumerDisplayName)), /* @__PURE__ */ React8.createElement(LogoutButton, null)) : /* @__PURE__ */ React8.createElement(
|
|
3032
|
+
"button",
|
|
3033
|
+
{
|
|
3034
|
+
"data-open-login-modal": true,
|
|
3035
|
+
className: "cursor-pointer rounded-interactive border border-secondary px-3 py-1.5 text-xs font-medium text-secondary hover:bg-secondary transition-colors"
|
|
3036
|
+
},
|
|
3037
|
+
"Sign in"
|
|
3038
|
+
), /* @__PURE__ */ React8.createElement("div", { className: "border-l border-tertiary pl-3" }, /* @__PURE__ */ React8.createElement(
|
|
3039
|
+
"a",
|
|
3040
|
+
{
|
|
3041
|
+
href: KEYSTONE_APP_URL,
|
|
3042
|
+
target: "_blank",
|
|
3043
|
+
rel: "noopener noreferrer",
|
|
3044
|
+
"aria-label": "Visit Keystone website"
|
|
3045
|
+
},
|
|
3046
|
+
/* @__PURE__ */ React8.createElement(
|
|
3047
|
+
KeystoneBrandLockup,
|
|
3048
|
+
{
|
|
3049
|
+
className: "gap-2",
|
|
3050
|
+
iconClassName: "h-3.5",
|
|
3051
|
+
wordmarkClassName: "hidden sm:block h-2.5"
|
|
3052
|
+
}
|
|
3053
|
+
)
|
|
3054
|
+
)))), /* @__PURE__ */ React8.createElement("div", { className: "relative mt-4" }, /* @__PURE__ */ React8.createElement("div", { className: "flex gap-1 overflow-x-auto pb-px scrollbar-none" }, tabs.map((t) => {
|
|
3055
|
+
const isActive = tab === t.id;
|
|
3056
|
+
const isGated = !isLoggedIn && (t.id === "specials" || t.id === "messages" || t.id === "book");
|
|
3057
|
+
const href = `${portalHref}?tab=${t.id}`;
|
|
3058
|
+
const className = `shrink-0 rounded-interactive px-4 py-2 text-sm font-medium transition-colors whitespace-nowrap ${isActive ? "bg-brand-solid text-white" : "text-secondary hover:bg-secondary hover:text-primary"}`;
|
|
3059
|
+
if (isGated) {
|
|
3060
|
+
return /* @__PURE__ */ React8.createElement(
|
|
3061
|
+
"button",
|
|
3062
|
+
{
|
|
3063
|
+
key: t.id,
|
|
3064
|
+
"data-open-login-modal": true,
|
|
3065
|
+
"data-login-redirect": href,
|
|
3066
|
+
className
|
|
3067
|
+
},
|
|
3068
|
+
t.label
|
|
3069
|
+
);
|
|
3070
|
+
}
|
|
3071
|
+
return /* @__PURE__ */ React8.createElement(Link, { key: t.id, href, className }, t.label);
|
|
3072
|
+
})), /* @__PURE__ */ React8.createElement("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-8 bg-gradient-to-l from-primary to-transparent md:hidden" })))), /* @__PURE__ */ React8.createElement("div", { className: "mx-auto max-w-4xl px-4 py-8" }, tab === "services" && /* @__PURE__ */ React8.createElement(
|
|
3073
|
+
ServicesPanel,
|
|
3074
|
+
{
|
|
3075
|
+
services: serviceList,
|
|
3076
|
+
isLoggedIn,
|
|
3077
|
+
specialsEnabled,
|
|
3078
|
+
portalHref
|
|
3079
|
+
}
|
|
3080
|
+
), tab === "packages" && /* @__PURE__ */ React8.createElement(
|
|
3081
|
+
PackagesPanel,
|
|
3082
|
+
{
|
|
3083
|
+
packages: packageList,
|
|
3084
|
+
isLoggedIn,
|
|
3085
|
+
specialsEnabled,
|
|
3086
|
+
portalHref
|
|
3087
|
+
}
|
|
3088
|
+
), tab === "specials" && (isLoggedIn ? /* @__PURE__ */ React8.createElement(SpecialsPanel, { specials }) : /* @__PURE__ */ React8.createElement(LoginWall, { message: "Continue to view specials.", cta: "View Specials" })), tab === "messages" && (isLoggedIn ? /* @__PURE__ */ React8.createElement(
|
|
3089
|
+
MessagesPanel,
|
|
3090
|
+
{
|
|
3091
|
+
messages: messageData.messages,
|
|
3092
|
+
contactSummary: messageData.contact,
|
|
3093
|
+
contactId: resolvedContactId,
|
|
3094
|
+
businessName
|
|
3095
|
+
}
|
|
3096
|
+
) : /* @__PURE__ */ React8.createElement(LoginWall, { message: "Continue to view your messages.", cta: "View Messages" })), tab === "book" && (resolvedBookingHref ? /* @__PURE__ */ React8.createElement(
|
|
3097
|
+
BookPanel,
|
|
3098
|
+
{
|
|
3099
|
+
bookingHref: resolvedBookingHref,
|
|
3100
|
+
bookingLabel,
|
|
3101
|
+
bookingAllowsIframe,
|
|
3102
|
+
isLoggedIn,
|
|
3103
|
+
businessName
|
|
3104
|
+
}
|
|
3105
|
+
) : /* @__PURE__ */ React8.createElement("div", { className: "flex flex-col items-center justify-center py-20 text-center" }, /* @__PURE__ */ React8.createElement("div", { className: "mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-secondary" }, /* @__PURE__ */ React8.createElement("svg", { className: "size-5 text-quaternary", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5 }, /* @__PURE__ */ React8.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 9v7.5" }))), /* @__PURE__ */ React8.createElement("p", { className: "text-sm font-medium text-primary" }, "Ready to book?"), /* @__PURE__ */ React8.createElement("p", { className: "mt-1 text-sm text-tertiary max-w-xs" }, "Give us a call or submit a request on our", " ", /* @__PURE__ */ React8.createElement("a", { href: contactHref, className: "underline underline-offset-2 hover:text-primary transition-colors" }, "contact page"), " ", "and we'll get you scheduled.")))), /* @__PURE__ */ React8.createElement(LoginModalController, null));
|
|
3106
|
+
}
|
|
3107
|
+
export {
|
|
3108
|
+
LoginForm,
|
|
3109
|
+
LoginModalController,
|
|
3110
|
+
LogoutButton,
|
|
3111
|
+
PortalPage
|
|
3112
|
+
};
|
|
3113
|
+
//# sourceMappingURL=index.js.map
|