@evinvest/kitstart 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +147 -10
  2. package/dist/core/accept.d.ts +14 -0
  3. package/dist/core/accept.js +21 -3
  4. package/dist/core/analytics.d.ts +11 -2
  5. package/dist/core/analytics.js +22 -0
  6. package/dist/core/channels.d.ts +12 -4
  7. package/dist/core/channels.js +11 -1
  8. package/dist/core/lead-capture-text.d.ts +6 -2
  9. package/dist/core/lead.d.ts +33 -10
  10. package/dist/core/lead.js +21 -8
  11. package/dist/core/messenger-message.d.ts +31 -0
  12. package/dist/core/messenger-message.js +33 -0
  13. package/dist/core/messenger-text.d.ts +104 -0
  14. package/dist/core/messenger-text.js +150 -0
  15. package/dist/core/messenger.d.ts +92 -0
  16. package/dist/core/messenger.js +55 -0
  17. package/dist/core/place/index.d.ts +1 -1
  18. package/dist/core/place/live.d.ts +5 -1
  19. package/dist/core/place/live.js +22 -1
  20. package/dist/core/place/types.d.ts +17 -1
  21. package/dist/core/site.d.ts +8 -1
  22. package/dist/core/site.js +22 -1
  23. package/dist/index.d.ts +8 -5
  24. package/dist/index.js +7 -4
  25. package/dist/next/quote-route.js +4 -7
  26. package/dist/react/AnalyticsBoundary.js +3 -3
  27. package/dist/react/LeadCapture.d.ts +24 -1
  28. package/dist/react/LeadCapture.js +96 -11
  29. package/dist/react/LeadCaptureChannels.d.ts +7 -2
  30. package/dist/react/LeadCaptureChannels.js +2 -2
  31. package/dist/react/LeadCaptureEstimate.d.ts +6 -0
  32. package/dist/react/LeadCaptureFields.d.ts +17 -0
  33. package/dist/react/LeadCaptureFields.js +42 -33
  34. package/dist/react/LeadCaptureMessenger.d.ts +41 -0
  35. package/dist/react/LeadCaptureMessenger.js +133 -0
  36. package/dist/react/LeadCapturePrice.d.ts +9 -3
  37. package/dist/react/LeadCapturePrice.js +24 -4
  38. package/dist/react/LeadCaptureSubmit.d.ts +3 -0
  39. package/dist/react/LeadCaptureSubmit.js +2 -2
  40. package/dist/react/LeadCaptureTiles.js +9 -4
  41. package/dist/react/analytics-context.d.ts +9 -3
  42. package/dist/react/analytics-context.js +7 -3
  43. package/dist/react/index.d.ts +1 -0
  44. package/dist/react/messenger/action.d.ts +144 -0
  45. package/dist/react/messenger/action.js +301 -0
  46. package/dist/react/messenger/chip.d.ts +8 -0
  47. package/dist/react/messenger/chip.js +22 -0
  48. package/dist/react/messenger/fallback.d.ts +8 -0
  49. package/dist/react/messenger/fallback.js +11 -0
  50. package/dist/react/messenger/saga.d.ts +10 -0
  51. package/dist/react/messenger/saga.js +33 -0
  52. package/dist/react/messenger/segment.d.ts +8 -0
  53. package/dist/react/messenger/segment.js +15 -0
  54. package/dist/react/messenger/select.d.ts +10 -0
  55. package/dist/react/messenger/select.js +26 -0
  56. package/dist/react/messenger/sheet.d.ts +10 -0
  57. package/dist/react/messenger/sheet.js +57 -0
  58. package/dist/react/messenger/split.d.ts +9 -0
  59. package/dist/react/messenger/split.js +17 -0
  60. package/dist/react/messenger/swap.d.ts +10 -0
  61. package/dist/react/messenger/swap.js +18 -0
  62. package/dist/react/messenger/thanks.d.ts +10 -0
  63. package/dist/react/messenger/thanks.js +18 -0
  64. package/dist/react/messenger/tiles.d.ts +8 -0
  65. package/dist/react/messenger/tiles.js +20 -0
  66. package/dist/react/messenger/types.d.ts +98 -0
  67. package/dist/react/messenger/types.js +1 -0
  68. package/dist/react/messenger/urgency.d.ts +10 -0
  69. package/dist/react/messenger/urgency.js +29 -0
  70. package/dist/react/use-lead-error.d.ts +3 -3
  71. package/dist/react/use-lead-events.d.ts +21 -1
  72. package/dist/react/use-lead-events.js +9 -3
  73. package/dist/react/use-lead-submit.d.ts +20 -6
  74. package/dist/react/use-lead-submit.js +23 -1
  75. package/dist/server/index.d.ts +1 -1
  76. package/dist/server/lead-store-sqlite.js +11 -5
  77. package/dist/server/lead-webhook.d.ts +30 -5
  78. package/dist/server/lead-webhook.js +13 -7
  79. package/dist/server/notify.js +11 -5
  80. package/dist/testing/index.d.ts +1 -1
  81. package/dist/testing/index.js +1 -1
  82. package/dist/testing/landing-contract.js +6 -1
  83. package/dist/testing/lead-rules.d.ts +8 -0
  84. package/dist/testing/lead-rules.js +30 -1
  85. package/package.json +5 -2
  86. package/template/flake.nix +1 -1
  87. package/template/package.json +2 -2
@@ -0,0 +1,301 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { whatsappHref } from "@evinvest/marketing";
4
+ import { Badge, Button, cn, Select, SelectContent, SelectItem, SelectTrigger } from "@evinvest/uikit";
5
+ import { useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
6
+ import { fillText } from "../../core/lead-capture-format.js";
7
+ import { telegramHref } from "../../core/messenger.js";
8
+ import { messengerMessage } from "../../core/messenger-message.js";
9
+ import { messengerTextOf } from "../../core/messenger-text.js";
10
+ import { phoneProblem } from "../../core/phone.js";
11
+ import { partWithLeading } from "../parts.js";
12
+ import { useAnalyticsId } from "../analytics-context.js";
13
+ import { postInBackground } from "../use-lead-submit.js";
14
+ /** The variant's words, the brand's over the kit's. */
15
+ export const wordsOf = (kit) => messengerTextOf(kit.text, kit.locale);
16
+ const formOf = (kit) => {
17
+ if (typeof document === "undefined")
18
+ return null;
19
+ const el = document.getElementById(`${kit.id}-form`);
20
+ return el instanceof HTMLFormElement ? el : null;
21
+ };
22
+ /** A posted field's value as it stands — an element of the form wherever it is drawn. */
23
+ function valueOf(form, name) {
24
+ const el = form?.elements.namedItem(name);
25
+ return el instanceof HTMLInputElement || el instanceof HTMLSelectElement ? el.value.trim() : "";
26
+ }
27
+ export function useMessage(kit, own) {
28
+ // A variant's own timing (`urgency`), else the estimate's answer the brand named.
29
+ const timing = own === undefined ? kit.timing : own === null ? null : { label: own, short: own };
30
+ const [postcode, setPostcode] = useState("");
31
+ useEffect(() => {
32
+ const form = formOf(kit);
33
+ if (!form)
34
+ return;
35
+ const read = () => setPostcode(valueOf(form, kit.wire.locality));
36
+ read();
37
+ form.addEventListener("input", read);
38
+ form.addEventListener("change", read);
39
+ return () => {
40
+ form.removeEventListener("input", read);
41
+ form.removeEventListener("change", read);
42
+ };
43
+ }, [kit.id, kit.wire.locality]);
44
+ const words = wordsOf(kit);
45
+ // The need with the estimate's answers: «Ménage standard · 2 ch. · 40–70 m²».
46
+ const need = kit.needLabel ? [kit.needLabel, ...kit.answers].join(" · ") : null;
47
+ const compose = (code) => messengerMessage({ brand: kit.brand, need, postcode: code, price: kit.priceText, timing: timing?.label ?? null, ref: kit.messageRef }, words);
48
+ const ref = kit.messageRef ? fillText(words.messageRef, { ref: kit.messageRef }) : null;
49
+ const price = kit.priceText ? fillText(words.messengerPreviewPrice, { price: kit.priceText }) : null;
50
+ const preview = [fillText(words.messageHello, { brand: kit.brand }), need, timing?.short, price, postcode, ref].filter(Boolean).join(" · ");
51
+ return { message: compose(postcode), preview, compose: () => compose(valueOf(formOf(kit), kit.wire.locality)) };
52
+ }
53
+ /** Where a messenger CTA leads: wa.me with the message, or the bot with the reference. */
54
+ export function hrefOf(kit, channel, message) {
55
+ if (channel === "whatsapp")
56
+ return kit.facts.whatsapp ? whatsappHref(kit.facts.whatsapp, message) : null;
57
+ return kit.facts.telegram ? telegramHref(kit.facts.telegram, kit.messageRef ?? undefined) : null;
58
+ }
59
+ /** A computer: no touch to hand a link to an app, and a screen wide enough to read a QR code off. */
60
+ const isDesktop = () => typeof window.matchMedia === "function" && window.matchMedia("(hover: hover) and (pointer: fine)").matches && window.innerWidth >= 768;
61
+ /** The browsers of apps that hold a link to themselves: WhatsApp may not open from them. */
62
+ const IN_APP = /Instagram|FBAN|FBAV|TikTok|musical_ly|BytedanceWebview|\bLine\/|Snapchat/i;
63
+ const noop = () => () => undefined;
64
+ /** Whether the page runs in such a browser — after hydration only: the server cannot know. */
65
+ function useInApp() {
66
+ return useSyncExternalStore(noop, () => IN_APP.test(navigator.userAgent), () => false);
67
+ }
68
+ /**
69
+ * The tap on a messenger CTA, shared by every variant: a number typed must be
70
+ * one (the browser says so, nothing leaves); the lead is posted in the
71
+ * background; on a phone the link opens the app, on a computer WhatsApp is a
72
+ * QR code in place and Telegram a new tab. Back on the page, the card asks
73
+ * whether the message went, with the way to a call when it did not.
74
+ */
75
+ export function useMessengerAction(kit, message, onCallback,
76
+ /**
77
+ * `followUp`: the lead is already sent (`thanks`) — a tap posts nothing,
78
+ * keeps the reference and asks nothing on the way back.
79
+ */
80
+ options = {}) {
81
+ const followUp = options.followUp === true;
82
+ const analyticsId = useAnalyticsId();
83
+ const words = wordsOf(kit);
84
+ const [qr, setQr] = useState(null);
85
+ const [back, setBack] = useState(null);
86
+ const left = useRef(null);
87
+ useEffect(() => {
88
+ let hidden = false;
89
+ const change = () => {
90
+ if (document.visibilityState === "hidden")
91
+ hidden = true;
92
+ else if (hidden && left.current) {
93
+ hidden = false;
94
+ setBack(left.current);
95
+ left.current = null;
96
+ }
97
+ };
98
+ document.addEventListener("visibilitychange", change);
99
+ return () => document.removeEventListener("visibilitychange", change);
100
+ }, []);
101
+ const link = channel => {
102
+ // The render's message — the server has no form to read; the tap reads it again.
103
+ const href = hrefOf(kit, channel, message.message);
104
+ if (!href)
105
+ return null;
106
+ if (!kit.ready)
107
+ return { "aria-disabled": true, onClick: event => event.preventDefault() };
108
+ const onClick = (event) => {
109
+ const form = formOf(kit);
110
+ const phone = form?.elements.namedItem(kit.wire.mobile);
111
+ if (phone instanceof HTMLInputElement && phone.value.trim() !== "" && phoneProblem(phone.value) !== null) {
112
+ event.preventDefault();
113
+ phone.reportValidity();
114
+ return;
115
+ }
116
+ const ref = kit.messageRef;
117
+ // What is on screen now; the link follows it before the browser does.
118
+ const fresh = hrefOf(kit, channel, message.compose()) ?? href;
119
+ event.currentTarget.href = fresh;
120
+ if (form && !followUp)
121
+ void postInBackground(form, { channel, ...(ref ? { message_ref: ref } : {}) }, analyticsId);
122
+ const desktop = isDesktop();
123
+ kit.events.messengerOpen({ channel, device: desktop ? "desktop" : "mobile", inapp: IN_APP.test(navigator.userAgent), ref });
124
+ if (desktop && channel === "whatsapp") {
125
+ event.preventDefault();
126
+ setQr(fresh);
127
+ kit.events.qrShown();
128
+ }
129
+ else if (!followUp) {
130
+ left.current = { channel, ref, href: fresh };
131
+ }
132
+ // Never renewed here: React redraws the link before the browser follows
133
+ // it, and the visitor would leave with a reference the lead does not
134
+ // carry. A second tap on the same lead is the same reference and the
135
+ // same submission id — one lead.
136
+ };
137
+ return channel === "telegram" ? { href, onClick, target: "_blank", rel: "noopener" } : { href, onClick };
138
+ };
139
+ const callback = () => {
140
+ setQr(null);
141
+ onCallback();
142
+ };
143
+ const panel = qr && _jsx(QrPanel, { href: qr, words: words, onCallback: followUp ? null : callback, kit: kit });
144
+ const answer = (said) => {
145
+ if (back)
146
+ kit.events.messengerReturn({ channel: back.channel, answer: said, ref: back.ref });
147
+ setBack(null);
148
+ // Back from the chat and answered: the next lead is another, with a reference of its own.
149
+ if (!followUp)
150
+ kit.renewRef();
151
+ if (said === "failed")
152
+ onCallback();
153
+ };
154
+ const overlay = back && _jsx(ReturnScreen, { left: back, words: words, onAnswer: answer, kit: kit });
155
+ return { link, panel, overlay };
156
+ }
157
+ /** To the phone: the variant's call state, its field focused once it is drawn. */
158
+ export function toCall(kit) {
159
+ kit.setMode("call");
160
+ requestAnimationFrame(() => formOf(kit)?.querySelector('[data-lead-field="phone"]')?.focus());
161
+ }
162
+ /** A computer, as `isDesktop` says it — after hydration only. */
163
+ export function useDesktop() {
164
+ return useSyncExternalStore(noop, isDesktop, () => false);
165
+ }
166
+ /** The slot by the channel — the message, the bot, or the phone — and the channel's button under it. */
167
+ export function ModeBody(props) {
168
+ const { kit, message, action, labels } = props;
169
+ const words = wordsOf(kit);
170
+ const mode = kit.mode ?? "whatsapp";
171
+ return (_jsxs(_Fragment, { children: [_jsx(Slot, { kit: kit, children: mode === "call" ? kit.phone() : mode === "telegram" ? _jsx(BotCard, { kit: kit }) : (action.panel ?? _jsx(Preview, { kit: kit, preview: message.preview })) }), mode === "call" ? (kit.submit(labels?.call)) : (_jsx(MessengerCta, { kit: kit, action: action, channel: mode, label: labels?.[mode] ?? (mode === "whatsapp" ? words.messengerWhatsappCta : words.messengerTelegramCta) })), mode === "whatsapp" && _jsx(InappHint, { kit: kit, message: message.message })] }));
172
+ }
173
+ const iconOf = (kit, channel) => (channel === "call" ? (kit.icons?.phone ?? kit.icons?.callback) : kit.icons?.[channel]);
174
+ /** Whether the brand drew an icon for a channel: a square without one says its name instead. */
175
+ export const hasIcon = (kit, channel) => iconOf(kit, channel) !== undefined && iconOf(kit, channel) !== null;
176
+ /** A channel as a square beside the row's button: its icon, or — the brand drew none — its name; `label` says what it does. */
177
+ export function SquareButton(props) {
178
+ const { kit, channel } = props;
179
+ const icon = hasIcon(kit, channel);
180
+ return (_jsx(Button, { type: "button", variant: "outline", size: "touch", icon: icon, "aria-label": props.label, className: cn("shrink-0", kit.classNames?.messengerSquare), onClick: props.onClick, children: icon ? _jsx(Glyph, { kit: kit, channel: channel }) : labelOf(wordsOf(kit), channel) }));
181
+ }
182
+ /** The brand's icon for a channel, hidden from assistive technology: the label names it. */
183
+ export function Glyph({ kit, channel }) {
184
+ const icon = iconOf(kit, channel);
185
+ if (icon === undefined || icon === null)
186
+ return null;
187
+ return (_jsx("span", { "aria-hidden": "true", className: "flex shrink-0 items-center [&_svg]:size-5", children: icon }));
188
+ }
189
+ /** A messenger CTA: a real link dressed as the card's button, its in-app hint under it. */
190
+ /** A whole-pixel line under a brand's own type size, as the channel buttons keep one. */
191
+ const CTA_LEADING = "leading-6";
192
+ /** The part a messenger button wears: the main one, a secondary one, or a square. */
193
+ const partOf = (kit, kind) => kind === "cta" ? kit.classNames?.messengerCta : kind === "secondary" ? kit.classNames?.messengerSecondary : kit.classNames?.messengerSquare;
194
+ export function MessengerCta(props) {
195
+ const { kit, action, channel } = props;
196
+ const link = action.link(channel);
197
+ if (!link)
198
+ return null;
199
+ const { onClick, ...attrs } = link;
200
+ const square = props.square === true && hasIcon(kit, channel);
201
+ return (_jsx(Button, { asChild: true, variant: props.variant ?? "primary", size: "touch", icon: square, className: partWithLeading(cn(props.square ? "shrink-0" : "w-full", props.className), CTA_LEADING, partOf(kit, props.square ? "square" : props.variant === "outline" ? "secondary" : "cta")), children: _jsxs("a", { ...attrs, onClick: onClick, "aria-label": square ? props.label : undefined, "data-experiment": kit.experiment?.name, "data-variant": kit.experiment?.variant, "data-intent": channel === "telegram" ? "telegram" : undefined, children: [_jsx(Glyph, { kit: kit, channel: channel }), !square && props.label] }) }));
202
+ }
203
+ /** In an app's own browser: WhatsApp may not open — how to leave it, and the message to paste. */
204
+ export function InappHint({ kit, message }) {
205
+ const inApp = useInApp();
206
+ const [copied, setCopied] = useState(false);
207
+ if (!inApp || !kit.facts.whatsapp)
208
+ return null;
209
+ const words = wordsOf(kit);
210
+ return (_jsxs("div", { className: cn("flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-ink-soft", kit.classNames?.messengerHint), children: [_jsx("span", { children: words.messengerInappHint }), _jsx(Button, { type: "button", variant: "link", size: "touch", className: "px-1", onClick: () => void navigator.clipboard?.writeText(message).then(() => setCopied(true), () => undefined), children: copied ? words.messengerCopied : words.messengerCopy })] }));
211
+ }
212
+ /** The fixed box a variant swaps its states in; the brand gives it its height (`messengerSlot`). */
213
+ export function Slot({ kit, children }) {
214
+ return _jsx("div", { className: cn("flex flex-col justify-center", kit.classNames?.messengerSlot), children: children });
215
+ }
216
+ /** The message, ready: what goes into the chat, so the visitor knows there is nothing to write. */
217
+ export function Preview({ kit, preview, title, lede }) {
218
+ const words = wordsOf(kit);
219
+ return (_jsxs("div", { className: cn("flex items-start gap-3 rounded-[var(--control-radius)] bg-muted p-3", kit.classNames?.messengerPreview), children: [_jsx(Glyph, { kit: kit, channel: "whatsapp" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("p", { className: "text-sm font-medium text-ink", children: title ?? words.messengerPreviewTitle }), _jsx("p", { className: "line-clamp-2 text-sm text-ink-soft", children: lede ?? `« ${preview} »${words.messengerPreviewNote ? ` ${words.messengerPreviewNote}` : ""}` })] })] }));
220
+ }
221
+ /** What the bot does, in the slot. */
222
+ export function BotCard({ kit }) {
223
+ const words = wordsOf(kit);
224
+ return (_jsxs("div", { className: cn("flex items-start gap-3 rounded-[var(--control-radius)] bg-muted p-3", kit.classNames?.messengerPreview), children: [_jsx(Glyph, { kit: kit, channel: "telegram" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("p", { className: "text-sm font-medium text-ink", children: words.messengerBotTitle }), _jsx("p", { className: "line-clamp-2 text-sm text-ink-soft", children: words.messengerBotLede })] })] }));
225
+ }
226
+ /** «ou via Telegram»: the bot, as a link under the submit. */
227
+ export function TelegramLink({ kit, action }) {
228
+ const link = action.link("telegram");
229
+ if (!link)
230
+ return null;
231
+ const { onClick, ...attrs } = link;
232
+ return (_jsx(Button, { asChild: true, variant: "link", size: "touch", className: cn("self-center", kit.classNames?.messengerSecondary), children: _jsxs("a", { ...attrs, onClick: onClick, "data-intent": "telegram", "data-experiment": kit.experiment?.name, "data-variant": kit.experiment?.variant, children: [_jsx(Glyph, { kit: kit, channel: "telegram" }), wordsOf(kit).messengerViaTelegram] }) }));
233
+ }
234
+ /** The channels a variant offers, in the Figma order; Telegram only with a bot. */
235
+ export function modesOf(kit, order = ["whatsapp", "call", "telegram"]) {
236
+ return order.filter(m => (m === "whatsapp" ? kit.facts.whatsapp !== null : m === "telegram" ? kit.facts.telegram !== null : true));
237
+ }
238
+ export const labelOf = (words, mode) => mode === "whatsapp" ? words.messengerOptionWhatsapp : mode === "telegram" ? words.messengerOptionTelegram : words.messengerOptionCall;
239
+ export const noteOf = (words, mode) => mode === "whatsapp" ? words.messengerOptionWhatsappNote : mode === "telegram" ? words.messengerOptionTelegramNote : words.messengerOptionCallNote;
240
+ /**
241
+ * The kit's `Select` as a channel picker: an overlay, so the form under it
242
+ * does not move. The trigger draws its own label — `SelectValue` would print
243
+ * the raw value.
244
+ */
245
+ export function ChannelPicker(props) {
246
+ const { kit } = props;
247
+ const words = wordsOf(kit);
248
+ // Its own id: inside the phone's `Field` the trigger would claim the field's,
249
+ // and the label «Téléphone» would name the picker instead of the number.
250
+ const id = useId();
251
+ return (_jsxs(Select, { value: kit.mode ?? "whatsapp", onValueChange: v => kit.setMode(modesOf(kit).find(m => m === v) ?? "call"), children: [_jsx(SelectTrigger, { id: id, "aria-label": words.messengerChannelLabel, className: cn("border-0 shadow-none", props.triggerClassName, kit.classNames?.messengerTrigger), children: props.trigger }), _jsx(SelectContent, { children: modesOf(kit).map(mode => (_jsx(SelectItem, { value: mode, textValue: labelOf(words, mode), className: kit.classNames?.messengerOption, children: _jsxs("span", { className: "flex items-center gap-3", children: [_jsx(Glyph, { kit: kit, channel: mode }), _jsxs("span", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium text-ink", children: labelOf(words, mode) }), _jsx("span", { className: "text-xs text-ink-soft", children: noteOf(words, mode) })] })] }) }, mode))) })] }));
252
+ }
253
+ /** A channel as a card to tap (`saga`, `sheet`): its name, its line, the recommended badge on WhatsApp. */
254
+ export function OptionCard(props) {
255
+ const { kit, mode, link } = props;
256
+ const words = wordsOf(kit);
257
+ const className = cn("flex w-full items-center gap-3 rounded-[var(--control-radius)] border border-border p-3 text-left outline-none hover:bg-hover focus-visible:ring-[3px] focus-visible:ring-ring/50", props.recommended && "border-primary", kit.classNames?.messengerOption);
258
+ const body = (_jsxs(_Fragment, { children: [_jsx(Glyph, { kit: kit, channel: mode }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsxs("span", { className: "flex items-center gap-2 font-medium text-ink", children: [labelOf(words, mode), props.recommended && _jsx(Badge, { children: words.messengerRecommended })] }), _jsx("span", { className: "text-sm text-ink-soft", children: noteOf(words, mode) })] }), _jsx("span", { "aria-hidden": "true", className: "text-ink-soft", children: "\u203A" })] }));
259
+ if (link) {
260
+ const { onClick, ...attrs } = link;
261
+ return (_jsx("a", { ...attrs, onClick: onClick, className: className, "data-messenger-option": "", "data-intent": mode === "telegram" ? "telegram" : undefined, "data-experiment": kit.experiment?.name, "data-variant": kit.experiment?.variant, children: body }));
262
+ }
263
+ return (_jsx("button", { type: "button", onClick: props.onPick, className: className, "data-messenger-option": "", children: body }));
264
+ }
265
+ /** A QR code drawn from `qrcode-generator`, loaded on the first desktop tap only. */
266
+ function QrCode({ value, label, className }) {
267
+ const [code, setCode] = useState(null);
268
+ useEffect(() => {
269
+ let live = true;
270
+ void import("qrcode-generator").then(({ default: qrcode }) => {
271
+ const qr = qrcode(0, "L");
272
+ qr.addData(value);
273
+ qr.make();
274
+ const size = qr.getModuleCount();
275
+ let path = "";
276
+ for (let r = 0; r < size; r++)
277
+ for (let c = 0; c < size; c++)
278
+ if (qr.isDark(r, c))
279
+ path += `M${c} ${r}h1v1h-1z`;
280
+ if (live)
281
+ setCode({ size, path });
282
+ });
283
+ return () => {
284
+ live = false;
285
+ };
286
+ }, [value]);
287
+ return (_jsx("svg", { role: "img", "aria-label": label, viewBox: code ? `-2 -2 ${code.size + 4} ${code.size + 4}` : "0 0 1 1", shapeRendering: "crispEdges", className: cn("size-28 shrink-0 bg-card text-ink", className), children: code && _jsx("path", { d: code.path, fill: "currentColor" }) }));
288
+ }
289
+ export function QrPanel(props) {
290
+ const { href, words, kit } = props;
291
+ return (_jsxs("div", { className: cn("flex items-center gap-4 rounded-[var(--control-radius)] border border-border p-3", kit.classNames?.messengerQr), children: [_jsx(QrCode, { value: href, label: words.messengerQrAlt }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("p", { className: "font-medium text-ink", children: words.messengerQrTitle }), _jsx("p", { className: "text-sm text-ink-soft", children: words.messengerQrLede }), _jsx("a", { href: href, target: "_blank", rel: "noopener", className: "text-sm font-medium text-primary-ink underline-offset-4 hover:underline", children: words.messengerQrWeb }), props.onCallback && (_jsx(Button, { type: "button", variant: "link", size: "touch", className: cn("self-start px-0", kit.classNames?.messengerSecondary), onClick: props.onCallback, children: words.messengerCallback }))] })] }));
292
+ }
293
+ function ReturnScreen(props) {
294
+ const c = props.kit.classNames;
295
+ const { left, words } = props;
296
+ const title = useRef(null);
297
+ // The card under it is still there: the question takes the focus, and is read out.
298
+ useEffect(() => title.current?.focus(), []);
299
+ const name = left.channel === "whatsapp" ? words.messengerOptionWhatsapp : words.messengerOptionTelegram;
300
+ return (_jsxs("div", { role: "status", className: cn("absolute inset-0 z-10 flex flex-col justify-center gap-4 bg-card", c?.messengerReturn), children: [_jsx("p", { ref: title, tabIndex: -1, className: "font-display text-xl font-bold text-ink outline-none", children: words.messengerReturnTitle }), _jsx("p", { className: "text-ink-soft", children: fillText(words.messengerReturnBody, { channel: name, ref: left.ref ?? "" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "touch", className: partWithLeading("w-full", CTA_LEADING, c?.messengerSecondary), children: _jsx("a", { href: left.href, ...(left.channel === "telegram" ? { target: "_blank", rel: "noopener" } : {}), children: fillText(words.messengerReopen, { channel: name }) }) }), _jsx(Button, { type: "button", size: "touch", className: partWithLeading("w-full", CTA_LEADING, c?.messengerCta), onClick: () => props.onAnswer("failed"), children: words.messengerReturnFailed }), _jsx(Button, { type: "button", variant: "link", size: "touch", className: "self-center", onClick: () => props.onAnswer("sent"), children: words.messengerReturnDone })] }));
301
+ }
@@ -0,0 +1,8 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * VF-5: the lede becomes a chip naming where the answer comes — its menu an
4
+ * overlay over the form — and the slot follows it: the message, the bot, or the phone.
5
+ */
6
+ export default function Chip({ kit }: {
7
+ kit: MessengerKit;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "@evinvest/uikit";
3
+ import { ChannelPicker, Glyph, ModeBody, toCall, useMessage, useMessengerAction, wordsOf } from "./action.js";
4
+ const chipOf = (words, mode) => mode === "whatsapp" ? words.messengerChipWhatsapp : mode === "telegram" ? words.messengerChipTelegram : words.messengerChipCall;
5
+ /**
6
+ * VF-5: the lede becomes a chip naming where the answer comes — its menu an
7
+ * overlay over the form — and the slot follows it: the message, the bot, or the phone.
8
+ */
9
+ export default function Chip({ kit }) {
10
+ return kit.at === "afterHead" ? _jsx(ChipMenu, { kit: kit }) : _jsx(ChipBody, { kit: kit });
11
+ }
12
+ function ChipMenu({ kit }) {
13
+ const mode = kit.mode ?? "whatsapp";
14
+ return (_jsx(ChannelPicker, { kit: kit,
15
+ // The chip is the picker itself: the picker's part dresses it too.
16
+ triggerClassName: cn("h-8 w-fit rounded-full bg-muted px-3 text-sm font-medium text-ink", kit.classNames?.messengerPicker), trigger: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx(Glyph, { kit: kit, channel: mode }), chipOf(wordsOf(kit), mode)] }) }));
17
+ }
18
+ function ChipBody({ kit }) {
19
+ const message = useMessage(kit);
20
+ const action = useMessengerAction(kit, message, () => toCall(kit));
21
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", kit.classNames?.messenger), children: [_jsx(ModeBody, { kit: kit, message: message, action: action }), action.overlay] }));
22
+ }
@@ -0,0 +1,8 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * Any variant on a place without WhatsApp but with a bot: the control, and
4
+ * «ou via Telegram» under its submit.
5
+ */
6
+ export default function Fallback({ kit }: {
7
+ kit: MessengerKit;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TelegramLink, toCall, useMessage, useMessengerAction } from "./action.js";
3
+ /**
4
+ * Any variant on a place without WhatsApp but with a bot: the control, and
5
+ * «ou via Telegram» under its submit.
6
+ */
7
+ export default function Fallback({ kit }) {
8
+ const message = useMessage(kit);
9
+ const action = useMessengerAction(kit, message, () => toCall(kit));
10
+ return (_jsxs(_Fragment, { children: [_jsx(TelegramLink, { kit: kit, action: action }), action.overlay] }));
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * AQ-5: the channel first, on a screen of its own — WhatsApp (recommended),
4
+ * a call, the bot — then the job and the channel's slot, «Changer de canal»
5
+ * in the same place on every screen. The card's height is the brand's
6
+ * (`root`): the screens change what is in it, the button stays at the bottom.
7
+ */
8
+ export default function Saga({ kit }: {
9
+ kit: MessengerKit;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, cn } from "@evinvest/uikit";
4
+ import { ModeBody, modesOf, OptionCard, toCall, useMessage, useMessengerAction, wordsOf } from "./action.js";
5
+ const headOf = (words, mode) => mode === "whatsapp" ? [words.messengerWhatsappTitle, words.messengerWhatsappLede] : mode === "telegram" ? [words.messengerTelegramTitle, words.messengerTelegramLede] : [words.messengerCallTitle, words.messengerCallLede];
6
+ /**
7
+ * AQ-5: the channel first, on a screen of its own — WhatsApp (recommended),
8
+ * a call, the bot — then the job and the channel's slot, «Changer de canal»
9
+ * in the same place on every screen. The card's height is the brand's
10
+ * (`root`): the screens change what is in it, the button stays at the bottom.
11
+ */
12
+ export default function Saga({ kit }) {
13
+ if (kit.at === "head")
14
+ return _jsx(SagaHead, { kit: kit });
15
+ if (kit.at === "body")
16
+ return _jsx(SagaPick, { kit: kit });
17
+ return _jsx(SagaContact, { kit: kit });
18
+ }
19
+ function SagaHead({ kit }) {
20
+ const words = wordsOf(kit);
21
+ const [title, lede] = kit.mode === null ? [words.messengerSagaTitle, words.messengerSagaLede] : headOf(words, kit.mode);
22
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [kit.mode !== null && (_jsxs(Button, { type: "button", variant: "link", size: "sm", className: "self-start px-0", onClick: () => kit.setMode(null), children: ["\u2039 ", words.messengerChange] })), _jsx("p", { className: "font-display text-2xl font-bold text-ink", children: title }), _jsx("p", { className: "text-ink-soft", children: lede })] }));
23
+ }
24
+ function SagaPick({ kit }) {
25
+ const words = wordsOf(kit);
26
+ return (_jsxs("div", { className: cn("flex flex-1 flex-col gap-3", kit.classNames?.messenger), children: [modesOf(kit).map(mode => (_jsx(OptionCard, { kit: kit, mode: mode, recommended: mode === "whatsapp", onPick: () => (mode === "call" ? toCall(kit) : kit.setMode(mode)) }, mode))), _jsx("p", { className: "mt-auto text-center text-sm text-ink-soft", children: words.messengerSagaFoot })] }));
27
+ }
28
+ function SagaContact({ kit }) {
29
+ const words = wordsOf(kit);
30
+ const message = useMessage(kit);
31
+ const action = useMessengerAction(kit, message, () => toCall(kit));
32
+ return (_jsxs("div", { className: cn("flex flex-1 flex-col gap-3", kit.classNames?.messenger), children: [_jsx(ModeBody, { kit: kit, message: message, action: action, labels: { whatsapp: words.messengerOpenWhatsapp } }), action.overlay] }));
33
+ }
@@ -0,0 +1,8 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * AQ-2: «WhatsApp | Appel» over a slot of one height — the message ready to
4
+ * send, or the phone — and the bot as a link under the button.
5
+ */
6
+ export default function Segment({ kit }: {
7
+ kit: MessengerKit;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn, ToggleGroup, ToggleGroupItem } from "@evinvest/uikit";
4
+ import { Glyph, labelOf, ModeBody, TelegramLink, toCall, useMessage, useMessengerAction, wordsOf } from "./action.js";
5
+ /**
6
+ * AQ-2: «WhatsApp | Appel» over a slot of one height — the message ready to
7
+ * send, or the phone — and the bot as a link under the button.
8
+ */
9
+ export default function Segment({ kit }) {
10
+ const words = wordsOf(kit);
11
+ const message = useMessage(kit);
12
+ const action = useMessengerAction(kit, message, () => toCall(kit));
13
+ const mode = kit.mode === "call" ? "call" : "whatsapp";
14
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", kit.classNames?.messenger), children: [_jsx(ToggleGroup, { role: "group", type: "single", value: mode, onValueChange: v => (v === "call" || v === "whatsapp" ? kit.setMode(v) : undefined), "aria-label": words.messengerChannelLabel, className: cn("w-full gap-1 rounded-[var(--control-radius)] bg-muted p-1", kit.classNames?.messengerPicker), children: ["whatsapp", "call"].map(m => (_jsxs(ToggleGroupItem, { value: m, className: cn("h-10 flex-1 gap-2 rounded-[var(--control-radius)] data-[state=on]:bg-card data-[state=on]:text-ink", kit.classNames?.messengerOption, kit.classNames?.messengerSegment), children: [_jsx(Glyph, { kit: kit, channel: m }), labelOf(words, m)] }, m))) }), _jsx(ModeBody, { kit: { ...kit, mode }, message: message, action: action }), _jsx(TelegramLink, { kit: kit, action: action }), action.overlay] }));
15
+ }
@@ -0,0 +1,10 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * AQ-1 / VF-1: the channel picked in the phone field itself — before the
4
+ * number (`prefix`) or after it (`suffix`). WhatsApp: the number optional,
5
+ * the button opens the chat. Appel: the number required, the control's
6
+ * submit. Telegram: no number, the button opens the bot.
7
+ */
8
+ export default function SelectVariant({ kit }: {
9
+ kit: MessengerKit;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn, InputGroup, InputGroupAddon } from "@evinvest/uikit";
3
+ import { ChannelPicker, Glyph, InappHint, labelOf, MessengerCta, toCall, useMessage, useMessengerAction, wordsOf } from "./action.js";
4
+ /**
5
+ * AQ-1 / VF-1: the channel picked in the phone field itself — before the
6
+ * number (`prefix`) or after it (`suffix`). WhatsApp: the number optional,
7
+ * the button opens the chat. Appel: the number required, the control's
8
+ * submit. Telegram: no number, the button opens the bot.
9
+ */
10
+ export default function SelectVariant({ kit }) {
11
+ const words = wordsOf(kit);
12
+ const message = useMessage(kit);
13
+ const action = useMessengerAction(kit, message, () => toCall(kit));
14
+ const mode = kit.mode ?? "whatsapp";
15
+ const side = kit.variant.kind === "select" && kit.variant.side === "suffix" ? "end" : "start";
16
+ const picker = (_jsx(ChannelPicker, { kit: kit, triggerClassName: cn("h-full rounded-none bg-transparent px-3", side === "start" ? "border-r border-input" : "border-l border-input"), trigger: _jsxs("span", { className: "flex items-center gap-2 font-medium text-ink", children: [_jsx(Glyph, { kit: kit, channel: mode }), labelOf(words, mode)] }) }));
17
+ const hint = mode === "call" ? undefined : _jsx("p", { className: cn("text-sm text-ink-soft", kit.classNames?.messengerHint), children: mode === "whatsapp" ? words.messengerWhatsappHint : words.messengerTelegramHint });
18
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", kit.classNames?.messenger), children: [kit.phone({
19
+ optional: mode !== "call",
20
+ disabled: mode === "telegram",
21
+ ...(mode !== "call" ? { placeholder: words.messengerPhoneOptional } : {}),
22
+ frame: (input) => (_jsxs(InputGroup, { className: cn("h-12 rounded-[var(--control-radius)]", kit.classNames?.messengerPicker), children: [side === "start" && _jsx(InputGroupAddon, { align: "inline-start", children: picker }), input, side === "end" && _jsx(InputGroupAddon, { align: "inline-end", children: picker })] })),
23
+ ...(hint ? { after: hint } : {}),
24
+ }), action.panel ??
25
+ (mode === "call" ? (kit.submit()) : (_jsx(MessengerCta, { kit: kit, action: action, channel: mode, label: mode === "whatsapp" ? words.messengerWhatsappCta : words.messengerTelegramCta }))), mode === "whatsapp" && _jsx(InappHint, { kit: kit, message: message.message }), action.overlay] }));
26
+ }
@@ -0,0 +1,10 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * VF-4: the card is the estimate, the postcode and one button; the button
4
+ * opens the kit's drawer — «Où recevoir votre devis ?»: WhatsApp
5
+ * (recommended), the bot, a call — and a call asks the phone in the drawer,
6
+ * a field of the card's form wherever the drawer is drawn (`form`).
7
+ */
8
+ export default function Sheet({ kit }: {
9
+ kit: MessengerKit;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, cn, Drawer, DrawerContent, DrawerTitle, DrawerTrigger } from "@evinvest/uikit";
4
+ import { useEffect, useId, useRef, useState } from "react";
5
+ import { InappHint, modesOf, OptionCard, useMessage, useMessengerAction, wordsOf } from "./action.js";
6
+ /**
7
+ * VF-4: the card is the estimate, the postcode and one button; the button
8
+ * opens the kit's drawer — «Où recevoir votre devis ?»: WhatsApp
9
+ * (recommended), the bot, a call — and a call asks the phone in the drawer,
10
+ * a field of the card's form wherever the drawer is drawn (`form`).
11
+ */
12
+ export default function Sheet({ kit }) {
13
+ const words = wordsOf(kit);
14
+ const message = useMessage(kit);
15
+ const [open, setOpen] = useState(false);
16
+ const [step, setStep] = useState("pick");
17
+ const action = useMessengerAction(kit, message, () => {
18
+ setStep("call");
19
+ setOpen(true);
20
+ });
21
+ const formId = `${kit.id}-form`;
22
+ // The dialog is named by its heading, on either step: uikit's drawer does not wire it.
23
+ const titleId = useId();
24
+ // A step swaps the drawer's content, the pressed button with it: the focus
25
+ // would fall to the page, and Escape with it. The call's step gives it to
26
+ // the phone, the way back to the first channel — on whatever path led there.
27
+ const shown = useRef(step);
28
+ useEffect(() => {
29
+ const was = shown.current;
30
+ shown.current = step;
31
+ if (!open || (step === "pick" && was === "pick"))
32
+ return;
33
+ // After the drawer's own focus scope, which takes the focus as it opens.
34
+ const frame = requestAnimationFrame(() => {
35
+ const dialog = document.getElementById(titleId)?.closest("[role=dialog]");
36
+ const target = step === "call"
37
+ ? document.querySelector(`[data-lead-field="phone"][form="${CSS.escape(formId)}"]`)
38
+ : dialog?.querySelector("[data-messenger-option]");
39
+ target?.focus();
40
+ });
41
+ return () => cancelAnimationFrame(frame);
42
+ }, [open, step, formId, titleId]);
43
+ // The app takes over: the drawer goes with it — unless the tap drew the QR code in it.
44
+ const closing = (link) => link && {
45
+ ...link,
46
+ onClick: (event) => {
47
+ link.onClick(event);
48
+ if (!event.defaultPrevented)
49
+ setOpen(false);
50
+ },
51
+ };
52
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", kit.classNames?.messenger), children: [_jsxs(Drawer, { open: open, onOpenChange: next => {
53
+ setOpen(next);
54
+ if (!next)
55
+ setStep("pick");
56
+ }, children: [_jsx(DrawerTrigger, { asChild: true, children: _jsx(Button, { type: "button", size: "touch", className: cn("w-full", kit.classNames?.messengerCta), children: words.messengerSheetCta }) }), _jsx(DrawerContent, { "aria-labelledby": titleId, children: _jsx("div", { className: cn("flex flex-col gap-4 p-4", kit.classNames?.messengerPicker), children: step === "pick" ? (_jsxs(_Fragment, { children: [_jsx(DrawerTitle, { id: titleId, className: "font-display text-xl font-bold text-ink", children: words.messengerSheetTitle }), action.panel, modesOf(kit, ["whatsapp", "telegram", "call"]).map(mode => mode === "call" ? (_jsx(OptionCard, { kit: kit, mode: mode, onPick: () => setStep("call") }, mode)) : (_jsx(OptionCard, { kit: kit, mode: mode, link: closing(action.link(mode)), recommended: mode === "whatsapp" }, mode))), _jsx(InappHint, { kit: kit, message: message.message })] })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { type: "button", variant: "link", size: "sm", className: "self-start px-0", onClick: () => setStep("pick"), children: ["\u2039 ", words.messengerChange] }), _jsx(DrawerTitle, { id: titleId, className: "font-display text-xl font-bold text-ink", children: words.messengerCallTitle }), kit.phone({ form: formId }), kit.submit(words.messengerCallback, { form: formId })] })) }) })] }), action.overlay] }));
57
+ }
@@ -0,0 +1,9 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * VF-6: the slot — the message, or the phone — over one row: «Devis sur
4
+ * WhatsApp» with the bot and a call as squares beside it; the call's square
5
+ * turns the row into «Être rappelé» with WhatsApp and the bot as squares.
6
+ */
7
+ export default function Split({ kit }: {
8
+ kit: MessengerKit;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@evinvest/uikit";
4
+ import { InappHint, MessengerCta, Preview, Slot, SquareButton, toCall, useMessage, useMessengerAction, wordsOf } from "./action.js";
5
+ /**
6
+ * VF-6: the slot — the message, or the phone — over one row: «Devis sur
7
+ * WhatsApp» with the bot and a call as squares beside it; the call's square
8
+ * turns the row into «Être rappelé» with WhatsApp and the bot as squares.
9
+ */
10
+ export default function Split({ kit }) {
11
+ const words = wordsOf(kit);
12
+ const message = useMessage(kit);
13
+ const action = useMessengerAction(kit, message, () => toCall(kit));
14
+ const call = kit.mode === "call";
15
+ const telegram = kit.facts.telegram && _jsx(MessengerCta, { kit: kit, action: action, channel: "telegram", label: words.messengerOptionTelegram, variant: "outline", square: true });
16
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", kit.classNames?.messenger), children: [_jsx(Slot, { kit: kit, children: call ? kit.phone() : (action.panel ?? _jsx(Preview, { kit: kit, preview: message.preview })) }), _jsx("div", { className: "flex gap-2", children: call ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "min-w-0 flex-1", children: kit.submit(words.messengerCallback) }), _jsx(SquareButton, { kit: kit, channel: "whatsapp", label: words.messengerBackToWhatsapp, onClick: () => kit.setMode("whatsapp") }), telegram] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(MessengerCta, { kit: kit, action: action, channel: "whatsapp", label: words.messengerSplitCta }) }), telegram, _jsx(SquareButton, { kit: kit, channel: "call", label: words.messengerCallback, onClick: () => toCall(kit) })] })) }), !call && _jsx(InappHint, { kit: kit, message: message.message }), action.overlay] }));
17
+ }
@@ -0,0 +1,10 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * AQ-4 / VF-3: no phone until it is asked for. Slot A is the WhatsApp button,
4
+ * row B the bot and «Être rappelé»; «Être rappelé» swaps them — A becomes the
5
+ * phone, B a square back to WhatsApp beside the call's submit — so the card
6
+ * keeps its height. On a computer, the QR code takes slot A.
7
+ */
8
+ export default function Swap({ kit }: {
9
+ kit: MessengerKit;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, cn } from "@evinvest/uikit";
4
+ import { Glyph, InappHint, MessengerCta, Slot, SquareButton, toCall, useDesktop, useMessage, useMessengerAction, wordsOf } from "./action.js";
5
+ /**
6
+ * AQ-4 / VF-3: no phone until it is asked for. Slot A is the WhatsApp button,
7
+ * row B the bot and «Être rappelé»; «Être rappelé» swaps them — A becomes the
8
+ * phone, B a square back to WhatsApp beside the call's submit — so the card
9
+ * keeps its height. On a computer, the QR code takes slot A.
10
+ */
11
+ export default function Swap({ kit }) {
12
+ const words = wordsOf(kit);
13
+ const message = useMessage(kit);
14
+ const action = useMessengerAction(kit, message, () => toCall(kit));
15
+ const call = kit.mode === "call";
16
+ const desktop = useDesktop();
17
+ return (_jsxs("div", { className: cn("flex flex-col gap-3", kit.classNames?.messenger), children: [_jsx(Slot, { kit: kit, children: call ? kit.phone({ after: null }) : (action.panel ?? _jsx(MessengerCta, { kit: kit, action: action, channel: "whatsapp", label: words.messengerWhatsappCta })) }), _jsx("div", { className: "flex gap-3", children: call ? (_jsxs(_Fragment, { children: [_jsx(SquareButton, { kit: kit, channel: "whatsapp", label: words.messengerBackToWhatsapp, onClick: () => kit.setMode("whatsapp") }), _jsx("div", { className: "min-w-0 flex-1", children: kit.submit() })] })) : (_jsxs(_Fragment, { children: [kit.facts.telegram && (_jsx("div", { className: "min-w-0 flex-1", children: _jsx(MessengerCta, { kit: kit, action: action, channel: "telegram", label: desktop ? words.messengerTelegramCta : words.messengerOptionTelegram, variant: "outline" }) })), _jsxs(Button, { type: "button", variant: "outline", size: "touch", className: cn("min-w-0 flex-1", kit.classNames?.messengerSecondary), onClick: () => toCall(kit), children: [_jsx(Glyph, { kit: kit, channel: "call" }), words.messengerCallback] })] })) }), !call && _jsx(InappHint, { kit: kit, message: message.message }), action.overlay] }));
18
+ }
@@ -0,0 +1,10 @@
1
+ import type { MessengerKit } from "./types.js";
2
+ /**
3
+ * AQ-3: the form is the control's; its success adds the faster way — a photo
4
+ * on WhatsApp, the message carrying the lead's own reference — and the bot.
5
+ * On a computer, WhatsApp is a QR code to scan with the phone that takes the
6
+ * photo. The lead is sent already: a tap here posts nothing.
7
+ */
8
+ export default function Thanks({ kit }: {
9
+ kit: MessengerKit;
10
+ }): import("react").JSX.Element;