@ollaid/native-sso 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4493 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
+ import { createContext, forwardRef, useEffect, useContext, useRef, useState, useCallback } from "react";
6
+ const iconProps = { width: "100%", height: "100%", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
7
+ function IconShieldCheck(props) {
8
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
9
+ /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }),
10
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
11
+ ] });
12
+ }
13
+ function IconMail(props) {
14
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
15
+ /* @__PURE__ */ jsx("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }),
16
+ /* @__PURE__ */ jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
17
+ ] });
18
+ }
19
+ function IconPhone(props) {
20
+ return /* @__PURE__ */ jsx("svg", { ...iconProps, ...props, children: /* @__PURE__ */ jsx("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" }) });
21
+ }
22
+ function IconLock(props) {
23
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
24
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }),
25
+ /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
26
+ ] });
27
+ }
28
+ function IconKeyRound(props) {
29
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
30
+ /* @__PURE__ */ jsx("path", { d: "M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" }),
31
+ /* @__PURE__ */ jsx("circle", { cx: "16.5", cy: "7.5", r: ".5", fill: "currentColor" })
32
+ ] });
33
+ }
34
+ function IconArrowLeft(props) {
35
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
36
+ /* @__PURE__ */ jsx("path", { d: "m12 19-7-7 7-7" }),
37
+ /* @__PURE__ */ jsx("path", { d: "M19 12H5" })
38
+ ] });
39
+ }
40
+ function IconEye(props) {
41
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
42
+ /* @__PURE__ */ jsx("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
43
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
44
+ ] });
45
+ }
46
+ function IconEyeOff(props) {
47
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
48
+ /* @__PURE__ */ jsx("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
49
+ /* @__PURE__ */ jsx("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
50
+ /* @__PURE__ */ jsx("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
51
+ /* @__PURE__ */ jsx("path", { d: "m2 2 20 20" })
52
+ ] });
53
+ }
54
+ function IconCheckCircle2(props) {
55
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
56
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
57
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
58
+ ] });
59
+ }
60
+ function IconLoader2(props) {
61
+ return /* @__PURE__ */ jsx("svg", { ...iconProps, ...props, style: { animation: "spin 1s linear infinite", ...props.style || {} }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
62
+ }
63
+ function IconSmartphone(props) {
64
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
65
+ /* @__PURE__ */ jsx("rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2" }),
66
+ /* @__PURE__ */ jsx("path", { d: "M12 18h.01" })
67
+ ] });
68
+ }
69
+ function IconHome(props) {
70
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
71
+ /* @__PURE__ */ jsx("path", { d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" }),
72
+ /* @__PURE__ */ jsx("path", { d: "M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" })
73
+ ] });
74
+ }
75
+ function IconCalendar(props) {
76
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
77
+ /* @__PURE__ */ jsx("path", { d: "M8 2v4" }),
78
+ /* @__PURE__ */ jsx("path", { d: "M16 2v4" }),
79
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }),
80
+ /* @__PURE__ */ jsx("path", { d: "M3 10h18" })
81
+ ] });
82
+ }
83
+ function IconBell(props) {
84
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
85
+ /* @__PURE__ */ jsx("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }),
86
+ /* @__PURE__ */ jsx("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })
87
+ ] });
88
+ }
89
+ function IconMessageCircle(props) {
90
+ return /* @__PURE__ */ jsx("svg", { ...iconProps, ...props, children: /* @__PURE__ */ jsx("path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22z" }) });
91
+ }
92
+ function IconUsers(props) {
93
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
94
+ /* @__PURE__ */ jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
95
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "7", r: "4" }),
96
+ /* @__PURE__ */ jsx("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
97
+ /* @__PURE__ */ jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
98
+ ] });
99
+ }
100
+ function IconSettings(props) {
101
+ return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
102
+ /* @__PURE__ */ jsx("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }),
103
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
104
+ ] });
105
+ }
106
+ const DialogContext = createContext({ open: false, onOpenChange: () => {
107
+ } });
108
+ function Dialog({ open, onOpenChange, children }) {
109
+ useEffect(() => {
110
+ if (open) {
111
+ document.body.style.overflow = "hidden";
112
+ } else {
113
+ document.body.style.overflow = "";
114
+ }
115
+ return () => {
116
+ document.body.style.overflow = "";
117
+ };
118
+ }, [open]);
119
+ if (!open) return null;
120
+ return /* @__PURE__ */ jsx(DialogContext.Provider, { value: { open, onOpenChange }, children });
121
+ }
122
+ function DialogContent({ children, className = "" }) {
123
+ const { onOpenChange } = useContext(DialogContext);
124
+ return /* @__PURE__ */ jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 50, display: "flex", alignItems: "center", justifyContent: "center" }, children: [
125
+ /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ style: { position: "fixed", inset: 0, backgroundColor: "rgba(0,0,0,0.5)" },
129
+ onClick: () => onOpenChange(false)
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsxs(
133
+ "div",
134
+ {
135
+ className,
136
+ style: {
137
+ position: "relative",
138
+ zIndex: 51,
139
+ width: "100%",
140
+ maxWidth: "28rem",
141
+ margin: "1rem",
142
+ padding: "1.5rem",
143
+ borderRadius: "0.75rem",
144
+ backgroundColor: "white",
145
+ boxShadow: "0 25px 50px -12px rgba(0,0,0,0.25)",
146
+ maxHeight: "90vh",
147
+ overflowY: "auto"
148
+ },
149
+ children: [
150
+ /* @__PURE__ */ jsx(
151
+ "button",
152
+ {
153
+ onClick: () => onOpenChange(false),
154
+ style: { position: "absolute", right: "1rem", top: "1rem", background: "none", border: "none", cursor: "pointer", fontSize: "1.25rem", color: "#9ca3af", lineHeight: 1 },
155
+ "aria-label": "Close",
156
+ children: "✕"
157
+ }
158
+ ),
159
+ children
160
+ ]
161
+ }
162
+ )
163
+ ] });
164
+ }
165
+ function DialogHeader({ children, className = "", style }) {
166
+ return /* @__PURE__ */ jsx("div", { className, style: { marginBottom: "0.5rem", ...style }, children });
167
+ }
168
+ function DialogTitle({ children, className = "" }) {
169
+ return /* @__PURE__ */ jsx("h2", { className, style: { fontSize: "1.25rem", fontWeight: 600, color: "#111827" }, children });
170
+ }
171
+ function DialogDescription({ children, className = "" }) {
172
+ return /* @__PURE__ */ jsx("p", { className, style: { fontSize: "0.875rem", color: "#6b7280", marginTop: "0.25rem" }, children });
173
+ }
174
+ const Button = forwardRef(({
175
+ variant = "default",
176
+ size = "default",
177
+ className = "",
178
+ style,
179
+ disabled,
180
+ children,
181
+ ...props
182
+ }, ref) => {
183
+ const baseStyle = {
184
+ display: "inline-flex",
185
+ alignItems: "center",
186
+ justifyContent: "center",
187
+ borderRadius: "0.5rem",
188
+ fontWeight: 500,
189
+ cursor: disabled ? "not-allowed" : "pointer",
190
+ transition: "all 0.15s",
191
+ border: "none",
192
+ opacity: disabled ? 0.5 : 1,
193
+ fontSize: size === "sm" ? "0.875rem" : "0.9375rem",
194
+ padding: size === "sm" ? "0.375rem 0.75rem" : size === "lg" ? "0.75rem 1.5rem" : "0.625rem 1rem",
195
+ height: size === "sm" ? "2rem" : size === "lg" ? "3rem" : "2.5rem",
196
+ ...variant === "default" ? { backgroundColor: "#002147", color: "white" } : {},
197
+ ...variant === "outline" ? { backgroundColor: "transparent", border: "1px solid #d1d5db", color: "#374151" } : {},
198
+ ...variant === "ghost" ? { backgroundColor: "transparent", color: "#374151" } : {},
199
+ ...style
200
+ };
201
+ return /* @__PURE__ */ jsx("button", { ref, className, style: baseStyle, disabled, ...props, children });
202
+ });
203
+ Button.displayName = "Button";
204
+ const Input = forwardRef(
205
+ ({ className = "", style, ...props }, ref) => {
206
+ return /* @__PURE__ */ jsx(
207
+ "input",
208
+ {
209
+ ref,
210
+ className,
211
+ style: {
212
+ width: "100%",
213
+ padding: "0.5rem 0.75rem",
214
+ borderRadius: "0.375rem",
215
+ border: "1px solid #d1d5db",
216
+ fontSize: "0.9375rem",
217
+ outline: "none",
218
+ transition: "border-color 0.15s",
219
+ backgroundColor: "#f9fafb",
220
+ color: "#111827",
221
+ ...style
222
+ },
223
+ ...props
224
+ }
225
+ );
226
+ }
227
+ );
228
+ Input.displayName = "Input";
229
+ const Label = forwardRef(
230
+ ({ className = "", style, ...props }, ref) => {
231
+ return /* @__PURE__ */ jsx(
232
+ "label",
233
+ {
234
+ ref,
235
+ className,
236
+ style: { fontSize: "0.875rem", fontWeight: 500, color: "#374151", ...style },
237
+ ...props
238
+ }
239
+ );
240
+ }
241
+ );
242
+ Label.displayName = "Label";
243
+ const RadioGroupContext = createContext({ value: "", onValueChange: () => {
244
+ } });
245
+ function RadioGroup({ value, onValueChange, children, className = "" }) {
246
+ return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: { value, onValueChange }, children: /* @__PURE__ */ jsx("div", { className, children }) });
247
+ }
248
+ function RadioGroupItem({ value, id }) {
249
+ const { value: groupValue, onValueChange } = useContext(RadioGroupContext);
250
+ return /* @__PURE__ */ jsx(
251
+ "input",
252
+ {
253
+ type: "radio",
254
+ id,
255
+ checked: groupValue === value,
256
+ onChange: () => onValueChange(value),
257
+ style: { width: "1rem", height: "1rem", accentColor: "#002147" }
258
+ }
259
+ );
260
+ }
261
+ if (typeof document !== "undefined") {
262
+ const styleId = "ollaid-sso-keyframes";
263
+ if (!document.getElementById(styleId)) {
264
+ const style = document.createElement("style");
265
+ style.id = styleId;
266
+ style.textContent = `@keyframes spin { to { transform: rotate(360deg); } }`;
267
+ document.head.appendChild(style);
268
+ }
269
+ }
270
+ function OTPInput({
271
+ length = 6,
272
+ value,
273
+ onChange,
274
+ onComplete,
275
+ disabled = false,
276
+ autoFocus = true
277
+ }) {
278
+ const inputRefs = useRef([]);
279
+ const [activeIndex, setActiveIndex] = useState(0);
280
+ useEffect(() => {
281
+ inputRefs.current = inputRefs.current.slice(0, length);
282
+ }, [length]);
283
+ useEffect(() => {
284
+ if (autoFocus && inputRefs.current[0]) inputRefs.current[0].focus();
285
+ }, [autoFocus]);
286
+ useEffect(() => {
287
+ if (value.length === length && onComplete) onComplete(value);
288
+ }, [value, length, onComplete]);
289
+ const handleChange = (index, char) => {
290
+ var _a;
291
+ if (disabled) return;
292
+ const digit = char.replace(/[^0-9]/g, "");
293
+ if (!digit) return;
294
+ const newValue = value.split("");
295
+ newValue[index] = digit;
296
+ onChange(newValue.join("").slice(0, length));
297
+ if (index < length - 1) {
298
+ (_a = inputRefs.current[index + 1]) == null ? void 0 : _a.focus();
299
+ setActiveIndex(index + 1);
300
+ }
301
+ };
302
+ const handleKeyDown = (index, e) => {
303
+ var _a, _b, _c;
304
+ if (disabled) return;
305
+ if (e.key === "Backspace") {
306
+ e.preventDefault();
307
+ const newValue = value.split("");
308
+ if (value[index]) {
309
+ newValue[index] = "";
310
+ onChange(newValue.join(""));
311
+ } else if (index > 0) {
312
+ newValue[index - 1] = "";
313
+ onChange(newValue.join(""));
314
+ (_a = inputRefs.current[index - 1]) == null ? void 0 : _a.focus();
315
+ setActiveIndex(index - 1);
316
+ }
317
+ } else if (e.key === "ArrowLeft" && index > 0) {
318
+ (_b = inputRefs.current[index - 1]) == null ? void 0 : _b.focus();
319
+ setActiveIndex(index - 1);
320
+ } else if (e.key === "ArrowRight" && index < length - 1) {
321
+ (_c = inputRefs.current[index + 1]) == null ? void 0 : _c.focus();
322
+ setActiveIndex(index + 1);
323
+ }
324
+ };
325
+ const handlePaste = (e) => {
326
+ var _a;
327
+ if (disabled) return;
328
+ e.preventDefault();
329
+ const pasted = e.clipboardData.getData("text").replace(/[^0-9]/g, "");
330
+ if (pasted) {
331
+ const newValue = pasted.slice(0, length);
332
+ onChange(newValue);
333
+ const focusIndex = Math.min(newValue.length, length - 1);
334
+ (_a = inputRefs.current[focusIndex]) == null ? void 0 : _a.focus();
335
+ setActiveIndex(focusIndex);
336
+ }
337
+ };
338
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0.5rem" }, children: Array.from({ length }).map((_, index) => /* @__PURE__ */ jsx(
339
+ "input",
340
+ {
341
+ ref: (el) => inputRefs.current[index] = el,
342
+ type: "text",
343
+ inputMode: "numeric",
344
+ maxLength: 1,
345
+ value: value[index] || "",
346
+ onChange: (e) => handleChange(index, e.target.value),
347
+ onKeyDown: (e) => handleKeyDown(index, e),
348
+ onPaste: handlePaste,
349
+ onFocus: () => setActiveIndex(index),
350
+ disabled,
351
+ "aria-label": `Digit ${index + 1}`,
352
+ style: {
353
+ width: "2.5rem",
354
+ height: "3rem",
355
+ textAlign: "center",
356
+ fontSize: "1.25rem",
357
+ fontWeight: 600,
358
+ borderRadius: "0.375rem",
359
+ border: "2px solid",
360
+ borderColor: activeIndex === index && !disabled ? "hsl(var(--primary, 222 47% 11%))" : value[index] ? "hsl(var(--primary, 222 47% 11%) / 0.5)" : "#d1d5db",
361
+ backgroundColor: value[index] ? "hsl(var(--primary, 222 47% 11%) / 0.05)" : "white",
362
+ outline: "none",
363
+ transition: "all 0.2s",
364
+ opacity: disabled ? 0.5 : 1,
365
+ cursor: disabled ? "not-allowed" : "text"
366
+ }
367
+ },
368
+ index
369
+ )) });
370
+ }
371
+ const MAX_LOGS = 50;
372
+ let apiLogs = [];
373
+ let listeners = [];
374
+ function notifyListeners() {
375
+ listeners.forEach((fn) => fn([...apiLogs]));
376
+ }
377
+ function logApiCall(method, endpoint, requestBody, requestHeaders) {
378
+ const id = `${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
379
+ const log = {
380
+ id,
381
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
382
+ method,
383
+ endpoint,
384
+ requestBody,
385
+ requestHeaders,
386
+ responseStatus: null,
387
+ responseBody: null,
388
+ duration: 0
389
+ };
390
+ apiLogs.unshift(log);
391
+ if (apiLogs.length > MAX_LOGS) {
392
+ apiLogs = apiLogs.slice(0, MAX_LOGS);
393
+ }
394
+ notifyListeners();
395
+ return id;
396
+ }
397
+ function logApiResponse(logId, responseStatus, responseBody, startTime, error) {
398
+ const log = apiLogs.find((l) => l.id === logId);
399
+ if (log) {
400
+ log.responseStatus = responseStatus;
401
+ log.responseBody = responseBody;
402
+ log.duration = Date.now() - startTime;
403
+ log.error = error;
404
+ notifyListeners();
405
+ }
406
+ }
407
+ function clearApiLogs() {
408
+ apiLogs = [];
409
+ notifyListeners();
410
+ }
411
+ function subscribeToLogs(callback) {
412
+ listeners.push(callback);
413
+ return () => {
414
+ listeners = listeners.filter((fn) => fn !== callback);
415
+ };
416
+ }
417
+ function exportLogsAsText() {
418
+ if (apiLogs.length === 0) return "Aucun appel API enregistré.";
419
+ const lines = [
420
+ "=".repeat(60),
421
+ "NATIVE SSO DEBUG LOGS",
422
+ `Exporté le: ${(/* @__PURE__ */ new Date()).toISOString()}`,
423
+ "=".repeat(60),
424
+ ""
425
+ ];
426
+ apiLogs.slice().reverse().forEach((log, index) => {
427
+ const emoji = log.responseStatus === null ? "⏳" : log.responseStatus >= 200 && log.responseStatus < 300 ? "✅" : "❌";
428
+ lines.push(`[${index + 1}] ${emoji} ${log.method} ${log.endpoint}`);
429
+ lines.push(` Status: ${log.responseStatus ?? "En cours..."} | Durée: ${log.duration}ms`);
430
+ if (log.requestBody) lines.push(` Request: ${JSON.stringify(log.requestBody)}`);
431
+ if (log.responseBody) lines.push(` Response: ${JSON.stringify(log.responseBody)}`);
432
+ if (log.error) lines.push(` ⚠️ Error: ${log.error}`);
433
+ lines.push("");
434
+ });
435
+ return lines.join("\n");
436
+ }
437
+ class ApiError extends Error {
438
+ constructor(message, type, statusCode, errorType, response) {
439
+ super(message);
440
+ __publicField(this, "type");
441
+ __publicField(this, "statusCode");
442
+ __publicField(this, "errorType");
443
+ __publicField(this, "response");
444
+ this.name = "ApiError";
445
+ this.type = type;
446
+ this.statusCode = statusCode;
447
+ this.errorType = errorType;
448
+ this.response = response;
449
+ }
450
+ }
451
+ let config = {
452
+ saasApiUrl: "",
453
+ iamApiUrl: "",
454
+ timeout: 3e4,
455
+ debug: false
456
+ };
457
+ const setNativeAuthConfig = (newConfig) => {
458
+ config = { ...config, ...newConfig };
459
+ if (config.debug) {
460
+ console.log("🔐 @ollaid/native-sso Config:", {
461
+ saasApiUrl: config.saasApiUrl,
462
+ iamApiUrl: config.iamApiUrl
463
+ });
464
+ }
465
+ };
466
+ const getNativeAuthConfig = () => ({ ...config });
467
+ const isDebugMode = () => config.debug === true;
468
+ const DEVICE_ID_KEY = "native_sso_device_id";
469
+ const getDeviceId = () => {
470
+ if (typeof localStorage === "undefined") return "";
471
+ let deviceId = localStorage.getItem(DEVICE_ID_KEY);
472
+ if (!deviceId) {
473
+ deviceId = `nat_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
474
+ localStorage.setItem(DEVICE_ID_KEY, deviceId);
475
+ }
476
+ return deviceId;
477
+ };
478
+ const STORAGE = {
479
+ AUTH_TOKEN: "auth_token",
480
+ TOKEN: "token",
481
+ USER: "user",
482
+ ACCOUNT_TYPE: "account_type"
483
+ };
484
+ const setAuthToken = (token) => {
485
+ if (typeof localStorage !== "undefined") {
486
+ localStorage.setItem(STORAGE.AUTH_TOKEN, token);
487
+ localStorage.setItem(STORAGE.TOKEN, token);
488
+ }
489
+ };
490
+ const getAuthToken = () => {
491
+ if (typeof localStorage === "undefined") return null;
492
+ return localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
493
+ };
494
+ const clearAuthToken = () => {
495
+ if (typeof localStorage !== "undefined") {
496
+ localStorage.removeItem(STORAGE.AUTH_TOKEN);
497
+ localStorage.removeItem(STORAGE.TOKEN);
498
+ localStorage.removeItem(STORAGE.USER);
499
+ localStorage.removeItem(STORAGE.ACCOUNT_TYPE);
500
+ }
501
+ };
502
+ const setAuthUser = (user) => {
503
+ if (typeof localStorage !== "undefined") {
504
+ localStorage.setItem(STORAGE.USER, JSON.stringify(user));
505
+ }
506
+ };
507
+ const getAuthUser = () => {
508
+ if (typeof localStorage === "undefined") return null;
509
+ const user = localStorage.getItem(STORAGE.USER);
510
+ if (!user) return null;
511
+ try {
512
+ return JSON.parse(user);
513
+ } catch {
514
+ localStorage.removeItem(STORAGE.USER);
515
+ return null;
516
+ }
517
+ };
518
+ const getAccountType = () => {
519
+ if (typeof localStorage === "undefined") return null;
520
+ return localStorage.getItem(STORAGE.ACCOUNT_TYPE);
521
+ };
522
+ async function fetchWithTimeout(url, options, timeout) {
523
+ const controller = new AbortController();
524
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
525
+ const method = options.method || "GET";
526
+ const logId = isDebugMode() ? logApiCall(method, url, options.body ? JSON.parse(options.body) : void 0) : null;
527
+ const startTime = Date.now();
528
+ try {
529
+ const response = await fetch(url, {
530
+ ...options,
531
+ signal: controller.signal
532
+ });
533
+ clearTimeout(timeoutId);
534
+ const contentType = response.headers.get("content-type");
535
+ if (!(contentType == null ? void 0 : contentType.includes("application/json"))) {
536
+ const text = await response.text();
537
+ if (isDebugMode()) {
538
+ console.error("❌ Réponse non-JSON:", text.substring(0, 500));
539
+ if (logId) logApiResponse(logId, response.status, { raw: text.substring(0, 200) }, startTime, "Non-JSON response");
540
+ }
541
+ throw new ApiError(`Erreur serveur (${response.status})`, "server", response.status);
542
+ }
543
+ const data = await response.json();
544
+ if (!response.ok) {
545
+ if (logId) logApiResponse(logId, response.status, data, startTime, data.message);
546
+ throw new ApiError(
547
+ data.message || `Erreur ${response.status}`,
548
+ response.status === 401 ? "auth" : response.status >= 500 ? "server" : "validation",
549
+ response.status,
550
+ data.error_type,
551
+ data
552
+ );
553
+ }
554
+ if (logId) logApiResponse(logId, response.status, data, startTime);
555
+ return data;
556
+ } catch (error) {
557
+ clearTimeout(timeoutId);
558
+ if (error instanceof ApiError) throw error;
559
+ if (error instanceof Error) {
560
+ if (error.name === "AbortError") {
561
+ if (logId) logApiResponse(logId, 0, null, startTime, "Timeout");
562
+ throw new ApiError("Délai d'attente dépassé", "timeout");
563
+ }
564
+ if (error instanceof TypeError) {
565
+ if (logId) logApiResponse(logId, 0, null, startTime, "Network error");
566
+ throw new ApiError("Impossible de contacter le serveur", "network");
567
+ }
568
+ }
569
+ if (logId) logApiResponse(logId, 0, null, startTime, "Unknown error");
570
+ throw new ApiError("Erreur inattendue", "unknown");
571
+ }
572
+ }
573
+ function getHeaders(token) {
574
+ const headers = {
575
+ "Content-Type": "application/json",
576
+ "Accept": "application/json"
577
+ };
578
+ const deviceId = getDeviceId();
579
+ if (deviceId) {
580
+ headers["X-Device-Id"] = deviceId;
581
+ }
582
+ if (token) {
583
+ headers["Authorization"] = `Bearer ${token}`;
584
+ }
585
+ return headers;
586
+ }
587
+ let credentials = null;
588
+ const nativeAuthService = {
589
+ hasCredentials() {
590
+ return credentials !== null;
591
+ },
592
+ getCredentials() {
593
+ return credentials ? { ...credentials } : null;
594
+ },
595
+ async loadCredentials() {
596
+ const config2 = getNativeAuthConfig();
597
+ if (!config2.saasApiUrl) {
598
+ throw new ApiError("saasApiUrl non configurée", "unknown");
599
+ }
600
+ if (isDebugMode()) {
601
+ console.log("📤 [SaaS] GET /native/config");
602
+ }
603
+ const response = await fetchWithTimeout(
604
+ `${config2.saasApiUrl}/native/config`,
605
+ { method: "GET", headers: getHeaders() },
606
+ config2.timeout || 3e4
607
+ );
608
+ if (!response.success || !response.encrypted_credentials || !response.app_key) {
609
+ throw new ApiError("Configuration invalide reçue du SaaS (app_key ou encrypted_credentials manquant)", "server");
610
+ }
611
+ credentials = {
612
+ appKey: response.app_key,
613
+ encryptedCredentials: response.encrypted_credentials
614
+ };
615
+ if (typeof response.debug === "boolean") {
616
+ const currentConfig = getNativeAuthConfig();
617
+ if (currentConfig.debug !== true) {
618
+ setNativeAuthConfig({ debug: response.debug });
619
+ }
620
+ }
621
+ if (isDebugMode()) {
622
+ console.log("✅ [SaaS] Credentials chargés (debug:", response.debug ?? "non défini", ")");
623
+ }
624
+ return credentials;
625
+ },
626
+ async encrypt(type, data) {
627
+ if (!credentials) {
628
+ throw new ApiError("Credentials non chargés. Appelez loadCredentials() d'abord.", "auth");
629
+ }
630
+ const config2 = getNativeAuthConfig();
631
+ if (!config2.iamApiUrl) {
632
+ throw new ApiError("iamApiUrl non configurée", "unknown");
633
+ }
634
+ const payload = {
635
+ type,
636
+ app_key: credentials.appKey,
637
+ encrypted_credentials: credentials.encryptedCredentials,
638
+ ...data
639
+ };
640
+ if (isDebugMode()) {
641
+ console.log("📤 [IAM] POST /iam/native/encrypt", { type, ...data });
642
+ }
643
+ const response = await fetchWithTimeout(
644
+ `${config2.iamApiUrl}/iam/native/encrypt`,
645
+ {
646
+ method: "POST",
647
+ headers: getHeaders(),
648
+ body: JSON.stringify(payload)
649
+ },
650
+ config2.timeout || 3e4
651
+ );
652
+ if (isDebugMode()) {
653
+ console.log("✅ [IAM] native_token reçu, expire dans", response.expires_in, "s");
654
+ }
655
+ return response;
656
+ },
657
+ async init(nativeToken) {
658
+ const config2 = getNativeAuthConfig();
659
+ if (!config2.iamApiUrl) {
660
+ throw new ApiError("iamApiUrl non configurée", "unknown");
661
+ }
662
+ if (isDebugMode()) {
663
+ console.log("📤 [IAM] POST /iam/native/init");
664
+ }
665
+ const response = await fetchWithTimeout(
666
+ `${config2.iamApiUrl}/iam/native/init`,
667
+ {
668
+ method: "POST",
669
+ headers: getHeaders(),
670
+ body: JSON.stringify({ native_token: nativeToken })
671
+ },
672
+ config2.timeout || 3e4
673
+ );
674
+ if (isDebugMode()) {
675
+ console.log("✅ [IAM] Init:", { status: response.status, hasProcessToken: !!response.process_token });
676
+ }
677
+ return response;
678
+ },
679
+ async validate(processToken, creds) {
680
+ const config2 = getNativeAuthConfig();
681
+ if (!config2.iamApiUrl) {
682
+ throw new ApiError("iamApiUrl non configurée", "unknown");
683
+ }
684
+ if (isDebugMode()) {
685
+ console.log("📤 [IAM] POST /iam/native/validate");
686
+ }
687
+ const response = await fetchWithTimeout(
688
+ `${config2.iamApiUrl}/iam/native/validate`,
689
+ {
690
+ method: "POST",
691
+ headers: getHeaders(),
692
+ body: JSON.stringify({
693
+ process_token: processToken,
694
+ ...creds
695
+ })
696
+ },
697
+ config2.timeout || 3e4
698
+ );
699
+ if (isDebugMode()) {
700
+ console.log("✅ [IAM] Validate:", {
701
+ hasCallback: !!response.callback_token,
702
+ requires2fa: response.requires_2fa
703
+ });
704
+ }
705
+ return response;
706
+ },
707
+ async verifyTotp(processToken, totpCode) {
708
+ const config2 = getNativeAuthConfig();
709
+ if (!config2.iamApiUrl) {
710
+ throw new ApiError("iamApiUrl non configurée", "unknown");
711
+ }
712
+ if (isDebugMode()) {
713
+ console.log("📤 [IAM] POST /iam/native/verify-totp");
714
+ }
715
+ return fetchWithTimeout(
716
+ `${config2.iamApiUrl}/iam/native/verify-totp`,
717
+ {
718
+ method: "POST",
719
+ headers: getHeaders(),
720
+ body: JSON.stringify({
721
+ process_token: processToken,
722
+ totp_code: totpCode
723
+ })
724
+ },
725
+ config2.timeout || 3e4
726
+ );
727
+ },
728
+ async grantAccess(processToken) {
729
+ const config2 = getNativeAuthConfig();
730
+ if (!config2.iamApiUrl) {
731
+ throw new ApiError("iamApiUrl non configurée", "unknown");
732
+ }
733
+ if (isDebugMode()) {
734
+ console.log("📤 [IAM] POST /iam/native/grant-access");
735
+ }
736
+ return fetchWithTimeout(
737
+ `${config2.iamApiUrl}/iam/native/grant-access`,
738
+ {
739
+ method: "POST",
740
+ headers: getHeaders(),
741
+ body: JSON.stringify({ process_token: processToken })
742
+ },
743
+ config2.timeout || 3e4
744
+ );
745
+ },
746
+ async resendOtp(processToken) {
747
+ const config2 = getNativeAuthConfig();
748
+ if (!config2.iamApiUrl) {
749
+ throw new ApiError("iamApiUrl non configurée", "unknown");
750
+ }
751
+ if (isDebugMode()) {
752
+ console.log("📤 [IAM] POST /iam/native/resend-otp");
753
+ }
754
+ return fetchWithTimeout(
755
+ `${config2.iamApiUrl}/iam/native/resend-otp`,
756
+ {
757
+ method: "POST",
758
+ headers: getHeaders(),
759
+ body: JSON.stringify({ process_token: processToken })
760
+ },
761
+ config2.timeout || 3e4
762
+ );
763
+ },
764
+ async exchange(callbackToken) {
765
+ var _a;
766
+ const config2 = getNativeAuthConfig();
767
+ if (!config2.saasApiUrl) {
768
+ throw new ApiError("saasApiUrl non configurée", "unknown");
769
+ }
770
+ if (isDebugMode()) {
771
+ console.log("📤 [SaaS] POST /native/exchange");
772
+ }
773
+ const response = await fetchWithTimeout(
774
+ `${config2.saasApiUrl}/native/exchange`,
775
+ {
776
+ method: "POST",
777
+ headers: getHeaders(),
778
+ body: JSON.stringify({ callback_token: callbackToken })
779
+ },
780
+ config2.timeout || 3e4
781
+ );
782
+ if (response.success && response.token) {
783
+ setAuthToken(response.token);
784
+ if (response.user) {
785
+ setAuthUser(response.user);
786
+ }
787
+ }
788
+ if (isDebugMode()) {
789
+ console.log("✅ [SaaS] Session établie:", { user: (_a = response.user) == null ? void 0 : _a.name });
790
+ }
791
+ return response;
792
+ },
793
+ async checkToken(token) {
794
+ const cfg = getNativeAuthConfig();
795
+ if (!cfg.saasApiUrl) {
796
+ throw new ApiError("saasApiUrl non configurée", "unknown");
797
+ }
798
+ if (isDebugMode()) {
799
+ console.log("📤 [SaaS] POST /native/check-token");
800
+ }
801
+ try {
802
+ const response = await fetchWithTimeout(
803
+ `${cfg.saasApiUrl}/native/check-token`,
804
+ {
805
+ method: "POST",
806
+ headers: getHeaders(token)
807
+ },
808
+ 1e4
809
+ );
810
+ return { valid: response.valid !== false, user_infos: response.user_infos };
811
+ } catch (err) {
812
+ if (err instanceof ApiError && err.statusCode === 401) {
813
+ return { valid: false };
814
+ }
815
+ throw err;
816
+ }
817
+ },
818
+ async logout(token) {
819
+ const config2 = getNativeAuthConfig();
820
+ if (!config2.saasApiUrl) {
821
+ throw new ApiError("saasApiUrl non configurée", "unknown");
822
+ }
823
+ try {
824
+ const response = await fetchWithTimeout(
825
+ `${config2.saasApiUrl}/native/logout`,
826
+ {
827
+ method: "POST",
828
+ headers: getHeaders(token)
829
+ },
830
+ config2.timeout || 3e4
831
+ );
832
+ clearAuthToken();
833
+ credentials = null;
834
+ return response;
835
+ } catch {
836
+ clearAuthToken();
837
+ credentials = null;
838
+ return { success: true };
839
+ }
840
+ },
841
+ clearCredentials() {
842
+ credentials = null;
843
+ },
844
+ // ============================================
845
+ // High-level methods
846
+ // ============================================
847
+ async loginEmail(email, accountType = "user") {
848
+ const { native_token } = await this.encrypt("login_email", {
849
+ email,
850
+ account_type: accountType
851
+ });
852
+ return this.init(native_token);
853
+ },
854
+ async loginPhone(ccphone, phone, accountType = "user") {
855
+ const { native_token } = await this.encrypt("login_phone", {
856
+ ccphone,
857
+ phone,
858
+ account_type: accountType
859
+ });
860
+ return this.init(native_token);
861
+ },
862
+ async loginAccessOtp(otpCode) {
863
+ const { native_token } = await this.encrypt("login_access_otp", {
864
+ otp_code: otpCode.replace(/[\s-]/g, "")
865
+ });
866
+ return this.init(native_token);
867
+ },
868
+ async register(data) {
869
+ const { native_token } = await this.encrypt("register", {
870
+ ...data,
871
+ registration_type: data.registration_type || "email"
872
+ });
873
+ return this.init(native_token);
874
+ },
875
+ async registerPhoneOnly(data) {
876
+ if (data.ccphone !== "+221") {
877
+ throw new ApiError(
878
+ "L'inscription par téléphone est réservée aux numéros sénégalais (+221)",
879
+ "validation",
880
+ 422,
881
+ "invalid_country_code"
882
+ );
883
+ }
884
+ const { native_token } = await this.encrypt("register", {
885
+ ...data,
886
+ email: "",
887
+ registration_type: "phone"
888
+ });
889
+ return this.init(native_token);
890
+ },
891
+ async recoverPassword(method, identifier) {
892
+ const { native_token } = await this.encrypt("recovery_password", {
893
+ method,
894
+ ...identifier
895
+ });
896
+ return this.init(native_token);
897
+ }
898
+ };
899
+ function getIAMHeaders() {
900
+ const creds = nativeAuthService.getCredentials();
901
+ if (!creds) {
902
+ throw new ApiError("Credentials non chargés. Appelez loadCredentials() d'abord.", "auth");
903
+ }
904
+ return {
905
+ "Content-Type": "application/json",
906
+ "Accept": "application/json"
907
+ };
908
+ }
909
+ const mobilePasswordService = {
910
+ hasCredentials() {
911
+ return nativeAuthService.hasCredentials();
912
+ },
913
+ async loadCredentials() {
914
+ return nativeAuthService.loadCredentials();
915
+ },
916
+ async initRecovery(email) {
917
+ const config2 = getNativeAuthConfig();
918
+ if (!config2.iamApiUrl) {
919
+ throw new ApiError("iamApiUrl non configurée", "unknown");
920
+ }
921
+ if (!nativeAuthService.hasCredentials()) {
922
+ await nativeAuthService.loadCredentials();
923
+ }
924
+ if (isDebugMode()) {
925
+ console.log("📤 [IAM] POST /iam/native/password/init", { email });
926
+ }
927
+ const response = await fetchWithTimeout(
928
+ `${config2.iamApiUrl}/iam/native/password/init`,
929
+ {
930
+ method: "POST",
931
+ headers: getIAMHeaders(),
932
+ body: JSON.stringify({ email })
933
+ },
934
+ config2.timeout || 3e4
935
+ );
936
+ if (isDebugMode()) {
937
+ console.log("✅ [IAM] Init recovery:", {
938
+ status: response.status,
939
+ hasOtp: !!response.otp_code
940
+ });
941
+ }
942
+ return response;
943
+ },
944
+ async selectMethod(processToken, receiveChoice) {
945
+ const config2 = getNativeAuthConfig();
946
+ if (!config2.iamApiUrl) {
947
+ throw new ApiError("iamApiUrl non configurée", "unknown");
948
+ }
949
+ if (isDebugMode()) {
950
+ console.log("📤 [IAM] POST /iam/native/password/select-method", { receiveChoice });
951
+ }
952
+ return fetchWithTimeout(
953
+ `${config2.iamApiUrl}/iam/native/password/select-method`,
954
+ {
955
+ method: "POST",
956
+ headers: getIAMHeaders(),
957
+ body: JSON.stringify({
958
+ process_token: processToken,
959
+ receive_choice: receiveChoice
960
+ })
961
+ },
962
+ config2.timeout || 3e4
963
+ );
964
+ },
965
+ validateOtpLocally(inputCode, storedCode) {
966
+ return inputCode === storedCode;
967
+ },
968
+ async reset(processToken, password) {
969
+ const config2 = getNativeAuthConfig();
970
+ if (!config2.iamApiUrl) {
971
+ throw new ApiError("iamApiUrl non configurée", "unknown");
972
+ }
973
+ if (isDebugMode()) {
974
+ console.log("📤 [IAM] POST /iam/native/password/reset");
975
+ }
976
+ return fetchWithTimeout(
977
+ `${config2.iamApiUrl}/iam/native/password/reset`,
978
+ {
979
+ method: "POST",
980
+ headers: getIAMHeaders(),
981
+ body: JSON.stringify({
982
+ process_token: processToken,
983
+ password,
984
+ password_confirmation: password
985
+ })
986
+ },
987
+ config2.timeout || 3e4
988
+ );
989
+ },
990
+ async resendOtp(processToken) {
991
+ const config2 = getNativeAuthConfig();
992
+ if (!config2.iamApiUrl) {
993
+ throw new ApiError("iamApiUrl non configurée", "unknown");
994
+ }
995
+ if (isDebugMode()) {
996
+ console.log("📤 [IAM] POST /iam/native/password/resend");
997
+ }
998
+ return fetchWithTimeout(
999
+ `${config2.iamApiUrl}/iam/native/password/resend`,
1000
+ {
1001
+ method: "POST",
1002
+ headers: getIAMHeaders(),
1003
+ body: JSON.stringify({ process_token: processToken })
1004
+ },
1005
+ config2.timeout || 3e4
1006
+ );
1007
+ }
1008
+ };
1009
+ function getErrorMessage$2(err, context) {
1010
+ if (err instanceof ApiError) {
1011
+ return { message: err.message, type: err.type };
1012
+ }
1013
+ if (err instanceof Error) {
1014
+ if (err.message.includes("fetch") || err.message.includes("network")) {
1015
+ return { message: "Vérifiez votre connexion Internet", type: "network" };
1016
+ }
1017
+ if (err.message.includes("timeout")) {
1018
+ return { message: "Le serveur met trop de temps à répondre", type: "timeout" };
1019
+ }
1020
+ return { message: err.message, type: "unknown" };
1021
+ }
1022
+ return { message: `Erreur lors de ${context}`, type: "unknown" };
1023
+ }
1024
+ function useMobilePassword(options) {
1025
+ const { saasApiUrl, iamApiUrl, autoLoadCredentials = true, debug = false } = options;
1026
+ const configuredRef = useRef(false);
1027
+ useEffect(() => {
1028
+ if (!configuredRef.current) {
1029
+ setNativeAuthConfig({ saasApiUrl, iamApiUrl, debug });
1030
+ configuredRef.current = true;
1031
+ }
1032
+ }, [saasApiUrl, iamApiUrl, debug]);
1033
+ const [state, setState] = useState({
1034
+ credentialsLoaded: false,
1035
+ processToken: null,
1036
+ status: "idle",
1037
+ storedOtp: null,
1038
+ receiveMode: null,
1039
+ maskedEmail: null,
1040
+ maskedPhone: null,
1041
+ loading: false,
1042
+ error: null,
1043
+ errorType: null
1044
+ });
1045
+ useEffect(() => {
1046
+ if (autoLoadCredentials && !state.credentialsLoaded) {
1047
+ loadCredentials();
1048
+ }
1049
+ }, [autoLoadCredentials, state.credentialsLoaded]);
1050
+ const loadCredentials = useCallback(async () => {
1051
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1052
+ try {
1053
+ await mobilePasswordService.loadCredentials();
1054
+ setState((prev) => ({ ...prev, credentialsLoaded: true, loading: false }));
1055
+ return { success: true };
1056
+ } catch (err) {
1057
+ const { message, type } = getErrorMessage$2(err, "le chargement");
1058
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1059
+ return { success: false, error_type: type };
1060
+ }
1061
+ }, []);
1062
+ const initRecovery = useCallback(async (email) => {
1063
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1064
+ try {
1065
+ if (!mobilePasswordService.hasCredentials()) {
1066
+ await mobilePasswordService.loadCredentials();
1067
+ }
1068
+ const response = await mobilePasswordService.initRecovery(email);
1069
+ if (response.success && response.process_token) {
1070
+ if (response.status === "choice_required") {
1071
+ setState((prev) => ({
1072
+ ...prev,
1073
+ credentialsLoaded: true,
1074
+ processToken: response.process_token,
1075
+ status: "choice_required",
1076
+ maskedEmail: response.masked_email || null,
1077
+ maskedPhone: response.masked_phone || null,
1078
+ loading: false
1079
+ }));
1080
+ return { success: true, status: "choice_required" };
1081
+ } else {
1082
+ setState((prev) => ({
1083
+ ...prev,
1084
+ credentialsLoaded: true,
1085
+ processToken: response.process_token,
1086
+ status: "pending_otp",
1087
+ storedOtp: response.otp_code || null,
1088
+ receiveMode: "email",
1089
+ maskedEmail: response.masked_email || null,
1090
+ loading: false
1091
+ }));
1092
+ return { success: true, status: "pending_otp" };
1093
+ }
1094
+ }
1095
+ setState((prev) => ({
1096
+ ...prev,
1097
+ loading: false,
1098
+ error: response.message || "Aucun compte trouvé avec cet email",
1099
+ errorType: response.error_type || "unknown"
1100
+ }));
1101
+ return { success: false, error_type: response.error_type };
1102
+ } catch (err) {
1103
+ const { message, type } = getErrorMessage$2(err, "la recherche du compte");
1104
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1105
+ return { success: false, error_type: type };
1106
+ }
1107
+ }, []);
1108
+ const selectMethod = useCallback(async (choice) => {
1109
+ if (!state.processToken) {
1110
+ return { success: false, error: "Session invalide" };
1111
+ }
1112
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1113
+ try {
1114
+ const response = await mobilePasswordService.selectMethod(state.processToken, choice);
1115
+ if (response.success) {
1116
+ setState((prev) => ({
1117
+ ...prev,
1118
+ status: "pending_otp",
1119
+ storedOtp: response.otp_code || null,
1120
+ receiveMode: response.receive_mode || choice,
1121
+ loading: false
1122
+ }));
1123
+ return { success: true };
1124
+ }
1125
+ setState((prev) => ({
1126
+ ...prev,
1127
+ loading: false,
1128
+ error: response.message || "Impossible d'envoyer le code",
1129
+ errorType: response.error_type || "unknown"
1130
+ }));
1131
+ return { success: false, error_type: response.error_type };
1132
+ } catch (err) {
1133
+ const { message, type } = getErrorMessage$2(err, "l'envoi du code");
1134
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1135
+ return { success: false, error_type: type };
1136
+ }
1137
+ }, [state.processToken]);
1138
+ const verifyOtp = useCallback((inputCode) => {
1139
+ if (!state.storedOtp) {
1140
+ setState((prev) => ({ ...prev, error: "Session invalide", errorType: "invalid_session" }));
1141
+ return false;
1142
+ }
1143
+ const isValid = mobilePasswordService.validateOtpLocally(inputCode, state.storedOtp);
1144
+ if (isValid) {
1145
+ setState((prev) => ({ ...prev, status: "pending_password", error: null, errorType: null }));
1146
+ return true;
1147
+ }
1148
+ setState((prev) => ({ ...prev, error: "Code incorrect", errorType: "invalid_otp" }));
1149
+ return false;
1150
+ }, [state.storedOtp]);
1151
+ const resetPassword = useCallback(async (password) => {
1152
+ if (!state.processToken) {
1153
+ return { success: false, error: "Session invalide" };
1154
+ }
1155
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1156
+ try {
1157
+ const response = await mobilePasswordService.reset(state.processToken, password);
1158
+ if (response.success) {
1159
+ setState((prev) => ({ ...prev, status: "completed", loading: false }));
1160
+ return { success: true };
1161
+ }
1162
+ setState((prev) => ({
1163
+ ...prev,
1164
+ loading: false,
1165
+ error: response.message || "Erreur lors de la réinitialisation",
1166
+ errorType: response.error_type || "unknown"
1167
+ }));
1168
+ return { success: false, error_type: response.error_type };
1169
+ } catch (err) {
1170
+ const { message, type } = getErrorMessage$2(err, "la réinitialisation");
1171
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1172
+ return { success: false, error_type: type };
1173
+ }
1174
+ }, [state.processToken]);
1175
+ const resendOtp = useCallback(async () => {
1176
+ if (!state.processToken) {
1177
+ return { success: false, error: "Session invalide" };
1178
+ }
1179
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1180
+ try {
1181
+ const response = await mobilePasswordService.resendOtp(state.processToken);
1182
+ if (response.success && response.otp_code) {
1183
+ setState((prev) => ({ ...prev, storedOtp: response.otp_code, loading: false }));
1184
+ return { success: true, cooldown: response.cooldown_remaining };
1185
+ }
1186
+ setState((prev) => ({ ...prev, loading: false }));
1187
+ return { success: false, error_type: response.error_type };
1188
+ } catch (err) {
1189
+ const { message, type } = getErrorMessage$2(err, "le renvoi");
1190
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1191
+ return { success: false, error_type: type };
1192
+ }
1193
+ }, [state.processToken]);
1194
+ const reset = useCallback(() => {
1195
+ setState({
1196
+ credentialsLoaded: state.credentialsLoaded,
1197
+ processToken: null,
1198
+ status: "idle",
1199
+ storedOtp: null,
1200
+ receiveMode: null,
1201
+ maskedEmail: null,
1202
+ maskedPhone: null,
1203
+ loading: false,
1204
+ error: null,
1205
+ errorType: null
1206
+ });
1207
+ }, [state.credentialsLoaded]);
1208
+ const clearError = useCallback(() => {
1209
+ setState((prev) => ({ ...prev, error: null, errorType: null }));
1210
+ }, []);
1211
+ return {
1212
+ credentialsLoaded: state.credentialsLoaded,
1213
+ processToken: state.processToken,
1214
+ status: state.status,
1215
+ storedOtp: state.storedOtp,
1216
+ receiveMode: state.receiveMode,
1217
+ maskedEmail: state.maskedEmail,
1218
+ maskedPhone: state.maskedPhone,
1219
+ loading: state.loading,
1220
+ error: state.error,
1221
+ errorType: state.errorType,
1222
+ isCompleted: state.status === "completed",
1223
+ isChoiceRequired: state.status === "choice_required",
1224
+ isPendingOtp: state.status === "pending_otp",
1225
+ isPendingPassword: state.status === "pending_password",
1226
+ loadCredentials,
1227
+ initRecovery,
1228
+ selectMethod,
1229
+ verifyOtp,
1230
+ resetPassword,
1231
+ resendOtp,
1232
+ reset,
1233
+ clearError
1234
+ };
1235
+ }
1236
+ const C$3 = {
1237
+ primary: "#002147",
1238
+ green: "#16a34a",
1239
+ greenBg: "#dcfce7",
1240
+ gray200: "#e5e7eb",
1241
+ gray500: "#6b7280",
1242
+ gray700: "#374151",
1243
+ red: "#dc2626",
1244
+ redBg: "#fef2f2"
1245
+ };
1246
+ const iconCircle$2 = (bg, size = "3rem") => ({
1247
+ margin: "0 auto",
1248
+ width: size,
1249
+ height: size,
1250
+ backgroundColor: bg,
1251
+ borderRadius: "50%",
1252
+ display: "flex",
1253
+ alignItems: "center",
1254
+ justifyContent: "center"
1255
+ });
1256
+ const backBtnStyle$2 = {
1257
+ position: "absolute",
1258
+ left: "1rem",
1259
+ top: "1rem",
1260
+ background: "none",
1261
+ border: "none",
1262
+ cursor: "pointer",
1263
+ padding: "0.5rem",
1264
+ borderRadius: "0.375rem",
1265
+ color: C$3.gray700,
1266
+ zIndex: 10
1267
+ };
1268
+ function PasswordRecoveryModal({ open, onOpenChange, onSuccess, saasApiUrl, iamApiUrl, debug = false }) {
1269
+ const {
1270
+ status,
1271
+ loading: pwLoading,
1272
+ error: pwError,
1273
+ receiveMode,
1274
+ maskedEmail,
1275
+ maskedPhone,
1276
+ initRecovery,
1277
+ selectMethod,
1278
+ verifyOtp,
1279
+ resetPassword,
1280
+ resendOtp,
1281
+ reset: resetHook,
1282
+ clearError
1283
+ } = useMobilePassword({ saasApiUrl, iamApiUrl, debug });
1284
+ const [step, setStep] = useState("email");
1285
+ const [email, setEmail] = useState("");
1286
+ const [otp, setOtp] = useState("");
1287
+ const [password, setPassword] = useState("");
1288
+ const [confirmPassword, setConfirmPassword] = useState("");
1289
+ const [localError, setLocalError] = useState(null);
1290
+ const [selectedMethod, setSelectedMethod] = useState("email");
1291
+ const [resendCooldown, setResendCooldown] = useState(0);
1292
+ const error = localError || pwError;
1293
+ useEffect(() => {
1294
+ if (status === "choice_required") setStep("method-choice");
1295
+ else if (status === "pending_otp") {
1296
+ setStep("otp");
1297
+ setResendCooldown(60);
1298
+ } else if (status === "pending_password") setStep("password");
1299
+ else if (status === "completed") setStep("success");
1300
+ }, [status]);
1301
+ useEffect(() => {
1302
+ if (resendCooldown > 0) {
1303
+ const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1e3);
1304
+ return () => clearTimeout(timer);
1305
+ }
1306
+ }, [resendCooldown]);
1307
+ const resetState = () => {
1308
+ setStep("email");
1309
+ setEmail("");
1310
+ setOtp("");
1311
+ setPassword("");
1312
+ setConfirmPassword("");
1313
+ setLocalError(null);
1314
+ setSelectedMethod("email");
1315
+ setResendCooldown(0);
1316
+ resetHook();
1317
+ };
1318
+ const handleClose = () => {
1319
+ resetState();
1320
+ onOpenChange(false);
1321
+ };
1322
+ const handleBackToLogin = () => {
1323
+ resetState();
1324
+ onSuccess();
1325
+ };
1326
+ const handleEmailSubmit = async () => {
1327
+ setLocalError(null);
1328
+ clearError();
1329
+ if (!email.trim()) {
1330
+ setLocalError("L'adresse email est requise");
1331
+ return;
1332
+ }
1333
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
1334
+ setLocalError("Veuillez entrer une adresse email valide");
1335
+ return;
1336
+ }
1337
+ await initRecovery(email.trim());
1338
+ };
1339
+ const handleMethodSubmit = async () => {
1340
+ setLocalError(null);
1341
+ clearError();
1342
+ await selectMethod(selectedMethod);
1343
+ };
1344
+ const handleOTPSubmit = () => {
1345
+ if (otp.length !== 6) {
1346
+ setLocalError("Veuillez entrer le code à 6 chiffres");
1347
+ return;
1348
+ }
1349
+ setLocalError(null);
1350
+ clearError();
1351
+ const isValid = verifyOtp(otp);
1352
+ if (!isValid) setLocalError("Code incorrect");
1353
+ };
1354
+ const handleResendOTP = async () => {
1355
+ if (resendCooldown > 0) return;
1356
+ const result = await resendOtp();
1357
+ if (result.success) {
1358
+ setResendCooldown(result.cooldown || 60);
1359
+ setOtp("");
1360
+ setLocalError(null);
1361
+ }
1362
+ };
1363
+ const handlePasswordSubmit = async () => {
1364
+ if (password.length < 8) {
1365
+ setLocalError("Le mot de passe doit contenir au moins 8 caractères");
1366
+ return;
1367
+ }
1368
+ if (password !== confirmPassword) {
1369
+ setLocalError("Les mots de passe ne correspondent pas");
1370
+ return;
1371
+ }
1372
+ setLocalError(null);
1373
+ clearError();
1374
+ await resetPassword(password);
1375
+ };
1376
+ const goBack = () => {
1377
+ setLocalError(null);
1378
+ clearError();
1379
+ if (step === "method-choice") {
1380
+ setStep("email");
1381
+ resetHook();
1382
+ } else if (step === "otp") {
1383
+ setStep(maskedPhone ? "method-choice" : "email");
1384
+ resetHook();
1385
+ } else if (step === "password") setStep("otp");
1386
+ };
1387
+ if (step === "success") {
1388
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
1389
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
1390
+ /* @__PURE__ */ jsx("div", { style: { ...iconCircle$2(C$3.greenBg), width: "5rem", height: "5rem" }, children: /* @__PURE__ */ jsx(IconCheckCircle2, { style: { width: "3rem", height: "3rem", color: C$3.green } }) }),
1391
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Mot de passe modifié !" }),
1392
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Votre mot de passe a été changé avec succès." })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsx(Button, { onClick: handleBackToLogin, style: { width: "100%", marginTop: "1rem" }, children: "Retour à la connexion" })
1395
+ ] }) });
1396
+ }
1397
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
1398
+ step !== "email" && /* @__PURE__ */ jsx("button", { onClick: goBack, style: backBtnStyle$2, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) }),
1399
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
1400
+ /* @__PURE__ */ jsx("div", { style: iconCircle$2(C$3.primary + "1a"), children: step === "otp" ? /* @__PURE__ */ jsx(IconKeyRound, { style: { width: "1.5rem", height: "1.5rem", color: C$3.primary } }) : /* @__PURE__ */ jsx(IconLock, { style: { width: "1.5rem", height: "1.5rem", color: C$3.primary } }) }),
1401
+ /* @__PURE__ */ jsxs(DialogTitle, { children: [
1402
+ step === "email" && "Récupération de mot de passe",
1403
+ step === "method-choice" && "Recevoir le code de vérification",
1404
+ step === "otp" && "Vérification",
1405
+ step === "password" && "Nouveau mot de passe"
1406
+ ] }),
1407
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
1408
+ step === "email" && "Veuillez entrer votre adresse email",
1409
+ step === "method-choice" && "Comment souhaitez-vous recevoir le code ?",
1410
+ step === "otp" && (receiveMode === "phone" ? `Code envoyé par SMS au ${maskedPhone}` : `Code envoyé par email à ${maskedEmail}`),
1411
+ step === "password" && "Définissez votre nouveau mot de passe"
1412
+ ] })
1413
+ ] }),
1414
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
1415
+ error && /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.375rem", backgroundColor: C$3.redBg, color: C$3.red, fontSize: "0.875rem" }, children: error }),
1416
+ step === "email" && /* @__PURE__ */ jsxs(Fragment, { children: [
1417
+ /* @__PURE__ */ jsxs("div", { children: [
1418
+ /* @__PURE__ */ jsx(Label, { htmlFor: "recovery-email", children: "Adresse email" }),
1419
+ /* @__PURE__ */ jsx(Input, { id: "recovery-email", type: "email", placeholder: "vous@exemple.com", value: email, onChange: (e) => setEmail(e.target.value), disabled: pwLoading })
1420
+ ] }),
1421
+ /* @__PURE__ */ jsx(Button, { onClick: handleEmailSubmit, disabled: pwLoading, style: { width: "100%" }, children: pwLoading ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1422
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
1423
+ " Vérification..."
1424
+ ] }) : "Continuer" }),
1425
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: handleBackToLogin, style: { width: "100%" }, children: "Retour à la connexion" })
1426
+ ] }),
1427
+ step === "method-choice" && /* @__PURE__ */ jsxs(Fragment, { children: [
1428
+ /* @__PURE__ */ jsx(RadioGroup, { value: selectedMethod, onValueChange: (v) => setSelectedMethod(v), children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
1429
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", padding: "1rem", borderRadius: "0.5rem", border: `2px solid ${selectedMethod === "email" ? C$3.primary : C$3.gray200}`, cursor: "pointer" }, onClick: () => setSelectedMethod("email"), children: [
1430
+ /* @__PURE__ */ jsx(RadioGroupItem, { value: "email", id: "method-email" }),
1431
+ /* @__PURE__ */ jsxs("div", { children: [
1432
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: "Par email" }),
1433
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: C$3.gray500 }, children: maskedEmail })
1434
+ ] })
1435
+ ] }),
1436
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", padding: "1rem", borderRadius: "0.5rem", border: `2px solid ${selectedMethod === "phone" ? C$3.primary : C$3.gray200}`, cursor: "pointer" }, onClick: () => setSelectedMethod("phone"), children: [
1437
+ /* @__PURE__ */ jsx(RadioGroupItem, { value: "phone", id: "method-phone" }),
1438
+ /* @__PURE__ */ jsxs("div", { children: [
1439
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: "Par SMS" }),
1440
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: C$3.gray500 }, children: maskedPhone })
1441
+ ] })
1442
+ ] })
1443
+ ] }) }),
1444
+ /* @__PURE__ */ jsx(Button, { onClick: handleMethodSubmit, disabled: pwLoading, style: { width: "100%" }, children: pwLoading ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1445
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
1446
+ " Envoi..."
1447
+ ] }) : "Envoyer le code" })
1448
+ ] }),
1449
+ step === "otp" && /* @__PURE__ */ jsxs(Fragment, { children: [
1450
+ /* @__PURE__ */ jsx(OTPInput, { value: otp, onChange: setOtp, disabled: pwLoading }),
1451
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center", fontSize: "0.875rem" }, children: resendCooldown > 0 ? /* @__PURE__ */ jsxs("span", { style: { color: C$3.gray500 }, children: [
1452
+ "Renvoyer dans ",
1453
+ resendCooldown,
1454
+ "s"
1455
+ ] }) : /* @__PURE__ */ jsx("button", { type: "button", style: { color: C$3.primary, background: "none", border: "none", cursor: "pointer", textDecoration: "underline" }, onClick: handleResendOTP, children: "Code non reçu ? Renvoyer" }) }),
1456
+ /* @__PURE__ */ jsx(Button, { onClick: handleOTPSubmit, disabled: otp.length !== 6, style: { width: "100%" }, children: "Vérifier" })
1457
+ ] }),
1458
+ step === "password" && /* @__PURE__ */ jsxs(Fragment, { children: [
1459
+ /* @__PURE__ */ jsxs("div", { children: [
1460
+ /* @__PURE__ */ jsx(Label, { children: "Nouveau mot de passe" }),
1461
+ /* @__PURE__ */ jsx(Input, { type: "password", placeholder: "Minimum 8 caractères", value: password, onChange: (e) => setPassword(e.target.value), disabled: pwLoading })
1462
+ ] }),
1463
+ /* @__PURE__ */ jsxs("div", { children: [
1464
+ /* @__PURE__ */ jsx(Label, { children: "Confirmer le mot de passe" }),
1465
+ /* @__PURE__ */ jsx(Input, { type: "password", placeholder: "Retapez votre mot de passe", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), disabled: pwLoading })
1466
+ ] }),
1467
+ /* @__PURE__ */ jsx(Button, { onClick: handlePasswordSubmit, disabled: pwLoading || !password || !confirmPassword, style: { width: "100%" }, children: pwLoading ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1468
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
1469
+ " Modification..."
1470
+ ] }) : "Modifier le mot de passe" })
1471
+ ] })
1472
+ ] })
1473
+ ] }) });
1474
+ }
1475
+ const FIRST_CHECK_DELAY = 2 * 60 * 1e3;
1476
+ const INTERVAL_DELAY = 5 * 60 * 1e3;
1477
+ async function checkTokenValidity(saasApiUrl, token, debug) {
1478
+ if (typeof navigator !== "undefined" && !navigator.onLine) {
1479
+ if (debug) console.log("🔄 [HealthCheck] Offline — skip");
1480
+ throw new Error("offline");
1481
+ }
1482
+ const controller = new AbortController();
1483
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
1484
+ try {
1485
+ const response = await fetch(`${saasApiUrl}/native/check-token`, {
1486
+ method: "POST",
1487
+ headers: {
1488
+ "Content-Type": "application/json",
1489
+ "Accept": "application/json",
1490
+ "Authorization": `Bearer ${token}`
1491
+ },
1492
+ signal: controller.signal
1493
+ });
1494
+ clearTimeout(timeoutId);
1495
+ if (response.status === 401) {
1496
+ if (debug) console.log("🔄 [HealthCheck] Token invalide (401)");
1497
+ return { valid: false };
1498
+ }
1499
+ if (!response.ok) {
1500
+ if (debug) console.log(`🔄 [HealthCheck] Serveur erreur ${response.status} — session conservée`);
1501
+ throw new Error(`server_error_${response.status}`);
1502
+ }
1503
+ const data = await response.json();
1504
+ if (debug) console.log("🔄 [HealthCheck] Token valide ✅");
1505
+ return {
1506
+ valid: data.valid !== false,
1507
+ user_infos: data.user_infos
1508
+ };
1509
+ } catch (error) {
1510
+ clearTimeout(timeoutId);
1511
+ if (error instanceof Error && error.message === "offline") {
1512
+ throw error;
1513
+ }
1514
+ if (debug) {
1515
+ const msg = error instanceof Error ? error.message : "unknown";
1516
+ console.log(`🔄 [HealthCheck] Erreur réseau/serveur (${msg}) — session conservée`);
1517
+ }
1518
+ throw error;
1519
+ }
1520
+ }
1521
+ function useTokenHealthCheck(options) {
1522
+ const { enabled, saasApiUrl, onTokenInvalid, onUserUpdated, debug = false } = options;
1523
+ const timerRef = useRef(null);
1524
+ const intervalRef = useRef(null);
1525
+ const enabledRef = useRef(enabled);
1526
+ const callbacksRef = useRef({ onTokenInvalid, onUserUpdated });
1527
+ enabledRef.current = enabled;
1528
+ callbacksRef.current = { onTokenInvalid, onUserUpdated };
1529
+ const performCheck = useCallback(async () => {
1530
+ if (!enabledRef.current) return;
1531
+ const token = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN) : null;
1532
+ if (!token) {
1533
+ if (debug) console.log("🔄 [HealthCheck] Pas de token — arrêt");
1534
+ return;
1535
+ }
1536
+ try {
1537
+ const result = await checkTokenValidity(saasApiUrl, token, debug);
1538
+ if (!result.valid) {
1539
+ callbacksRef.current.onTokenInvalid();
1540
+ return;
1541
+ }
1542
+ if (result.user_infos && callbacksRef.current.onUserUpdated) {
1543
+ callbacksRef.current.onUserUpdated(result.user_infos);
1544
+ }
1545
+ } catch {
1546
+ }
1547
+ }, [saasApiUrl, debug]);
1548
+ useEffect(() => {
1549
+ if (timerRef.current) {
1550
+ clearTimeout(timerRef.current);
1551
+ timerRef.current = null;
1552
+ }
1553
+ if (intervalRef.current) {
1554
+ clearInterval(intervalRef.current);
1555
+ intervalRef.current = null;
1556
+ }
1557
+ if (!enabled || !saasApiUrl) return;
1558
+ if (debug) console.log("🔄 [HealthCheck] Activé — premier check dans 2 min");
1559
+ timerRef.current = setTimeout(() => {
1560
+ performCheck();
1561
+ intervalRef.current = setInterval(performCheck, INTERVAL_DELAY);
1562
+ }, FIRST_CHECK_DELAY);
1563
+ return () => {
1564
+ if (timerRef.current) clearTimeout(timerRef.current);
1565
+ if (intervalRef.current) clearInterval(intervalRef.current);
1566
+ };
1567
+ }, [enabled, saasApiUrl, performCheck, debug]);
1568
+ }
1569
+ function saveSession(exchangeResult, accountType) {
1570
+ const sanctumToken = exchangeResult.auth_token || exchangeResult.token;
1571
+ localStorage.setItem(STORAGE.AUTH_TOKEN, sanctumToken);
1572
+ localStorage.setItem(STORAGE.TOKEN, sanctumToken);
1573
+ const userToStore = exchangeResult.user_infos ? { ...exchangeResult.user, ...exchangeResult.user_infos } : exchangeResult.user;
1574
+ const acctType = accountType === "phone-only" ? "client" : "user";
1575
+ localStorage.setItem(STORAGE.USER, JSON.stringify(userToStore));
1576
+ localStorage.setItem(STORAGE.ACCOUNT_TYPE, acctType);
1577
+ return { token: sanctumToken, user: userToStore };
1578
+ }
1579
+ function clearSession() {
1580
+ localStorage.removeItem(STORAGE.AUTH_TOKEN);
1581
+ localStorage.removeItem(STORAGE.TOKEN);
1582
+ localStorage.removeItem(STORAGE.USER);
1583
+ localStorage.removeItem(STORAGE.ACCOUNT_TYPE);
1584
+ }
1585
+ function getErrorMessage$1(err, context) {
1586
+ if (err instanceof Error) {
1587
+ if (err.message.includes("fetch") || err.message.includes("network")) {
1588
+ return { message: "Vérifiez votre connexion Internet", type: "network" };
1589
+ }
1590
+ if (err.message.includes("timeout")) {
1591
+ return { message: "Le serveur met trop de temps à répondre", type: "timeout" };
1592
+ }
1593
+ return { message: err.message, type: "unknown" };
1594
+ }
1595
+ return { message: `Erreur lors de ${context}`, type: "unknown" };
1596
+ }
1597
+ function useNativeAuth(options) {
1598
+ const { saasApiUrl, iamApiUrl, autoLoadCredentials = true, debug = false } = options;
1599
+ const configuredRef = useRef(false);
1600
+ useEffect(() => {
1601
+ if (!configuredRef.current) {
1602
+ setNativeAuthConfig({ saasApiUrl, iamApiUrl, debug });
1603
+ configuredRef.current = true;
1604
+ }
1605
+ }, [saasApiUrl, iamApiUrl, debug]);
1606
+ const [state, setState] = useState({
1607
+ credentialsLoaded: false,
1608
+ processToken: null,
1609
+ status: null,
1610
+ user: null,
1611
+ application: null,
1612
+ prompt: null,
1613
+ alternativeMethod: null,
1614
+ conflict: null,
1615
+ loading: false,
1616
+ error: null,
1617
+ errorType: null,
1618
+ otpMethod: null,
1619
+ otpSentTo: null
1620
+ });
1621
+ const [accountType, setAccountType] = useState("email");
1622
+ const handleTokenInvalid = useCallback(() => {
1623
+ if (debug) console.log("🔐 [HealthCheck] Token invalide — déconnexion locale");
1624
+ clearSession();
1625
+ setState({
1626
+ credentialsLoaded: false,
1627
+ processToken: null,
1628
+ status: null,
1629
+ user: null,
1630
+ application: null,
1631
+ prompt: null,
1632
+ alternativeMethod: null,
1633
+ conflict: null,
1634
+ loading: false,
1635
+ error: null,
1636
+ errorType: null,
1637
+ otpMethod: null,
1638
+ otpSentTo: null
1639
+ });
1640
+ }, [debug]);
1641
+ const handleUserUpdated = useCallback((userInfos) => {
1642
+ const storedRaw = localStorage.getItem(STORAGE.USER);
1643
+ if (storedRaw) {
1644
+ try {
1645
+ const stored = JSON.parse(storedRaw);
1646
+ const merged = { ...stored, ...userInfos };
1647
+ localStorage.setItem(STORAGE.USER, JSON.stringify(merged));
1648
+ setState((prev) => ({ ...prev, user: merged }));
1649
+ } catch {
1650
+ }
1651
+ }
1652
+ }, []);
1653
+ useTokenHealthCheck({
1654
+ enabled: state.status === "completed" && state.user !== null,
1655
+ saasApiUrl,
1656
+ onTokenInvalid: handleTokenInvalid,
1657
+ onUserUpdated: handleUserUpdated,
1658
+ debug
1659
+ });
1660
+ useEffect(() => {
1661
+ const storedToken = localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
1662
+ const storedUser = localStorage.getItem(STORAGE.USER);
1663
+ if (storedToken && storedUser) {
1664
+ try {
1665
+ const user = JSON.parse(storedUser);
1666
+ setState((prev) => ({ ...prev, user, status: "completed" }));
1667
+ } catch {
1668
+ clearSession();
1669
+ }
1670
+ }
1671
+ }, []);
1672
+ useEffect(() => {
1673
+ if (autoLoadCredentials && !state.credentialsLoaded && !state.user) {
1674
+ loadCredentials();
1675
+ }
1676
+ }, [autoLoadCredentials, state.credentialsLoaded, state.user]);
1677
+ const loadCredentials = useCallback(async () => {
1678
+ setState((prev) => ({ ...prev, loading: true, error: null }));
1679
+ try {
1680
+ await nativeAuthService.loadCredentials();
1681
+ setState((prev) => ({ ...prev, credentialsLoaded: true, loading: false }));
1682
+ return { success: true };
1683
+ } catch (err) {
1684
+ const { message, type } = getErrorMessage$1(err, "le chargement");
1685
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1686
+ return { success: false, error_type: type };
1687
+ }
1688
+ }, []);
1689
+ const loginWithEmail = useCallback(async (email, acctType = "user") => {
1690
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1691
+ try {
1692
+ if (!nativeAuthService.hasCredentials()) {
1693
+ await nativeAuthService.loadCredentials();
1694
+ }
1695
+ const response = await nativeAuthService.loginEmail(email, acctType);
1696
+ if (response.success && (response.status === "needs_access" || response.needs_access)) {
1697
+ setState((prev) => ({
1698
+ ...prev,
1699
+ processToken: response.process_token || null,
1700
+ status: "needs_access",
1701
+ application: response.application || null,
1702
+ prompt: response.prompt || null,
1703
+ user: response.user ? {
1704
+ reference: `USR-${response.user.id}`,
1705
+ name: response.user.name,
1706
+ email: response.user.email
1707
+ } : null,
1708
+ loading: false
1709
+ }));
1710
+ return { success: true, status: "needs_access" };
1711
+ }
1712
+ if (response.success && response.process_token) {
1713
+ setState((prev) => ({
1714
+ ...prev,
1715
+ processToken: response.process_token,
1716
+ status: response.status,
1717
+ application: response.application || null,
1718
+ prompt: response.prompt || null,
1719
+ user: response.user ? {
1720
+ reference: `USR-${response.user.id}`,
1721
+ name: response.user.name,
1722
+ email: response.user.email
1723
+ } : null,
1724
+ loading: false
1725
+ }));
1726
+ return { success: true, status: response.status };
1727
+ }
1728
+ if (response.error_type === "alias_disabled" && response.alternative_method) {
1729
+ setState((prev) => ({
1730
+ ...prev,
1731
+ alternativeMethod: {
1732
+ type: response.alternative_method,
1733
+ value: response.alternative_method === "email" ? response.alternative_email : response.alternative_phone
1734
+ },
1735
+ loading: false,
1736
+ error: "Cette méthode de connexion est désactivée",
1737
+ errorType: "alias_disabled"
1738
+ }));
1739
+ return { success: false, error_type: "alias_disabled" };
1740
+ }
1741
+ setState((prev) => ({
1742
+ ...prev,
1743
+ loading: false,
1744
+ error: response.message || "Erreur d'authentification",
1745
+ errorType: response.error_type || "unknown"
1746
+ }));
1747
+ return { success: false, error_type: response.error_type };
1748
+ } catch (err) {
1749
+ const { message, type } = getErrorMessage$1(err, "l'authentification");
1750
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1751
+ return { success: false, error_type: type };
1752
+ }
1753
+ }, []);
1754
+ const loginWithPhone = useCallback(async (ccphone, phone, acctType = "user") => {
1755
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1756
+ try {
1757
+ if (!nativeAuthService.hasCredentials()) {
1758
+ await nativeAuthService.loadCredentials();
1759
+ }
1760
+ const response = await nativeAuthService.loginPhone(ccphone, phone, acctType);
1761
+ if (response.success && (response.status === "needs_access" || response.needs_access)) {
1762
+ setState((prev) => ({
1763
+ ...prev,
1764
+ processToken: response.process_token || null,
1765
+ status: "needs_access",
1766
+ application: response.application || null,
1767
+ user: response.user ? {
1768
+ reference: `USR-${response.user.id}`,
1769
+ name: response.user.name,
1770
+ email: response.user.email
1771
+ } : null,
1772
+ loading: false
1773
+ }));
1774
+ return { success: true, status: "needs_access" };
1775
+ }
1776
+ if (response.success && response.process_token) {
1777
+ setState((prev) => ({
1778
+ ...prev,
1779
+ processToken: response.process_token,
1780
+ status: response.status,
1781
+ application: response.application || null,
1782
+ loading: false
1783
+ }));
1784
+ return { success: true, status: response.status };
1785
+ }
1786
+ setState((prev) => ({
1787
+ ...prev,
1788
+ loading: false,
1789
+ error: response.message || "Erreur d'authentification",
1790
+ errorType: response.error_type || "unknown"
1791
+ }));
1792
+ return { success: false, error_type: response.error_type };
1793
+ } catch (err) {
1794
+ const { message, type } = getErrorMessage$1(err, "l'authentification");
1795
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1796
+ return { success: false, error_type: type };
1797
+ }
1798
+ }, []);
1799
+ const loginWithAccessOtp = useCallback(async (otpCode) => {
1800
+ setState((prev) => ({ ...prev, loading: true, error: null }));
1801
+ try {
1802
+ if (!nativeAuthService.hasCredentials()) {
1803
+ await nativeAuthService.loadCredentials();
1804
+ }
1805
+ const response = await nativeAuthService.loginAccessOtp(otpCode);
1806
+ if (response.success && response.needs_access && response.process_token) {
1807
+ setState((prev) => ({
1808
+ ...prev,
1809
+ processToken: response.process_token,
1810
+ status: "needs_access",
1811
+ application: response.application || null,
1812
+ user: response.user ? {
1813
+ reference: `USR-${response.user.id}`,
1814
+ name: response.user.name,
1815
+ email: response.user.email
1816
+ } : null,
1817
+ loading: false
1818
+ }));
1819
+ return { success: true, status: "needs_access" };
1820
+ }
1821
+ if (response.success && response.callback_token) {
1822
+ setState((prev) => ({ ...prev, loading: false }));
1823
+ return { success: true, status: "completed", callback_token: response.callback_token };
1824
+ }
1825
+ if (response.success && response.process_token) {
1826
+ setState((prev) => ({
1827
+ ...prev,
1828
+ processToken: response.process_token,
1829
+ status: response.status,
1830
+ loading: false
1831
+ }));
1832
+ return { success: true, status: response.status };
1833
+ }
1834
+ setState((prev) => ({
1835
+ ...prev,
1836
+ loading: false,
1837
+ error: response.message || "Code invalide",
1838
+ errorType: response.error_type || "invalid_otp"
1839
+ }));
1840
+ return { success: false, error_type: response.error_type };
1841
+ } catch (err) {
1842
+ const { message, type } = getErrorMessage$1(err, "l'authentification");
1843
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1844
+ return { success: false, error_type: type };
1845
+ }
1846
+ }, []);
1847
+ const register = useCallback(async (data) => {
1848
+ setState((prev) => ({ ...prev, loading: true, error: null, errorType: null }));
1849
+ try {
1850
+ if (!nativeAuthService.hasCredentials()) {
1851
+ await nativeAuthService.loadCredentials();
1852
+ }
1853
+ const registrationType = accountType === "phone-only" ? "phone" : "email";
1854
+ const response = await nativeAuthService.register({
1855
+ ...data,
1856
+ email: registrationType === "phone" ? "" : data.email,
1857
+ registration_type: registrationType
1858
+ });
1859
+ if (response.success && response.process_token) {
1860
+ setState((prev) => ({
1861
+ ...prev,
1862
+ processToken: response.process_token,
1863
+ status: response.status,
1864
+ otpMethod: response.otp_method || null,
1865
+ otpSentTo: response.otp_sent_to || null,
1866
+ conflict: response.conflict ? {
1867
+ type: response.conflict.type,
1868
+ maskedIdentifier: response.conflict.masked_identifier,
1869
+ options: {
1870
+ canLogin: response.conflict.options.can_login,
1871
+ canRecoverByEmail: response.conflict.options.can_recover_by_email,
1872
+ canRecoverBySms: response.conflict.options.can_recover_by_sms,
1873
+ maskedEmail: response.conflict.options.masked_email,
1874
+ maskedPhone: response.conflict.options.masked_phone
1875
+ }
1876
+ } : null,
1877
+ loading: false
1878
+ }));
1879
+ return { success: true, status: response.status };
1880
+ }
1881
+ setState((prev) => ({
1882
+ ...prev,
1883
+ loading: false,
1884
+ error: response.message || "Erreur lors de l'inscription",
1885
+ errorType: response.error_type || "unknown"
1886
+ }));
1887
+ return { success: false, error_type: response.error_type };
1888
+ } catch (err) {
1889
+ const { message, type } = getErrorMessage$1(err, "l'inscription");
1890
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1891
+ return { success: false, error_type: type };
1892
+ }
1893
+ }, [accountType]);
1894
+ const submitPassword = useCallback(async (password) => {
1895
+ if (!state.processToken) {
1896
+ return { success: false, error: "Session invalide" };
1897
+ }
1898
+ setState((prev) => ({ ...prev, loading: true, error: null }));
1899
+ try {
1900
+ const response = await nativeAuthService.validate(state.processToken, { password });
1901
+ if (response.success && response.callback_token) {
1902
+ const exchangeResult = await nativeAuthService.exchange(response.callback_token);
1903
+ if (exchangeResult.success) {
1904
+ const { user: savedUser } = saveSession(exchangeResult, accountType);
1905
+ setState((prev) => ({
1906
+ ...prev,
1907
+ status: "completed",
1908
+ user: savedUser,
1909
+ processToken: null,
1910
+ loading: false
1911
+ }));
1912
+ return { success: true, user: savedUser, callback_token: response.callback_token };
1913
+ }
1914
+ setState((prev) => ({
1915
+ ...prev,
1916
+ loading: false,
1917
+ error: exchangeResult.message || "Erreur lors de l'échange",
1918
+ errorType: "exchange_failed"
1919
+ }));
1920
+ return { success: false, error_type: "exchange_failed" };
1921
+ }
1922
+ if (response.requires_2fa) {
1923
+ setState((prev) => ({ ...prev, status: "pending_2fa", loading: false }));
1924
+ return { success: true, requires_2fa: true };
1925
+ }
1926
+ if (response.success && (response.status === "needs_access" || response.needs_access)) {
1927
+ setState((prev) => ({
1928
+ ...prev,
1929
+ status: "needs_access",
1930
+ processToken: response.process_token || prev.processToken,
1931
+ application: response.application || prev.application,
1932
+ user: response.user ? {
1933
+ reference: `USR-${response.user.id}`,
1934
+ name: response.user.name,
1935
+ email: response.user.email
1936
+ } : prev.user,
1937
+ prompt: response.prompt || prev.prompt,
1938
+ loading: false
1939
+ }));
1940
+ return { success: true, status: "needs_access" };
1941
+ }
1942
+ setState((prev) => ({
1943
+ ...prev,
1944
+ loading: false,
1945
+ error: response.message || "Mot de passe incorrect",
1946
+ errorType: response.error_type || "invalid_password"
1947
+ }));
1948
+ return { success: false, error_type: response.error_type };
1949
+ } catch (err) {
1950
+ const { message, type } = getErrorMessage$1(err, "la validation");
1951
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
1952
+ return { success: false, error_type: type };
1953
+ }
1954
+ }, [state.processToken]);
1955
+ const submitOtp = useCallback(async (otpCode) => {
1956
+ if (!state.processToken) {
1957
+ return { success: false, error: "Session invalide" };
1958
+ }
1959
+ setState((prev) => ({ ...prev, loading: true, error: null }));
1960
+ try {
1961
+ const response = await nativeAuthService.validate(state.processToken, { otp_code: otpCode });
1962
+ if (response.success && response.callback_token) {
1963
+ const exchangeResult = await nativeAuthService.exchange(response.callback_token);
1964
+ if (exchangeResult.success) {
1965
+ const { user: savedUser } = saveSession(exchangeResult, accountType);
1966
+ setState((prev) => ({
1967
+ ...prev,
1968
+ status: "completed",
1969
+ user: savedUser,
1970
+ processToken: null,
1971
+ loading: false
1972
+ }));
1973
+ return { success: true, user: savedUser, callback_token: response.callback_token };
1974
+ }
1975
+ setState((prev) => ({
1976
+ ...prev,
1977
+ loading: false,
1978
+ error: exchangeResult.message || "Erreur lors de l'échange",
1979
+ errorType: "exchange_failed"
1980
+ }));
1981
+ return { success: false, error_type: "exchange_failed" };
1982
+ }
1983
+ if (response.success && response.status === "pending_registration") {
1984
+ setState((prev) => ({ ...prev, status: "pending_registration", loading: false }));
1985
+ return { success: true, status: "pending_registration" };
1986
+ }
1987
+ if (response.success && (response.status === "needs_access" || response.needs_access)) {
1988
+ setState((prev) => ({
1989
+ ...prev,
1990
+ status: "needs_access",
1991
+ processToken: response.process_token || prev.processToken,
1992
+ application: response.application || prev.application,
1993
+ user: response.user ? {
1994
+ reference: `USR-${response.user.id}`,
1995
+ name: response.user.name,
1996
+ email: response.user.email
1997
+ } : prev.user,
1998
+ prompt: response.prompt || prev.prompt,
1999
+ loading: false
2000
+ }));
2001
+ return { success: true, status: "needs_access" };
2002
+ }
2003
+ setState((prev) => ({
2004
+ ...prev,
2005
+ loading: false,
2006
+ error: response.message || "Code incorrect",
2007
+ errorType: response.error_type || "invalid_otp"
2008
+ }));
2009
+ return { success: false, error_type: response.error_type };
2010
+ } catch (err) {
2011
+ const { message, type } = getErrorMessage$1(err, "la vérification");
2012
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
2013
+ return { success: false, error_type: type };
2014
+ }
2015
+ }, [state.processToken]);
2016
+ const submitTotp = useCallback(async (totpCode) => {
2017
+ if (!state.processToken) {
2018
+ return { success: false, error: "Session invalide" };
2019
+ }
2020
+ setState((prev) => ({ ...prev, loading: true, error: null }));
2021
+ try {
2022
+ const response = await nativeAuthService.verifyTotp(state.processToken, totpCode);
2023
+ if (response.success && response.callback_token) {
2024
+ const exchangeResult = await nativeAuthService.exchange(response.callback_token);
2025
+ if (exchangeResult.success) {
2026
+ const { user: savedUser } = saveSession(exchangeResult, accountType);
2027
+ setState((prev) => ({
2028
+ ...prev,
2029
+ status: "completed",
2030
+ user: savedUser,
2031
+ processToken: null,
2032
+ loading: false
2033
+ }));
2034
+ return { success: true, user: savedUser };
2035
+ }
2036
+ }
2037
+ setState((prev) => ({
2038
+ ...prev,
2039
+ loading: false,
2040
+ error: response.message || "Code 2FA incorrect",
2041
+ errorType: response.error_type || "invalid_totp"
2042
+ }));
2043
+ return { success: false, error_type: response.error_type };
2044
+ } catch (err) {
2045
+ const { message, type } = getErrorMessage$1(err, "la vérification 2FA");
2046
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
2047
+ return { success: false, error_type: type };
2048
+ }
2049
+ }, [state.processToken]);
2050
+ const grantAccess = useCallback(async () => {
2051
+ if (!state.processToken) {
2052
+ return { success: false, error: "Session invalide" };
2053
+ }
2054
+ setState((prev) => ({ ...prev, loading: true, error: null }));
2055
+ try {
2056
+ const response = await nativeAuthService.grantAccess(state.processToken);
2057
+ if (response.success && response.callback_token) {
2058
+ const exchangeResult = await nativeAuthService.exchange(response.callback_token);
2059
+ if (exchangeResult.success) {
2060
+ const { user: savedUser } = saveSession(exchangeResult, accountType);
2061
+ setState((prev) => ({
2062
+ ...prev,
2063
+ status: "completed",
2064
+ user: savedUser,
2065
+ processToken: null,
2066
+ loading: false
2067
+ }));
2068
+ return { success: true, user: savedUser, callback_token: response.callback_token };
2069
+ }
2070
+ }
2071
+ setState((prev) => ({
2072
+ ...prev,
2073
+ loading: false,
2074
+ error: response.message || "Erreur lors de l'accès",
2075
+ errorType: response.error_type || "unknown"
2076
+ }));
2077
+ return { success: false };
2078
+ } catch (err) {
2079
+ const { message, type } = getErrorMessage$1(err, "l'accès");
2080
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
2081
+ return { success: false, error_type: type };
2082
+ }
2083
+ }, [state.processToken]);
2084
+ const resendOtp = useCallback(async () => {
2085
+ if (!state.processToken) {
2086
+ return { success: false, error: "Session invalide" };
2087
+ }
2088
+ setState((prev) => ({ ...prev, loading: true, error: null }));
2089
+ try {
2090
+ const response = await nativeAuthService.resendOtp(state.processToken);
2091
+ setState((prev) => ({ ...prev, loading: false }));
2092
+ return {
2093
+ success: response.success,
2094
+ cooldown: response.cooldown_remaining,
2095
+ message: response.message,
2096
+ otp_code_dev: response.otp_code_dev
2097
+ };
2098
+ } catch (err) {
2099
+ const { message, type } = getErrorMessage$1(err, "le renvoi");
2100
+ setState((prev) => ({ ...prev, loading: false, error: message, errorType: type }));
2101
+ return { success: false };
2102
+ }
2103
+ }, [state.processToken]);
2104
+ const setSession = useCallback((data) => {
2105
+ const { user: savedUser } = saveSession(data, accountType);
2106
+ setState((prev) => ({
2107
+ ...prev,
2108
+ user: savedUser,
2109
+ status: "completed",
2110
+ processToken: null
2111
+ }));
2112
+ }, [accountType]);
2113
+ const logout = useCallback(async () => {
2114
+ const token = localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
2115
+ try {
2116
+ if (token) {
2117
+ await nativeAuthService.logout(token);
2118
+ }
2119
+ } catch {
2120
+ }
2121
+ clearSession();
2122
+ setState({
2123
+ credentialsLoaded: false,
2124
+ processToken: null,
2125
+ status: null,
2126
+ user: null,
2127
+ application: null,
2128
+ prompt: null,
2129
+ alternativeMethod: null,
2130
+ conflict: null,
2131
+ loading: false,
2132
+ error: null,
2133
+ errorType: null,
2134
+ otpMethod: null,
2135
+ otpSentTo: null
2136
+ });
2137
+ }, []);
2138
+ const reset = useCallback(() => {
2139
+ setState((prev) => ({
2140
+ ...prev,
2141
+ processToken: null,
2142
+ status: null,
2143
+ application: null,
2144
+ prompt: null,
2145
+ alternativeMethod: null,
2146
+ conflict: null,
2147
+ error: null,
2148
+ errorType: null
2149
+ }));
2150
+ }, []);
2151
+ const clearError = useCallback(() => {
2152
+ setState((prev) => ({ ...prev, error: null, errorType: null }));
2153
+ }, []);
2154
+ return {
2155
+ // State
2156
+ credentialsLoaded: state.credentialsLoaded,
2157
+ processToken: state.processToken,
2158
+ status: state.status,
2159
+ user: state.user,
2160
+ application: state.application,
2161
+ prompt: state.prompt,
2162
+ alternativeMethod: state.alternativeMethod,
2163
+ conflict: state.conflict,
2164
+ loading: state.loading,
2165
+ error: state.error,
2166
+ errorType: state.errorType,
2167
+ isAuthenticated: state.status === "completed" && state.user !== null,
2168
+ accountType,
2169
+ isPhoneOnly: accountType === "phone-only",
2170
+ otpMethod: state.otpMethod,
2171
+ otpSentTo: state.otpSentTo,
2172
+ // Methods
2173
+ loadCredentials,
2174
+ loginWithEmail,
2175
+ loginWithPhone,
2176
+ loginWithAccessOtp,
2177
+ submitPassword,
2178
+ submitOtp,
2179
+ submitTotp,
2180
+ grantAccess,
2181
+ resendOtp,
2182
+ setSession,
2183
+ logout,
2184
+ reset,
2185
+ clearError,
2186
+ register,
2187
+ setAccountType
2188
+ };
2189
+ }
2190
+ const C$2 = {
2191
+ primary: "#002147",
2192
+ accent: "#e8430a",
2193
+ green: "#16a34a",
2194
+ greenBg: "#dcfce7",
2195
+ amber: "#f59e0b",
2196
+ amberBg: "#fef3c7",
2197
+ gray100: "#f3f4f6",
2198
+ gray200: "#e5e7eb",
2199
+ gray500: "#6b7280",
2200
+ gray700: "#374151",
2201
+ gray900: "#111827",
2202
+ red: "#dc2626",
2203
+ redBg: "#fef2f2",
2204
+ white: "#ffffff"
2205
+ };
2206
+ const iconCircle$1 = (bg) => ({
2207
+ margin: "0 auto",
2208
+ width: "3rem",
2209
+ height: "3rem",
2210
+ backgroundColor: bg,
2211
+ borderRadius: "50%",
2212
+ display: "flex",
2213
+ alignItems: "center",
2214
+ justifyContent: "center"
2215
+ });
2216
+ const backBtnStyle$1 = {
2217
+ position: "absolute",
2218
+ left: "1rem",
2219
+ top: "1rem",
2220
+ background: "none",
2221
+ border: "none",
2222
+ cursor: "pointer",
2223
+ padding: "0.5rem",
2224
+ borderRadius: "0.375rem",
2225
+ color: C$2.gray700,
2226
+ zIndex: 10
2227
+ };
2228
+ const linkStyle$1 = {
2229
+ color: C$2.primary,
2230
+ cursor: "pointer",
2231
+ background: "none",
2232
+ border: "none",
2233
+ fontWeight: 500,
2234
+ textDecoration: "underline",
2235
+ fontSize: "0.875rem"
2236
+ };
2237
+ const errorBoxStyle = {
2238
+ padding: "0.75rem",
2239
+ borderRadius: "0.375rem",
2240
+ backgroundColor: C$2.redBg,
2241
+ color: C$2.red,
2242
+ fontSize: "0.875rem"
2243
+ };
2244
+ const methodBtnStyle = {
2245
+ width: "100%",
2246
+ height: "3.5rem",
2247
+ justifyContent: "flex-start",
2248
+ gap: "0.75rem",
2249
+ textAlign: "left",
2250
+ backgroundColor: C$2.gray100,
2251
+ borderColor: C$2.gray200
2252
+ };
2253
+ const methodIconStyle = (bg) => ({
2254
+ width: "2.5rem",
2255
+ height: "2.5rem",
2256
+ backgroundColor: bg,
2257
+ borderRadius: "0.5rem",
2258
+ display: "flex",
2259
+ alignItems: "center",
2260
+ justifyContent: "center",
2261
+ flexShrink: 0
2262
+ });
2263
+ function LoginModal({
2264
+ open,
2265
+ onOpenChange,
2266
+ onSwitchToSignup,
2267
+ onLoginSuccess,
2268
+ saasApiUrl,
2269
+ iamApiUrl,
2270
+ loading,
2271
+ showSwitchToSignup = true,
2272
+ debug = false
2273
+ }) {
2274
+ const {
2275
+ status,
2276
+ user,
2277
+ application,
2278
+ alternativeMethod,
2279
+ loading: authLoading,
2280
+ error: authError,
2281
+ loginWithEmail,
2282
+ loginWithPhone,
2283
+ submitPassword,
2284
+ submitOtp,
2285
+ grantAccess,
2286
+ loginWithAccessOtp,
2287
+ resendOtp,
2288
+ setSession,
2289
+ reset: resetAuth,
2290
+ clearError
2291
+ } = useNativeAuth({ saasApiUrl, iamApiUrl, debug });
2292
+ const [step, setStep] = useState("choice");
2293
+ const [email, setEmail] = useState("");
2294
+ const [password, setPassword] = useState("");
2295
+ const [showPassword, setShowPassword] = useState(false);
2296
+ const [userName, setUserName] = useState(null);
2297
+ const [phone, setPhone] = useState("");
2298
+ const [phoneOtpCode, setPhoneOtpCode] = useState("");
2299
+ const [emailOtpCode, setEmailOtpCode] = useState("");
2300
+ const [accessOtpCode, setAccessOtpCode] = useState("");
2301
+ const [localError, setLocalError] = useState(null);
2302
+ const [resendCooldown, setResendCooldown] = useState(0);
2303
+ const [loginSuccess, setLoginSuccess] = useState(false);
2304
+ const [loginData, setLoginData] = useState(null);
2305
+ const [showPasswordRecovery, setShowPasswordRecovery] = useState(false);
2306
+ const CCPHONE = "+221";
2307
+ const isSubmitting = authLoading || loading;
2308
+ const error = localError || authError;
2309
+ useEffect(() => {
2310
+ if (resendCooldown > 0) {
2311
+ const timer = setTimeout(() => setResendCooldown((p) => p - 1), 1e3);
2312
+ return () => clearTimeout(timer);
2313
+ }
2314
+ }, [resendCooldown]);
2315
+ useEffect(() => {
2316
+ if (status === "pending_password") setStep("email-password");
2317
+ else if (status === "pending_otp") {
2318
+ if (step === "email-check" || step === "email-password") setStep("email-otp");
2319
+ else if (step === "phone-input") setStep("phone-otp");
2320
+ setResendCooldown(60);
2321
+ }
2322
+ }, [status, step]);
2323
+ useEffect(() => {
2324
+ if (loginSuccess && loginData) {
2325
+ const timer = setTimeout(() => {
2326
+ setSession({ success: true, token: loginData.token, expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString(), user: loginData.user });
2327
+ onLoginSuccess == null ? void 0 : onLoginSuccess(loginData.token, loginData.user);
2328
+ }, 1e3);
2329
+ return () => clearTimeout(timer);
2330
+ }
2331
+ }, [loginSuccess, loginData]);
2332
+ const resetState = () => {
2333
+ setStep("choice");
2334
+ setEmail("");
2335
+ setPassword("");
2336
+ setShowPassword(false);
2337
+ setUserName(null);
2338
+ setPhone("");
2339
+ setPhoneOtpCode("");
2340
+ setEmailOtpCode("");
2341
+ setAccessOtpCode("");
2342
+ setLocalError(null);
2343
+ setLoginSuccess(false);
2344
+ setLoginData(null);
2345
+ setResendCooldown(0);
2346
+ resetAuth();
2347
+ };
2348
+ useEffect(() => {
2349
+ if (!open) resetState();
2350
+ }, [open]);
2351
+ const exchangeCallbackToken = async (callbackToken) => {
2352
+ try {
2353
+ const response = await nativeAuthService.exchange(callbackToken);
2354
+ if (response.success && response.token) {
2355
+ setLoginData({ token: response.token, user: response.user });
2356
+ setLoginSuccess(true);
2357
+ } else {
2358
+ setLocalError("Erreur lors de la finalisation de la connexion");
2359
+ }
2360
+ } catch {
2361
+ setLocalError("Erreur de connexion au serveur");
2362
+ }
2363
+ };
2364
+ const handleEmailCheck = async () => {
2365
+ setLocalError(null);
2366
+ clearError();
2367
+ if (!email.trim()) {
2368
+ setLocalError("L'adresse email est requise");
2369
+ return;
2370
+ }
2371
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
2372
+ setLocalError("Adresse email invalide");
2373
+ return;
2374
+ }
2375
+ const result = await loginWithEmail(email.trim());
2376
+ if (result.success && (user == null ? void 0 : user.name)) setUserName(user.name);
2377
+ };
2378
+ const handleEmailPasswordSubmit = async (e) => {
2379
+ e.preventDefault();
2380
+ setLocalError(null);
2381
+ clearError();
2382
+ if (!password) {
2383
+ setLocalError("Le mot de passe est requis");
2384
+ return;
2385
+ }
2386
+ if (password.length < 8) {
2387
+ setLocalError("Le mot de passe doit contenir au moins 8 caractères");
2388
+ return;
2389
+ }
2390
+ const result = await submitPassword(password);
2391
+ if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2392
+ };
2393
+ const handleEmailOtpVerify = async () => {
2394
+ setLocalError(null);
2395
+ clearError();
2396
+ if (emailOtpCode.length !== 6) {
2397
+ setLocalError("Le code doit contenir 6 chiffres");
2398
+ return;
2399
+ }
2400
+ const result = await submitOtp(emailOtpCode);
2401
+ if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2402
+ };
2403
+ const handlePhoneInit = async () => {
2404
+ setLocalError(null);
2405
+ clearError();
2406
+ const cleanPhone = phone.replace(/\s/g, "");
2407
+ if (cleanPhone.length !== 9) {
2408
+ setLocalError("Le numéro doit contenir exactement 9 chiffres");
2409
+ return;
2410
+ }
2411
+ await loginWithPhone(CCPHONE, cleanPhone);
2412
+ };
2413
+ const handlePhoneVerify = async () => {
2414
+ setLocalError(null);
2415
+ clearError();
2416
+ if (phoneOtpCode.length !== 6) {
2417
+ setLocalError("Veuillez entrer le code à 6 chiffres");
2418
+ return;
2419
+ }
2420
+ const result = await submitOtp(phoneOtpCode);
2421
+ if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2422
+ };
2423
+ const handleAccessOtpSubmit = async () => {
2424
+ setLocalError(null);
2425
+ clearError();
2426
+ if (accessOtpCode.length !== 8) {
2427
+ setLocalError("Veuillez entrer le code à 8 chiffres");
2428
+ return;
2429
+ }
2430
+ const result = await loginWithAccessOtp(accessOtpCode);
2431
+ if (result.success) {
2432
+ if (result.callback_token) {
2433
+ await exchangeCallbackToken(result.callback_token);
2434
+ }
2435
+ }
2436
+ };
2437
+ const handleGrantAccess = async () => {
2438
+ setLocalError(null);
2439
+ clearError();
2440
+ const result = await grantAccess();
2441
+ if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
2442
+ };
2443
+ const handleResendOTP = async () => {
2444
+ if (resendCooldown > 0) return;
2445
+ const result = await resendOtp();
2446
+ if (result.success) setResendCooldown(result.cooldown || 60);
2447
+ };
2448
+ const goBack = () => {
2449
+ setLocalError(null);
2450
+ clearError();
2451
+ switch (step) {
2452
+ case "email-check":
2453
+ case "phone-input":
2454
+ case "access-otp":
2455
+ setStep("choice");
2456
+ resetAuth();
2457
+ break;
2458
+ case "email-password":
2459
+ case "email-otp":
2460
+ setStep("email-check");
2461
+ resetAuth();
2462
+ break;
2463
+ case "phone-otp":
2464
+ setStep("phone-input");
2465
+ resetAuth();
2466
+ break;
2467
+ default:
2468
+ setStep("choice");
2469
+ resetAuth();
2470
+ }
2471
+ };
2472
+ const renderError = () => error ? /* @__PURE__ */ jsx("div", { style: errorBoxStyle, children: error }) : null;
2473
+ const renderBackBtn = () => /* @__PURE__ */ jsx("button", { onClick: goBack, style: backBtnStyle$1, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) });
2474
+ const renderResendLink = () => /* @__PURE__ */ jsx("div", { style: { textAlign: "center", fontSize: "0.875rem" }, children: resendCooldown > 0 ? /* @__PURE__ */ jsxs("span", { style: { color: C$2.gray500 }, children: [
2475
+ "Renvoyer dans ",
2476
+ resendCooldown,
2477
+ "s"
2478
+ ] }) : /* @__PURE__ */ jsx("button", { type: "button", style: linkStyle$1, onClick: handleResendOTP, children: "Code non reçu ? Renvoyer" }) });
2479
+ const renderLoaderBtn = (text, loadingText, onClick, disabled, extraStyle) => /* @__PURE__ */ jsx(Button, { onClick, disabled: isSubmitting || disabled, style: { width: "100%", ...extraStyle }, children: isSubmitting ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
2480
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
2481
+ loadingText
2482
+ ] }) : text });
2483
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2484
+ /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { children: loginSuccess && loginData ? /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2485
+ /* @__PURE__ */ jsx("div", { style: { ...iconCircle$1(C$2.greenBg), width: "5rem", height: "5rem" }, children: /* @__PURE__ */ jsx(IconCheckCircle2, { style: { width: "3rem", height: "3rem", color: C$2.green } }) }),
2486
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Connexion réussie" }),
2487
+ /* @__PURE__ */ jsx(DialogDescription, { children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0.5rem", marginTop: "0.5rem" }, children: [
2488
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem", color: C$2.gray500 } }),
2489
+ "Redirection en cours..."
2490
+ ] }) })
2491
+ ] }) : status === "needs_access" && user && application ? /* @__PURE__ */ jsxs(Fragment, { children: [
2492
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2493
+ /* @__PURE__ */ jsx("div", { style: { ...iconCircle$1(C$2.primary + "1a"), width: "4rem", height: "4rem" }, children: application.logo ? /* @__PURE__ */ jsx("img", { src: application.logo, alt: application.name, style: { width: "2.5rem", height: "2.5rem", borderRadius: "0.5rem" } }) : /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "2rem", height: "2rem", color: C$2.primary } }) }),
2494
+ /* @__PURE__ */ jsxs(DialogTitle, { children: [
2495
+ "Bienvenue sur ",
2496
+ application.name,
2497
+ " !"
2498
+ ] }),
2499
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
2500
+ "Bonjour ",
2501
+ /* @__PURE__ */ jsx("strong", { style: { color: C$2.gray900 }, children: user.name }),
2502
+ ", vous n'avez pas encore de compte sur ",
2503
+ /* @__PURE__ */ jsx("strong", { style: { color: C$2.primary }, children: application.name }),
2504
+ "."
2505
+ ] })
2506
+ ] }),
2507
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem" }, children: [
2508
+ /* @__PURE__ */ jsxs("div", { style: { padding: "1rem", backgroundColor: C$2.gray100, borderRadius: "0.5rem", marginBottom: "1rem", fontSize: "0.875rem", color: C$2.gray500 }, children: [
2509
+ "Votre compte ",
2510
+ /* @__PURE__ */ jsx("strong", { style: { color: C$2.primary }, children: "iam.ollaid.com" }),
2511
+ " existe déjà. Cliquez sur Confirmer pour créer votre accès à ",
2512
+ /* @__PURE__ */ jsx("strong", { style: { color: C$2.gray900 }, children: application.name }),
2513
+ "."
2514
+ ] }),
2515
+ renderError(),
2516
+ /* @__PURE__ */ jsx(Button, { onClick: handleGrantAccess, disabled: isSubmitting, style: { width: "100%", marginTop: "0.75rem" }, children: isSubmitting ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
2517
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
2518
+ "Création en cours..."
2519
+ ] }) : "Confirmer la création de mon compte" }),
2520
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => {
2521
+ resetAuth();
2522
+ setStep("choice");
2523
+ }, disabled: isSubmitting, style: { width: "100%", marginTop: "0.5rem" }, children: "Annuler" })
2524
+ ] })
2525
+ ] }) : alternativeMethod ? /* @__PURE__ */ jsxs(Fragment, { children: [
2526
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2527
+ /* @__PURE__ */ jsx("div", { style: { ...iconCircle$1(C$2.amberBg), width: "4rem", height: "4rem" }, children: alternativeMethod.type === "email" ? /* @__PURE__ */ jsx(IconMail, { style: { width: "2rem", height: "2rem", color: C$2.amber } }) : /* @__PURE__ */ jsx(IconPhone, { style: { width: "2rem", height: "2rem", color: C$2.amber } }) }),
2528
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Moyen de connexion désactivé" }),
2529
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Vos identifiants sont corrects, mais vous avez désactivé ce moyen de connexion depuis votre compte IAM." })
2530
+ ] }),
2531
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem" }, children: [
2532
+ alternativeMethod.value && /* @__PURE__ */ jsxs("div", { style: { padding: "1rem", backgroundColor: C$2.gray100, borderRadius: "0.5rem", marginBottom: "1rem" }, children: [
2533
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: C$2.gray500, marginBottom: "0.75rem" }, children: "Vous pouvez vous connecter avec :" }),
2534
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "0.875rem", fontWeight: 500, color: C$2.gray900 }, children: [
2535
+ alternativeMethod.type === "phone" ? /* @__PURE__ */ jsx(IconPhone, { style: { width: "1rem", height: "1rem", color: C$2.primary } }) : /* @__PURE__ */ jsx(IconMail, { style: { width: "1rem", height: "1rem", color: C$2.primary } }),
2536
+ /* @__PURE__ */ jsx("span", { children: alternativeMethod.value })
2537
+ ] })
2538
+ ] }),
2539
+ /* @__PURE__ */ jsx(Button, { onClick: () => {
2540
+ if (alternativeMethod.type === "phone") setStep("phone-input");
2541
+ else setStep("email-check");
2542
+ resetAuth();
2543
+ }, style: { width: "100%" }, children: alternativeMethod.type === "phone" ? "Se connecter par téléphone" : "Se connecter par email" }),
2544
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => window.open("https://iam.ollaid.com", "_blank"), style: { width: "100%", marginTop: "0.5rem" }, children: "Réactiver depuis iam.ollaid.com" }),
2545
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), style: { width: "100%", marginTop: "0.5rem" }, children: "Annuler" })
2546
+ ] })
2547
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2548
+ step !== "choice" && renderBackBtn(),
2549
+ step === "choice" && /* @__PURE__ */ jsxs(Fragment, { children: [
2550
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2551
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.gray100), children: /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "1.5rem", height: "1.5rem", color: C$2.primary } }) }),
2552
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Connexion" }),
2553
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Choisissez votre méthode de connexion" })
2554
+ ] }),
2555
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
2556
+ renderError(),
2557
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => setStep("email-check"), style: methodBtnStyle, children: [
2558
+ /* @__PURE__ */ jsx("div", { style: methodIconStyle(C$2.accent), children: /* @__PURE__ */ jsx(IconMail, { style: { width: "1.25rem", height: "1.25rem", color: C$2.white } }) }),
2559
+ /* @__PURE__ */ jsxs("div", { children: [
2560
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: C$2.gray900 }, children: "Adresse email" }),
2561
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: C$2.gray500 }, children: "Connexion avec email" })
2562
+ ] })
2563
+ ] }),
2564
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => setStep("phone-input"), style: methodBtnStyle, children: [
2565
+ /* @__PURE__ */ jsx("div", { style: methodIconStyle(C$2.accent), children: /* @__PURE__ */ jsx(IconPhone, { style: { width: "1.25rem", height: "1.25rem", color: C$2.white } }) }),
2566
+ /* @__PURE__ */ jsxs("div", { children: [
2567
+ /* @__PURE__ */ jsxs("div", { style: { fontWeight: 500, color: C$2.gray900 }, children: [
2568
+ "Numéro de téléphone ",
2569
+ /* @__PURE__ */ jsx("span", { style: { color: C$2.gray500 }, children: "(Sénégal)" })
2570
+ ] }),
2571
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: C$2.gray500 }, children: "Connexion avec code SMS" })
2572
+ ] })
2573
+ ] }),
2574
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: () => setStep("access-otp"), style: methodBtnStyle, children: [
2575
+ /* @__PURE__ */ jsx("div", { style: methodIconStyle(C$2.primary), children: /* @__PURE__ */ jsx(IconKeyRound, { style: { width: "1.25rem", height: "1.25rem", color: C$2.white } }) }),
2576
+ /* @__PURE__ */ jsxs("div", { children: [
2577
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: C$2.gray900 }, children: "Accès OTP" }),
2578
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.75rem", color: C$2.gray500 }, children: "Disponible sur le backoffice IAM" })
2579
+ ] })
2580
+ ] }),
2581
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", fontSize: "0.875rem", paddingTop: "0.5rem" }, children: [
2582
+ /* @__PURE__ */ jsx("span", { style: { color: C$2.gray500 }, children: "Mot de passe oublié ? " }),
2583
+ /* @__PURE__ */ jsx("button", { type: "button", style: linkStyle$1, onClick: () => setShowPasswordRecovery(true), children: "Récupérer" })
2584
+ ] }),
2585
+ /* @__PURE__ */ jsx("div", { style: { position: "relative", padding: "0.5rem 0" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx("span", { style: { width: "100%", borderTop: `1px solid ${C$2.gray200}` } }) }) }),
2586
+ showSwitchToSignup && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", fontSize: "0.875rem" }, children: [
2587
+ /* @__PURE__ */ jsx("span", { style: { color: C$2.gray500 }, children: "Pas encore de compte ? " }),
2588
+ /* @__PURE__ */ jsx("button", { type: "button", style: linkStyle$1, onClick: onSwitchToSignup, children: "Inscrivez-vous" })
2589
+ ] })
2590
+ ] })
2591
+ ] }),
2592
+ step === "email-check" && /* @__PURE__ */ jsxs(Fragment, { children: [
2593
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2594
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.accent), children: /* @__PURE__ */ jsx(IconMail, { style: { width: "1.5rem", height: "1.5rem", color: C$2.white } }) }),
2595
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Connexion par email" }),
2596
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Entrez votre adresse email" })
2597
+ ] }),
2598
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
2599
+ renderError(),
2600
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
2601
+ /* @__PURE__ */ jsx(Label, { htmlFor: "email", children: "Adresse email" }),
2602
+ /* @__PURE__ */ jsx(
2603
+ Input,
2604
+ {
2605
+ id: "email",
2606
+ type: "email",
2607
+ placeholder: "vous@exemple.com",
2608
+ value: email,
2609
+ onChange: (e) => setEmail(e.target.value),
2610
+ disabled: isSubmitting,
2611
+ onKeyDown: (e) => {
2612
+ if (e.key === "Enter") {
2613
+ e.preventDefault();
2614
+ handleEmailCheck();
2615
+ }
2616
+ }
2617
+ }
2618
+ )
2619
+ ] }),
2620
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "right" }, children: /* @__PURE__ */ jsx("button", { type: "button", style: { ...linkStyle$1, fontSize: "0.75rem" }, onClick: () => setShowPasswordRecovery(true), children: "Mot de passe oublié ?" }) }),
2621
+ renderLoaderBtn("Continuer", "Vérification...", handleEmailCheck, !email.trim()),
2622
+ showSwitchToSignup && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", fontSize: "0.875rem" }, children: [
2623
+ /* @__PURE__ */ jsx("span", { style: { color: C$2.gray500 }, children: "Pas encore de compte ? " }),
2624
+ /* @__PURE__ */ jsx("button", { type: "button", style: linkStyle$1, onClick: onSwitchToSignup, children: "Inscrivez-vous" })
2625
+ ] })
2626
+ ] })
2627
+ ] }),
2628
+ step === "email-password" && /* @__PURE__ */ jsxs(Fragment, { children: [
2629
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2630
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.accent), children: /* @__PURE__ */ jsx(IconLock, { style: { width: "1.5rem", height: "1.5rem", color: C$2.white } }) }),
2631
+ userName && /* @__PURE__ */ jsxs(DialogTitle, { children: [
2632
+ "Bonjour ",
2633
+ userName,
2634
+ " 👋"
2635
+ ] }),
2636
+ /* @__PURE__ */ jsx(DialogDescription, { children: email })
2637
+ ] }),
2638
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleEmailPasswordSubmit, style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
2639
+ renderError(),
2640
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
2641
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
2642
+ /* @__PURE__ */ jsx(Label, { htmlFor: "password", children: "Mot de passe" }),
2643
+ /* @__PURE__ */ jsx("button", { type: "button", style: { ...linkStyle$1, fontSize: "0.75rem" }, onClick: () => setShowPasswordRecovery(true), children: "Mot de passe oublié ?" })
2644
+ ] }),
2645
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
2646
+ /* @__PURE__ */ jsx(
2647
+ Input,
2648
+ {
2649
+ id: "password",
2650
+ type: showPassword ? "text" : "password",
2651
+ placeholder: "••••••••",
2652
+ value: password,
2653
+ onChange: (e) => setPassword(e.target.value),
2654
+ disabled: isSubmitting,
2655
+ style: { paddingRight: "2.5rem" }
2656
+ }
2657
+ ),
2658
+ /* @__PURE__ */ jsx(
2659
+ "button",
2660
+ {
2661
+ type: "button",
2662
+ onClick: () => setShowPassword(!showPassword),
2663
+ style: { position: "absolute", right: 0, top: 0, height: "100%", padding: "0 0.75rem", background: "none", border: "none", cursor: "pointer" },
2664
+ children: showPassword ? /* @__PURE__ */ jsx(IconEyeOff, { style: { width: "1rem", height: "1rem", color: C$2.gray500 } }) : /* @__PURE__ */ jsx(IconEye, { style: { width: "1rem", height: "1rem", color: C$2.gray500 } })
2665
+ }
2666
+ )
2667
+ ] })
2668
+ ] }),
2669
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSubmitting || !password, style: { width: "100%" }, children: isSubmitting ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
2670
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
2671
+ "Connexion..."
2672
+ ] }) : "Se connecter" })
2673
+ ] })
2674
+ ] }),
2675
+ step === "email-otp" && /* @__PURE__ */ jsxs(Fragment, { children: [
2676
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2677
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.accent), children: /* @__PURE__ */ jsx(IconKeyRound, { style: { width: "1.5rem", height: "1.5rem", color: C$2.white } }) }),
2678
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Vérification" }),
2679
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
2680
+ "Un code de vérification a été envoyé à",
2681
+ /* @__PURE__ */ jsx("br", {}),
2682
+ /* @__PURE__ */ jsx("strong", { style: { color: C$2.gray900 }, children: email })
2683
+ ] })
2684
+ ] }),
2685
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
2686
+ renderError(),
2687
+ /* @__PURE__ */ jsx(OTPInput, { value: emailOtpCode, onChange: setEmailOtpCode, disabled: isSubmitting }),
2688
+ renderResendLink(),
2689
+ renderLoaderBtn("Vérifier", "Vérification...", handleEmailOtpVerify, emailOtpCode.length !== 6)
2690
+ ] })
2691
+ ] }),
2692
+ step === "phone-input" && /* @__PURE__ */ jsxs(Fragment, { children: [
2693
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2694
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.accent), children: /* @__PURE__ */ jsx(IconPhone, { style: { width: "1.5rem", height: "1.5rem", color: C$2.white } }) }),
2695
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Connexion par téléphone" }),
2696
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Entrez votre numéro pour recevoir un code SMS" })
2697
+ ] }),
2698
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
2699
+ renderError(),
2700
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
2701
+ /* @__PURE__ */ jsx(Label, { children: "Indicatif" }),
2702
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", padding: "0.75rem", backgroundColor: C$2.gray100, borderRadius: "0.375rem" }, children: [
2703
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "1.125rem" }, children: "🇸🇳" }),
2704
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: C$2.gray900 }, children: "Sénégal (+221)" })
2705
+ ] })
2706
+ ] }),
2707
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
2708
+ /* @__PURE__ */ jsx(Label, { htmlFor: "login-phone", children: "Numéro de téléphone" }),
2709
+ /* @__PURE__ */ jsx(
2710
+ Input,
2711
+ {
2712
+ id: "login-phone",
2713
+ type: "tel",
2714
+ placeholder: "77 123 45 67",
2715
+ value: phone,
2716
+ onChange: (e) => {
2717
+ const val = e.target.value.replace(/\D/g, "").slice(0, 9);
2718
+ setPhone(val);
2719
+ },
2720
+ disabled: isSubmitting,
2721
+ maxLength: 9,
2722
+ onKeyDown: (e) => {
2723
+ if (e.key === "Enter") {
2724
+ e.preventDefault();
2725
+ handlePhoneInit();
2726
+ }
2727
+ }
2728
+ }
2729
+ ),
2730
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.75rem", color: C$2.gray500 }, children: [
2731
+ "9 chiffres requis (",
2732
+ phone.replace(/\D/g, "").length,
2733
+ "/9)"
2734
+ ] })
2735
+ ] }),
2736
+ renderLoaderBtn("Recevoir le code SMS", "Envoi en cours...", handlePhoneInit, phone.replace(/\D/g, "").length !== 9),
2737
+ showSwitchToSignup && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", fontSize: "0.875rem" }, children: [
2738
+ /* @__PURE__ */ jsx("span", { style: { color: C$2.gray500 }, children: "Pas encore de compte ? " }),
2739
+ /* @__PURE__ */ jsx("button", { type: "button", style: linkStyle$1, onClick: onSwitchToSignup, children: "Inscrivez-vous" })
2740
+ ] })
2741
+ ] })
2742
+ ] }),
2743
+ step === "phone-otp" && /* @__PURE__ */ jsxs(Fragment, { children: [
2744
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2745
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.accent), children: /* @__PURE__ */ jsx(IconPhone, { style: { width: "1.5rem", height: "1.5rem", color: C$2.white } }) }),
2746
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Vérification" }),
2747
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
2748
+ "Un code a été envoyé au",
2749
+ " ",
2750
+ /* @__PURE__ */ jsxs("strong", { style: { color: C$2.gray900 }, children: [
2751
+ "+221 ",
2752
+ phone.slice(0, 2),
2753
+ "***",
2754
+ phone.slice(-2)
2755
+ ] })
2756
+ ] })
2757
+ ] }),
2758
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
2759
+ renderError(),
2760
+ /* @__PURE__ */ jsx(OTPInput, { value: phoneOtpCode, onChange: setPhoneOtpCode, disabled: isSubmitting }),
2761
+ renderResendLink(),
2762
+ renderLoaderBtn("Se connecter", "Vérification...", handlePhoneVerify, phoneOtpCode.length !== 6)
2763
+ ] })
2764
+ ] }),
2765
+ step === "access-otp" && /* @__PURE__ */ jsxs(Fragment, { children: [
2766
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
2767
+ /* @__PURE__ */ jsx("div", { style: iconCircle$1(C$2.primary), children: /* @__PURE__ */ jsx(IconKeyRound, { style: { width: "1.5rem", height: "1.5rem", color: C$2.white } }) }),
2768
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Code d'accès" }),
2769
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Entrez le code d'accès à 8 chiffres" })
2770
+ ] }),
2771
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
2772
+ renderError(),
2773
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.25rem" }, children: [
2774
+ /* @__PURE__ */ jsx(Label, { children: "Code d'accès (8 chiffres)" }),
2775
+ /* @__PURE__ */ jsx(
2776
+ Input,
2777
+ {
2778
+ type: "text",
2779
+ inputMode: "numeric",
2780
+ placeholder: "0000-0000",
2781
+ value: accessOtpCode,
2782
+ onChange: (e) => setAccessOtpCode(e.target.value.replace(/\D/g, "").slice(0, 8)),
2783
+ disabled: isSubmitting,
2784
+ style: { textAlign: "center", fontSize: "1.5rem", letterSpacing: "0.15em", fontFamily: "monospace" },
2785
+ maxLength: 8
2786
+ }
2787
+ )
2788
+ ] }),
2789
+ renderLoaderBtn("Se connecter", "Vérification...", handleAccessOtpSubmit, accessOtpCode.length !== 8)
2790
+ ] })
2791
+ ] })
2792
+ ] }) }) }),
2793
+ /* @__PURE__ */ jsx(
2794
+ PasswordRecoveryModal,
2795
+ {
2796
+ open: showPasswordRecovery,
2797
+ onOpenChange: setShowPasswordRecovery,
2798
+ onSuccess: () => setShowPasswordRecovery(false),
2799
+ saasApiUrl,
2800
+ iamApiUrl,
2801
+ debug
2802
+ }
2803
+ )
2804
+ ] });
2805
+ }
2806
+ const SUPPORTED_COUNTRIES = [
2807
+ { code: "+221", name: "Sénégal", flag: "🇸🇳", digits: 9 },
2808
+ { code: "+229", name: "Bénin", flag: "🇧🇯", digits: 8 },
2809
+ { code: "+225", name: "Côte d'Ivoire", flag: "🇨🇮", digits: 10 },
2810
+ { code: "+228", name: "Togo", flag: "🇹🇬", digits: 8 },
2811
+ { code: "+237", name: "Cameroun", flag: "🇨🇲", digits: 9 },
2812
+ { code: "+33", name: "France", flag: "🇫🇷", digits: 9 }
2813
+ ];
2814
+ function PhoneInput({
2815
+ value,
2816
+ onChange,
2817
+ ccphone = "+221",
2818
+ onCcphoneChange,
2819
+ disabled = false,
2820
+ error,
2821
+ placeholder = "77 123 45 67",
2822
+ lockCcphone = false
2823
+ }) {
2824
+ const selectedCountry = SUPPORTED_COUNTRIES.find((c) => c.code === ccphone) || SUPPORTED_COUNTRIES[0];
2825
+ const handleChange = (e) => {
2826
+ const cleaned = e.target.value.replace(/\D/g, "");
2827
+ onChange(cleaned.slice(0, selectedCountry.digits));
2828
+ };
2829
+ const formatPhone = (phone) => {
2830
+ if (phone.length <= 2) return phone;
2831
+ if (phone.length <= 5) return `${phone.slice(0, 2)} ${phone.slice(2)}`;
2832
+ if (phone.length <= 7) return `${phone.slice(0, 2)} ${phone.slice(2, 5)} ${phone.slice(5)}`;
2833
+ return `${phone.slice(0, 2)} ${phone.slice(2, 5)} ${phone.slice(5, 7)} ${phone.slice(7)}`;
2834
+ };
2835
+ return /* @__PURE__ */ jsxs("div", { children: [
2836
+ /* @__PURE__ */ jsxs("div", { style: {
2837
+ display: "flex",
2838
+ alignItems: "center",
2839
+ border: `2px solid ${error ? "#ef4444" : "#d1d5db"}`,
2840
+ borderRadius: "0.5rem",
2841
+ overflow: "hidden",
2842
+ opacity: disabled ? 0.5 : 1,
2843
+ backgroundColor: disabled ? "#f3f4f6" : "white"
2844
+ }, children: [
2845
+ onCcphoneChange && !lockCcphone ? /* @__PURE__ */ jsx(
2846
+ "select",
2847
+ {
2848
+ value: ccphone,
2849
+ onChange: (e) => onCcphoneChange(e.target.value),
2850
+ disabled,
2851
+ style: { padding: "0.75rem", backgroundColor: "#f9fafb", borderRight: "1px solid #e5e7eb", fontWeight: 500, color: "#374151", outline: "none", border: "none" },
2852
+ children: SUPPORTED_COUNTRIES.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
2853
+ c.flag,
2854
+ " ",
2855
+ c.code
2856
+ ] }, c.code))
2857
+ }
2858
+ ) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", padding: "0.75rem", backgroundColor: "#f9fafb", borderRight: "1px solid #e5e7eb" }, children: [
2859
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "1.125rem" }, children: selectedCountry.flag }),
2860
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: "#374151" }, children: selectedCountry.code })
2861
+ ] }),
2862
+ /* @__PURE__ */ jsx(
2863
+ "input",
2864
+ {
2865
+ type: "tel",
2866
+ inputMode: "numeric",
2867
+ value: formatPhone(value),
2868
+ onChange: handleChange,
2869
+ disabled,
2870
+ placeholder,
2871
+ style: { flex: 1, padding: "0.75rem", fontSize: "1.125rem", border: "none", outline: "none", backgroundColor: "transparent" }
2872
+ }
2873
+ )
2874
+ ] }),
2875
+ error && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: "#ef4444", marginTop: "0.25rem" }, children: error }),
2876
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.75rem", color: "#6b7280", marginTop: "0.25rem" }, children: [
2877
+ selectedCountry.digits,
2878
+ " chiffres requis (",
2879
+ value.length,
2880
+ "/",
2881
+ selectedCountry.digits,
2882
+ ")"
2883
+ ] })
2884
+ ] });
2885
+ }
2886
+ function AppsLogoSlider({ speed = "normal", className = "" }) {
2887
+ const scrollRef = useRef(null);
2888
+ const [isPaused, setIsPaused] = useState(false);
2889
+ const [applications, setApplications] = useState([]);
2890
+ const [isLoading, setIsLoading] = useState(true);
2891
+ const speedMap = { slow: 50, normal: 30, fast: 15 };
2892
+ useEffect(() => {
2893
+ const fetchApps = async () => {
2894
+ try {
2895
+ const config2 = getNativeAuthConfig();
2896
+ const iamBaseUrl = config2.iamApiUrl.replace("/api", "");
2897
+ const response = await fetch(`${iamBaseUrl}/api/public/applications`, { headers: { "Accept": "application/json" } });
2898
+ if (response.ok) {
2899
+ const data = await response.json();
2900
+ if (data.success && Array.isArray(data.data)) setApplications(data.data);
2901
+ }
2902
+ } catch {
2903
+ } finally {
2904
+ setIsLoading(false);
2905
+ }
2906
+ };
2907
+ fetchApps();
2908
+ }, []);
2909
+ useEffect(() => {
2910
+ const container = scrollRef.current;
2911
+ if (!container || applications.length === 0) return;
2912
+ let pos = 0;
2913
+ const id = setInterval(() => {
2914
+ if (!isPaused && container) {
2915
+ pos += 1;
2916
+ if (pos >= container.scrollWidth / 2) pos = 0;
2917
+ container.scrollLeft = pos;
2918
+ }
2919
+ }, speedMap[speed]);
2920
+ return () => clearInterval(id);
2921
+ }, [isPaused, speed, applications.length]);
2922
+ const getLogoUrl = (logo) => {
2923
+ if (!logo) return null;
2924
+ if (logo.startsWith("http")) return logo;
2925
+ const config2 = getNativeAuthConfig();
2926
+ return `${config2.iamApiUrl.replace("/api", "")}/storage/applications/${logo}`;
2927
+ };
2928
+ if (isLoading) {
2929
+ return /* @__PURE__ */ jsx("div", { className, style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "1rem", padding: "0.5rem 0" }, children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0, display: "flex", flexDirection: "column", alignItems: "center", gap: "0.5rem" }, children: [
2930
+ /* @__PURE__ */ jsx("div", { style: { width: "3rem", height: "3rem", borderRadius: "0.75rem", backgroundColor: "#e5e7eb" } }),
2931
+ /* @__PURE__ */ jsx("div", { style: { width: "2.5rem", height: "0.75rem", borderRadius: "0.25rem", backgroundColor: "#e5e7eb" } })
2932
+ ] }, i)) }) });
2933
+ }
2934
+ if (applications.length === 0) return null;
2935
+ const displayApps = [...applications, ...applications];
2936
+ return /* @__PURE__ */ jsx(
2937
+ "div",
2938
+ {
2939
+ className,
2940
+ style: { position: "relative", overflow: "hidden" },
2941
+ onMouseEnter: () => setIsPaused(true),
2942
+ onMouseLeave: () => setIsPaused(false),
2943
+ children: /* @__PURE__ */ jsx("div", { ref: scrollRef, style: { display: "flex", gap: "1rem", overflow: "hidden", padding: "0.5rem 0" }, children: displayApps.map((app, index) => {
2944
+ const logoUrl = getLogoUrl(app.logo);
2945
+ return /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0, display: "flex", flexDirection: "column", alignItems: "center", gap: "0.5rem" }, children: [
2946
+ /* @__PURE__ */ jsx("div", { style: { width: "3rem", height: "3rem", borderRadius: "0.75rem", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 1px 3px rgba(0,0,0,0.1)", backgroundColor: "white", border: "1px solid #f3f4f6" }, children: logoUrl ? /* @__PURE__ */ jsx("img", { src: logoUrl, alt: app.name, style: { width: "2.5rem", height: "2.5rem", objectFit: "contain", borderRadius: "0.5rem" } }) : /* @__PURE__ */ jsx("span", { style: { fontWeight: 700, fontSize: "1.125rem", color: "hsl(var(--primary, 222 47% 11%))" }, children: app.name.charAt(0).toUpperCase() }) }),
2947
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.75rem", color: "#6b7280", fontWeight: 500 }, children: app.name })
2948
+ ] }, `${app.id}-${index}`);
2949
+ }) })
2950
+ }
2951
+ );
2952
+ }
2953
+ const mobileRegistrationService = {
2954
+ /**
2955
+ * Init registration via nativeAuth.register() (Frontend-First)
2956
+ */
2957
+ async init(data) {
2958
+ if (!nativeAuthService.hasCredentials()) {
2959
+ await nativeAuthService.loadCredentials();
2960
+ }
2961
+ return nativeAuthService.register({
2962
+ name: data.name,
2963
+ email: data.email,
2964
+ ccphone: data.ccphone,
2965
+ phone: data.phone,
2966
+ town: data.town,
2967
+ country: data.country,
2968
+ registration_type: data.registration_type
2969
+ });
2970
+ },
2971
+ /**
2972
+ * Verify OTP via nativeAuth.validate()
2973
+ */
2974
+ async verifyOtp(processToken, otpCode) {
2975
+ const config2 = getNativeAuthConfig();
2976
+ if (isDebugMode()) {
2977
+ console.log("📤 [IAM] POST /iam/native/validate (registration OTP)");
2978
+ }
2979
+ return fetchWithTimeout(
2980
+ `${config2.iamApiUrl}/iam/native/validate`,
2981
+ {
2982
+ method: "POST",
2983
+ headers: getHeaders(),
2984
+ body: JSON.stringify({
2985
+ process_token: processToken,
2986
+ otp_code: otpCode
2987
+ })
2988
+ },
2989
+ config2.timeout || 3e4
2990
+ );
2991
+ },
2992
+ /**
2993
+ * Complete registration with password
2994
+ */
2995
+ async complete(processToken, password) {
2996
+ const config2 = getNativeAuthConfig();
2997
+ if (isDebugMode()) {
2998
+ console.log("📤 [IAM] POST /iam/native/validate (registration complete)");
2999
+ }
3000
+ return fetchWithTimeout(
3001
+ `${config2.iamApiUrl}/iam/native/validate`,
3002
+ {
3003
+ method: "POST",
3004
+ headers: getHeaders(),
3005
+ body: JSON.stringify({
3006
+ process_token: processToken,
3007
+ password,
3008
+ password_confirmation: password
3009
+ })
3010
+ },
3011
+ config2.timeout || 3e4
3012
+ );
3013
+ },
3014
+ /**
3015
+ * Complete phone-only registration (no password)
3016
+ */
3017
+ async completePhoneOnly(processToken) {
3018
+ const config2 = getNativeAuthConfig();
3019
+ if (isDebugMode()) {
3020
+ console.log("📤 [IAM] POST /iam/native/validate (phone-only complete)");
3021
+ }
3022
+ return fetchWithTimeout(
3023
+ `${config2.iamApiUrl}/iam/native/validate`,
3024
+ {
3025
+ method: "POST",
3026
+ headers: getHeaders(),
3027
+ body: JSON.stringify({
3028
+ process_token: processToken
3029
+ })
3030
+ },
3031
+ config2.timeout || 3e4
3032
+ );
3033
+ },
3034
+ /**
3035
+ * Resend OTP
3036
+ */
3037
+ async resendOtp(processToken) {
3038
+ return nativeAuthService.resendOtp(processToken);
3039
+ }
3040
+ };
3041
+ function getErrorMessage(err, context) {
3042
+ if (err instanceof Error) {
3043
+ if (err.message.includes("fetch") || err.message.includes("network")) {
3044
+ return "Vérifiez votre connexion Internet";
3045
+ }
3046
+ if (err.message.includes("timeout")) {
3047
+ return "Le serveur met trop de temps à répondre";
3048
+ }
3049
+ return err.message;
3050
+ }
3051
+ return `Erreur lors de ${context}`;
3052
+ }
3053
+ function useMobileRegistration(options) {
3054
+ const configuredRef = useRef(false);
3055
+ useEffect(() => {
3056
+ if (options && !configuredRef.current) {
3057
+ setNativeAuthConfig({
3058
+ saasApiUrl: options.saasApiUrl,
3059
+ iamApiUrl: options.iamApiUrl,
3060
+ debug: options.debug
3061
+ });
3062
+ configuredRef.current = true;
3063
+ }
3064
+ }, [options]);
3065
+ const [state, setState] = useState({
3066
+ processToken: null,
3067
+ status: "idle",
3068
+ loading: false,
3069
+ error: null,
3070
+ conflict: null
3071
+ });
3072
+ const [formData, setFormData] = useState({});
3073
+ const [accountType, setAccountType] = useState("email");
3074
+ const updateFormData = useCallback((data) => {
3075
+ setFormData((prev) => ({ ...prev, ...data }));
3076
+ }, []);
3077
+ const initRegistration = useCallback(async (data) => {
3078
+ setState((prev) => ({ ...prev, loading: true, error: null, conflict: null }));
3079
+ const enrichedData = {
3080
+ ...data,
3081
+ email: accountType === "phone-only" ? "" : data.email,
3082
+ registration_type: accountType === "phone-only" ? "phone" : "email"
3083
+ };
3084
+ setFormData(enrichedData);
3085
+ try {
3086
+ if (!nativeAuthService.hasCredentials()) {
3087
+ await nativeAuthService.loadCredentials();
3088
+ }
3089
+ const response = await mobileRegistrationService.init(enrichedData);
3090
+ if (response.success && response.process_token) {
3091
+ setState((prev) => ({
3092
+ ...prev,
3093
+ processToken: response.process_token,
3094
+ status: "pending_otp",
3095
+ loading: false,
3096
+ conflict: null
3097
+ }));
3098
+ return {
3099
+ success: true,
3100
+ otp_code_dev: response.otp_code_dev,
3101
+ otp_method: response.otp_method,
3102
+ otp_sent_to: response.otp_sent_to
3103
+ };
3104
+ }
3105
+ if (response.conflict) {
3106
+ setState((prev) => ({
3107
+ ...prev,
3108
+ loading: false,
3109
+ error: response.message || "Ce compte existe déjà",
3110
+ conflict: response.conflict
3111
+ }));
3112
+ return { success: false, error_type: response.error_type };
3113
+ }
3114
+ setState((prev) => ({
3115
+ ...prev,
3116
+ loading: false,
3117
+ error: response.message || "Erreur lors de l'inscription",
3118
+ conflict: null
3119
+ }));
3120
+ return { success: false, error_type: response.error_type };
3121
+ } catch (err) {
3122
+ const message = getErrorMessage(err, "l'inscription");
3123
+ setState((prev) => ({ ...prev, loading: false, error: message, conflict: null }));
3124
+ return { success: false };
3125
+ }
3126
+ }, [accountType]);
3127
+ const verifyOtp = useCallback(async (otpCode) => {
3128
+ if (!state.processToken) {
3129
+ return { success: false, error: "Session invalide" };
3130
+ }
3131
+ setState((prev) => ({ ...prev, loading: true, error: null }));
3132
+ try {
3133
+ const response = await mobileRegistrationService.verifyOtp(state.processToken, otpCode);
3134
+ if (response.success) {
3135
+ if (response.status === "completed" && response.callback_token) {
3136
+ setState((prev) => ({ ...prev, status: "completed", loading: false }));
3137
+ return { success: true, completed: true, callback_token: response.callback_token };
3138
+ }
3139
+ setState((prev) => ({ ...prev, status: "pending_password", loading: false }));
3140
+ return { success: true, completed: false };
3141
+ }
3142
+ setState((prev) => ({
3143
+ ...prev,
3144
+ loading: false,
3145
+ error: response.message || "Code incorrect"
3146
+ }));
3147
+ return { success: false, error_type: response.error_type };
3148
+ } catch (err) {
3149
+ const message = getErrorMessage(err, "la vérification");
3150
+ setState((prev) => ({ ...prev, loading: false, error: message }));
3151
+ return { success: false };
3152
+ }
3153
+ }, [state.processToken]);
3154
+ const completeRegistration = useCallback(async (password) => {
3155
+ if (!state.processToken) {
3156
+ return { success: false, error: "Session invalide" };
3157
+ }
3158
+ setState((prev) => ({ ...prev, loading: true, error: null }));
3159
+ try {
3160
+ const response = await mobileRegistrationService.complete(state.processToken, password);
3161
+ if (response.success && response.callback_token) {
3162
+ setState((prev) => ({ ...prev, status: "completed", loading: false }));
3163
+ return { success: true, callback_token: response.callback_token };
3164
+ }
3165
+ setState((prev) => ({
3166
+ ...prev,
3167
+ loading: false,
3168
+ error: response.message || "Erreur lors de la finalisation"
3169
+ }));
3170
+ return { success: false, error_type: response.error_type };
3171
+ } catch (err) {
3172
+ const message = getErrorMessage(err, "la finalisation");
3173
+ setState((prev) => ({ ...prev, loading: false, error: message }));
3174
+ return { success: false };
3175
+ }
3176
+ }, [state.processToken]);
3177
+ const completePhoneOnlyRegistration = useCallback(async () => {
3178
+ if (!state.processToken) {
3179
+ return { success: false, error: "Session invalide" };
3180
+ }
3181
+ setState((prev) => ({ ...prev, loading: true, error: null }));
3182
+ try {
3183
+ const response = await mobileRegistrationService.completePhoneOnly(state.processToken);
3184
+ if (response.success && response.callback_token) {
3185
+ setState((prev) => ({ ...prev, status: "completed", loading: false }));
3186
+ return { success: true, callback_token: response.callback_token };
3187
+ }
3188
+ setState((prev) => ({
3189
+ ...prev,
3190
+ loading: false,
3191
+ error: response.message || "Erreur lors de la finalisation"
3192
+ }));
3193
+ return { success: false, error_type: response.error_type };
3194
+ } catch (err) {
3195
+ const message = getErrorMessage(err, "la finalisation");
3196
+ setState((prev) => ({ ...prev, loading: false, error: message }));
3197
+ return { success: false };
3198
+ }
3199
+ }, [state.processToken]);
3200
+ const resendOtp = useCallback(async () => {
3201
+ if (!state.processToken) {
3202
+ return { success: false, error: "Session invalide" };
3203
+ }
3204
+ setState((prev) => ({ ...prev, loading: true, error: null }));
3205
+ try {
3206
+ const response = await mobileRegistrationService.resendOtp(state.processToken);
3207
+ setState((prev) => ({ ...prev, loading: false }));
3208
+ return {
3209
+ success: response.success,
3210
+ cooldown: response.cooldown_remaining,
3211
+ otp_code_dev: response.otp_code_dev
3212
+ };
3213
+ } catch (err) {
3214
+ const message = getErrorMessage(err, "le renvoi");
3215
+ setState((prev) => ({ ...prev, loading: false, error: message }));
3216
+ return { success: false };
3217
+ }
3218
+ }, [state.processToken]);
3219
+ const reset = useCallback(() => {
3220
+ setState({
3221
+ processToken: null,
3222
+ status: "idle",
3223
+ loading: false,
3224
+ error: null,
3225
+ conflict: null
3226
+ });
3227
+ setFormData({});
3228
+ setAccountType("email");
3229
+ }, []);
3230
+ const clearError = useCallback(() => {
3231
+ setState((prev) => ({ ...prev, error: null, conflict: null }));
3232
+ }, []);
3233
+ return {
3234
+ processToken: state.processToken,
3235
+ status: state.status,
3236
+ formData,
3237
+ loading: state.loading,
3238
+ error: state.error,
3239
+ conflict: state.conflict,
3240
+ isCompleted: state.status === "completed",
3241
+ hasConflict: state.conflict !== null,
3242
+ accountType,
3243
+ setAccountType,
3244
+ isPhoneOnly: accountType === "phone-only",
3245
+ updateFormData,
3246
+ initRegistration,
3247
+ verifyOtp,
3248
+ completeRegistration,
3249
+ completePhoneOnlyRegistration,
3250
+ resendOtp,
3251
+ reset,
3252
+ clearError
3253
+ };
3254
+ }
3255
+ const C$1 = {
3256
+ primary: "#002147",
3257
+ accent: "#e8430a",
3258
+ green: "#16a34a",
3259
+ greenBg: "#dcfce7",
3260
+ gray100: "#f3f4f6",
3261
+ gray200: "#e5e7eb",
3262
+ gray500: "#6b7280",
3263
+ gray700: "#374151",
3264
+ gray900: "#111827",
3265
+ red: "#dc2626",
3266
+ redBg: "#fef2f2",
3267
+ amberBg: "#fef3c7",
3268
+ white: "#ffffff"
3269
+ };
3270
+ const iconCircle = (bg, size = "3rem") => ({
3271
+ margin: "0 auto",
3272
+ width: size,
3273
+ height: size,
3274
+ backgroundColor: bg,
3275
+ borderRadius: "50%",
3276
+ display: "flex",
3277
+ alignItems: "center",
3278
+ justifyContent: "center"
3279
+ });
3280
+ const backBtnStyle = {
3281
+ position: "absolute",
3282
+ left: "1rem",
3283
+ top: "1rem",
3284
+ background: "none",
3285
+ border: "none",
3286
+ cursor: "pointer",
3287
+ padding: "0.5rem",
3288
+ borderRadius: "0.375rem",
3289
+ color: C$1.gray700,
3290
+ zIndex: 10
3291
+ };
3292
+ const linkStyle = {
3293
+ color: C$1.primary,
3294
+ cursor: "pointer",
3295
+ background: "none",
3296
+ border: "none",
3297
+ fontWeight: 500,
3298
+ textDecoration: "underline",
3299
+ fontSize: "0.875rem"
3300
+ };
3301
+ const TOTAL_STEPS = 6;
3302
+ function StepIndicator({ current }) {
3303
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0.5rem", marginTop: "0.75rem" }, children: [
3304
+ Array.from({ length: TOTAL_STEPS }, (_, i) => /* @__PURE__ */ jsx("div", { style: {
3305
+ width: i + 1 === current ? "1.5rem" : "0.5rem",
3306
+ height: "0.5rem",
3307
+ borderRadius: "9999px",
3308
+ backgroundColor: i + 1 <= current ? C$1.accent : C$1.gray200,
3309
+ transition: "all 0.2s"
3310
+ } }, i)),
3311
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "0.75rem", color: C$1.gray500, marginLeft: "0.5rem" }, children: [
3312
+ "Étape ",
3313
+ current,
3314
+ "/",
3315
+ TOTAL_STEPS
3316
+ ] })
3317
+ ] });
3318
+ }
3319
+ function SuccessOrbit() {
3320
+ const orbitIcons = [IconHome, IconCalendar, IconBell, IconMessageCircle, IconUsers, IconSettings];
3321
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "10rem", height: "10rem", margin: "0 auto" }, children: [
3322
+ /* @__PURE__ */ jsx("div", { style: {
3323
+ position: "absolute",
3324
+ top: "50%",
3325
+ left: "50%",
3326
+ transform: "translate(-50%, -50%)",
3327
+ width: "4rem",
3328
+ height: "4rem",
3329
+ backgroundColor: C$1.greenBg,
3330
+ borderRadius: "50%",
3331
+ display: "flex",
3332
+ alignItems: "center",
3333
+ justifyContent: "center",
3334
+ zIndex: 2
3335
+ }, children: /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "2rem", height: "2rem", color: C$1.green } }) }),
3336
+ orbitIcons.map((Icon, i) => {
3337
+ const angle = i * 360 / orbitIcons.length;
3338
+ return /* @__PURE__ */ jsx("div", { style: {
3339
+ position: "absolute",
3340
+ top: "50%",
3341
+ left: "50%",
3342
+ width: "2rem",
3343
+ height: "2rem",
3344
+ transform: `rotate(${angle}deg) translate(4rem) rotate(-${angle}deg)`,
3345
+ transformOrigin: "0 0",
3346
+ animation: `spin ${8 + i}s linear infinite`
3347
+ }, children: /* @__PURE__ */ jsx("div", { style: {
3348
+ width: "2rem",
3349
+ height: "2rem",
3350
+ backgroundColor: C$1.gray100,
3351
+ borderRadius: "50%",
3352
+ display: "flex",
3353
+ alignItems: "center",
3354
+ justifyContent: "center"
3355
+ }, children: /* @__PURE__ */ jsx(Icon, { style: { width: "1rem", height: "1rem", color: C$1.gray500 } }) }) }, i);
3356
+ })
3357
+ ] });
3358
+ }
3359
+ function SignupModal({ open, onOpenChange, onSwitchToLogin, onSignupSuccess, saasApiUrl, iamApiUrl, debug = false }) {
3360
+ const {
3361
+ status,
3362
+ formData,
3363
+ loading: regLoading,
3364
+ error: regError,
3365
+ accountType,
3366
+ setAccountType,
3367
+ isPhoneOnly,
3368
+ updateFormData,
3369
+ initRegistration,
3370
+ verifyOtp,
3371
+ completeRegistration,
3372
+ resendOtp,
3373
+ reset: resetReg,
3374
+ clearError
3375
+ } = useMobileRegistration({ saasApiUrl, iamApiUrl, debug });
3376
+ const [step, setStep] = useState("intro");
3377
+ const [otpCode, setOtpCode] = useState("");
3378
+ const [password, setPassword] = useState("");
3379
+ const [passwordConfirm, setPasswordConfirm] = useState("");
3380
+ const [localError, setLocalError] = useState(null);
3381
+ const [resendCooldown, setResendCooldown] = useState(0);
3382
+ const [signupSuccess, setSignupSuccess] = useState(false);
3383
+ const [signupData, setSignupData] = useState(null);
3384
+ const error = localError || regError;
3385
+ useEffect(() => {
3386
+ if (!open) {
3387
+ setStep("intro");
3388
+ setOtpCode("");
3389
+ setPassword("");
3390
+ setPasswordConfirm("");
3391
+ setLocalError(null);
3392
+ setSignupSuccess(false);
3393
+ setSignupData(null);
3394
+ setResendCooldown(0);
3395
+ resetReg();
3396
+ }
3397
+ }, [open]);
3398
+ useEffect(() => {
3399
+ if (status === "pending_otp") {
3400
+ setStep("otp");
3401
+ setResendCooldown(60);
3402
+ } else if (status === "pending_password") setStep("password");
3403
+ }, [status]);
3404
+ useEffect(() => {
3405
+ if (resendCooldown > 0) {
3406
+ const t = setTimeout(() => setResendCooldown((r) => r - 1), 1e3);
3407
+ return () => clearTimeout(t);
3408
+ }
3409
+ }, [resendCooldown]);
3410
+ useEffect(() => {
3411
+ if (signupSuccess && signupData) {
3412
+ const t = setTimeout(() => onSignupSuccess(signupData.token, signupData.user), 2e3);
3413
+ return () => clearTimeout(t);
3414
+ }
3415
+ }, [signupSuccess, signupData]);
3416
+ const exchangeCallbackToken = async (callbackToken) => {
3417
+ try {
3418
+ const response = await nativeAuthService.exchange(callbackToken);
3419
+ if (response.success && response.token) {
3420
+ setSignupData({ token: response.token, user: response.user });
3421
+ setSignupSuccess(true);
3422
+ } else setLocalError("Erreur lors de la finalisation de l'inscription");
3423
+ } catch {
3424
+ setLocalError("Erreur de connexion au serveur");
3425
+ }
3426
+ };
3427
+ const handleInfoSubmit = async (e) => {
3428
+ var _a, _b, _c, _d, _e, _f;
3429
+ e.preventDefault();
3430
+ setLocalError(null);
3431
+ clearError();
3432
+ if (!((_a = formData.name) == null ? void 0 : _a.trim())) {
3433
+ setLocalError("Le nom est requis");
3434
+ return;
3435
+ }
3436
+ if (!isPhoneOnly && !((_b = formData.email) == null ? void 0 : _b.trim())) {
3437
+ setLocalError("L'adresse email est requise");
3438
+ return;
3439
+ }
3440
+ if (!((_c = formData.phone) == null ? void 0 : _c.trim()) || (((_d = formData.phone) == null ? void 0 : _d.length) || 0) < 6) {
3441
+ setLocalError("Numéro de téléphone invalide");
3442
+ return;
3443
+ }
3444
+ if (isPhoneOnly && formData.ccphone !== "+221") {
3445
+ setLocalError("L'inscription par téléphone est réservée aux numéros sénégalais (+221)");
3446
+ return;
3447
+ }
3448
+ if (!((_e = formData.town) == null ? void 0 : _e.trim())) {
3449
+ setLocalError("La ville est requise");
3450
+ return;
3451
+ }
3452
+ if (!((_f = formData.country) == null ? void 0 : _f.trim())) {
3453
+ setLocalError("Le pays est requis");
3454
+ return;
3455
+ }
3456
+ await initRegistration({ name: formData.name || "", email: formData.email || "", ccphone: formData.ccphone || "+221", phone: formData.phone || "", town: formData.town, country: formData.country });
3457
+ };
3458
+ const handleOTPSubmit = async () => {
3459
+ if (otpCode.length !== 6) {
3460
+ setLocalError("Veuillez entrer le code à 6 chiffres");
3461
+ return;
3462
+ }
3463
+ setLocalError(null);
3464
+ clearError();
3465
+ const result = await verifyOtp(otpCode);
3466
+ if (result.success && result.completed && result.callback_token) await exchangeCallbackToken(result.callback_token);
3467
+ };
3468
+ const handlePasswordSubmit = (e) => {
3469
+ e.preventDefault();
3470
+ setLocalError(null);
3471
+ if (!password || password.length < 8) {
3472
+ setLocalError("Le mot de passe doit contenir au moins 8 caractères");
3473
+ return;
3474
+ }
3475
+ if (password !== passwordConfirm) {
3476
+ setLocalError("Les mots de passe ne correspondent pas");
3477
+ return;
3478
+ }
3479
+ setStep("confirm");
3480
+ };
3481
+ const handleConfirm = async () => {
3482
+ setLocalError(null);
3483
+ clearError();
3484
+ const result = await completeRegistration(password);
3485
+ if (result.success && result.callback_token) await exchangeCallbackToken(result.callback_token);
3486
+ };
3487
+ const handleResendOTP = async () => {
3488
+ if (resendCooldown > 0) return;
3489
+ const r = await resendOtp();
3490
+ if (r.success) setResendCooldown(r.cooldown || 60);
3491
+ };
3492
+ const goToStep = (s) => {
3493
+ setLocalError(null);
3494
+ clearError();
3495
+ setStep(s);
3496
+ };
3497
+ const handleAccountTypeSelect = (type) => {
3498
+ setAccountType(type);
3499
+ if (type === "phone-only") updateFormData({ ccphone: "+221", country: "Sénégal", email: "" });
3500
+ goToStep("info");
3501
+ };
3502
+ const renderError = () => error ? /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.375rem", backgroundColor: C$1.redBg, color: C$1.red, fontSize: "0.875rem" }, children: error }) : null;
3503
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { children: signupSuccess && signupData ? /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3504
+ /* @__PURE__ */ jsx(SuccessOrbit, {}),
3505
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Félicitations !" }),
3506
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
3507
+ "Votre compte a été créé avec succès. Bienvenue ",
3508
+ formData.name,
3509
+ " !"
3510
+ ] }),
3511
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0.5rem", fontSize: "0.875rem", color: C$1.gray500, marginTop: "1rem" }, children: [
3512
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
3513
+ "Connexion automatique en cours..."
3514
+ ] })
3515
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3516
+ step === "intro" && /* @__PURE__ */ jsxs(Fragment, { children: [
3517
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3518
+ /* @__PURE__ */ jsx("div", { style: iconCircle(C$1.accent + "1a", "4rem"), children: /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "2rem", height: "2rem", color: C$1.accent } }) }),
3519
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Ouvrez un compte Ollaid" }),
3520
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Un compte unique qui vous donne accès à toutes les applications" })
3521
+ ] }),
3522
+ /* @__PURE__ */ jsx(AppsLogoSlider, {}),
3523
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", fontSize: "0.875rem", color: C$1.gray500, margin: "1rem 0" }, children: ["Un seul compte pour toutes les applications", "Plus besoin de multiples mots de passe", "Connexion simplifiée et sécurisée"].map((text) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem" }, children: [
3524
+ /* @__PURE__ */ jsx(IconCheckCircle2, { style: { width: "1.25rem", height: "1.25rem", color: C$1.green, flexShrink: 0 } }),
3525
+ text
3526
+ ] }, text)) }),
3527
+ /* @__PURE__ */ jsx(StepIndicator, { current: 1 }),
3528
+ /* @__PURE__ */ jsx(Button, { onClick: () => goToStep("account-type"), style: { width: "100%", marginTop: "1rem" }, children: "Suivant →" }),
3529
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", fontSize: "0.875rem", marginTop: "1rem" }, children: [
3530
+ /* @__PURE__ */ jsx("span", { style: { color: C$1.gray500 }, children: "Déjà un compte ? " }),
3531
+ /* @__PURE__ */ jsx("button", { type: "button", style: linkStyle, onClick: onSwitchToLogin, children: "Connectez-vous" })
3532
+ ] })
3533
+ ] }),
3534
+ step === "account-type" && /* @__PURE__ */ jsxs(Fragment, { children: [
3535
+ /* @__PURE__ */ jsx("button", { onClick: () => goToStep("intro"), style: backBtnStyle, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) }),
3536
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3537
+ /* @__PURE__ */ jsx("div", { style: iconCircle(C$1.gray100), children: /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "1.5rem", height: "1.5rem", color: C$1.primary } }) }),
3538
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Choisissez votre type de compte" })
3539
+ ] }),
3540
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", marginTop: "1rem" }, children: [
3541
+ /* @__PURE__ */ jsxs(
3542
+ "button",
3543
+ {
3544
+ type: "button",
3545
+ onClick: () => handleAccountTypeSelect("email"),
3546
+ style: { width: "100%", padding: "1rem", border: `2px solid ${C$1.gray200}`, borderRadius: "0.5rem", textAlign: "left", cursor: "pointer", background: C$1.white, display: "flex", alignItems: "center", gap: "0.75rem" },
3547
+ children: [
3548
+ /* @__PURE__ */ jsx("div", { style: { width: "2.5rem", height: "2.5rem", backgroundColor: C$1.accent, borderRadius: "0.5rem", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx(IconMail, { style: { width: "1.25rem", height: "1.25rem", color: C$1.white } }) }),
3549
+ /* @__PURE__ */ jsxs("div", { children: [
3550
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: C$1.gray900 }, children: "Email + Téléphone" }),
3551
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", color: C$1.gray500 }, children: "Compte complet" })
3552
+ ] })
3553
+ ]
3554
+ }
3555
+ ),
3556
+ /* @__PURE__ */ jsxs(
3557
+ "button",
3558
+ {
3559
+ type: "button",
3560
+ onClick: () => handleAccountTypeSelect("phone-only"),
3561
+ style: { width: "100%", padding: "1rem", border: `2px solid ${C$1.gray200}`, borderRadius: "0.5rem", textAlign: "left", cursor: "pointer", background: C$1.white, display: "flex", alignItems: "center", gap: "0.75rem" },
3562
+ children: [
3563
+ /* @__PURE__ */ jsx("div", { style: { width: "2.5rem", height: "2.5rem", backgroundColor: C$1.accent, borderRadius: "0.5rem", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx(IconSmartphone, { style: { width: "1.25rem", height: "1.25rem", color: C$1.white } }) }),
3564
+ /* @__PURE__ */ jsxs("div", { children: [
3565
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: C$1.gray900 }, children: "Téléphone uniquement" }),
3566
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", color: C$1.gray500 }, children: "🇸🇳 Sénégal uniquement" })
3567
+ ] })
3568
+ ]
3569
+ }
3570
+ )
3571
+ ] }),
3572
+ /* @__PURE__ */ jsx(StepIndicator, { current: 2 })
3573
+ ] }),
3574
+ step === "info" && /* @__PURE__ */ jsxs(Fragment, { children: [
3575
+ /* @__PURE__ */ jsx("button", { onClick: () => goToStep("account-type"), style: backBtnStyle, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) }),
3576
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3577
+ /* @__PURE__ */ jsx("div", { style: iconCircle(C$1.primary + "1a"), children: /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "1.5rem", height: "1.5rem", color: C$1.primary } }) }),
3578
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Créer votre compte" })
3579
+ ] }),
3580
+ isPhoneOnly && /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.375rem", backgroundColor: C$1.accent + "1a", color: C$1.accent, fontSize: "0.8125rem", marginTop: "0.5rem", textAlign: "center" }, children: "📱 Inscription par téléphone — 🇸🇳 Sénégal uniquement" }),
3581
+ !isPhoneOnly && /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.375rem", backgroundColor: C$1.amberBg, color: "#92400e", fontSize: "0.8125rem", marginTop: "0.5rem", textAlign: "center" }, children: "⚠️ Un code OTP sera envoyé par email pour vérification" }),
3582
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleInfoSubmit, style: { display: "flex", flexDirection: "column", gap: "0.75rem", marginTop: "0.75rem" }, children: [
3583
+ renderError(),
3584
+ /* @__PURE__ */ jsxs("div", { children: [
3585
+ /* @__PURE__ */ jsx(Label, { children: "Nom complet" }),
3586
+ /* @__PURE__ */ jsx(Input, { placeholder: "Jean Dupont", value: formData.name || "", onChange: (e) => updateFormData({ name: e.target.value }), disabled: regLoading })
3587
+ ] }),
3588
+ !isPhoneOnly && /* @__PURE__ */ jsxs("div", { children: [
3589
+ /* @__PURE__ */ jsx(Label, { children: "Adresse email" }),
3590
+ /* @__PURE__ */ jsx(Input, { type: "email", placeholder: "vous@exemple.com", value: formData.email || "", onChange: (e) => updateFormData({ email: e.target.value }), disabled: regLoading })
3591
+ ] }),
3592
+ /* @__PURE__ */ jsxs("div", { children: [
3593
+ /* @__PURE__ */ jsx(Label, { children: "Numéro de téléphone" }),
3594
+ /* @__PURE__ */ jsx(PhoneInput, { value: formData.phone || "", onChange: (p) => updateFormData({ phone: p }), ccphone: formData.ccphone || "+221", onCcphoneChange: (c) => updateFormData({ ccphone: c }), disabled: regLoading, lockCcphone: isPhoneOnly })
3595
+ ] }),
3596
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.75rem" }, children: [
3597
+ /* @__PURE__ */ jsxs("div", { children: [
3598
+ /* @__PURE__ */ jsx(Label, { children: "Ville" }),
3599
+ /* @__PURE__ */ jsx(Input, { placeholder: "Dakar", value: formData.town || "", onChange: (e) => updateFormData({ town: e.target.value }), disabled: regLoading })
3600
+ ] }),
3601
+ /* @__PURE__ */ jsxs("div", { children: [
3602
+ /* @__PURE__ */ jsx(Label, { children: "Pays" }),
3603
+ /* @__PURE__ */ jsx(Input, { placeholder: "Sénégal", value: formData.country || "", onChange: (e) => updateFormData({ country: e.target.value }), disabled: regLoading || isPhoneOnly })
3604
+ ] })
3605
+ ] }),
3606
+ /* @__PURE__ */ jsx(StepIndicator, { current: 3 }),
3607
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: regLoading, style: { width: "100%" }, children: regLoading ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
3608
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
3609
+ " Vérification..."
3610
+ ] }) : "Continuer" })
3611
+ ] })
3612
+ ] }),
3613
+ step === "otp" && /* @__PURE__ */ jsxs(Fragment, { children: [
3614
+ /* @__PURE__ */ jsx("button", { onClick: () => goToStep("info"), style: backBtnStyle, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) }),
3615
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3616
+ /* @__PURE__ */ jsx("div", { style: iconCircle(C$1.accent), children: /* @__PURE__ */ jsx(IconKeyRound, { style: { width: "1.5rem", height: "1.5rem", color: C$1.white } }) }),
3617
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Vérification" }),
3618
+ /* @__PURE__ */ jsxs(DialogDescription, { children: [
3619
+ "Entrez le code envoyé ",
3620
+ isPhoneOnly ? `au ${formData.ccphone} ${formData.phone}` : `à ${formData.email}`
3621
+ ] })
3622
+ ] }),
3623
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }, children: [
3624
+ renderError(),
3625
+ /* @__PURE__ */ jsx(OTPInput, { value: otpCode, onChange: setOtpCode, disabled: regLoading }),
3626
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center", fontSize: "0.875rem" }, children: resendCooldown > 0 ? /* @__PURE__ */ jsxs("span", { style: { color: C$1.gray500 }, children: [
3627
+ "Renvoyer dans ",
3628
+ resendCooldown,
3629
+ "s"
3630
+ ] }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: handleResendOTP, style: linkStyle, children: "Code non reçu ? Renvoyer" }) }),
3631
+ /* @__PURE__ */ jsx(StepIndicator, { current: 4 }),
3632
+ /* @__PURE__ */ jsx(Button, { onClick: handleOTPSubmit, disabled: regLoading || otpCode.length !== 6, style: { width: "100%" }, children: regLoading ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
3633
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
3634
+ " Vérification..."
3635
+ ] }) : "Vérifier" })
3636
+ ] })
3637
+ ] }),
3638
+ step === "password" && /* @__PURE__ */ jsxs(Fragment, { children: [
3639
+ /* @__PURE__ */ jsx("button", { onClick: () => goToStep("otp"), style: backBtnStyle, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) }),
3640
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3641
+ /* @__PURE__ */ jsx("div", { style: iconCircle(C$1.accent), children: /* @__PURE__ */ jsx(IconLock, { style: { width: "1.5rem", height: "1.5rem", color: C$1.white } }) }),
3642
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Créer un mot de passe" })
3643
+ ] }),
3644
+ /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, style: { display: "flex", flexDirection: "column", gap: "0.75rem", marginTop: "0.75rem" }, children: [
3645
+ renderError(),
3646
+ /* @__PURE__ */ jsxs("div", { children: [
3647
+ /* @__PURE__ */ jsx(Label, { children: "Mot de passe" }),
3648
+ /* @__PURE__ */ jsx(Input, { type: "password", placeholder: "Minimum 8 caractères", value: password, onChange: (e) => setPassword(e.target.value), disabled: regLoading })
3649
+ ] }),
3650
+ /* @__PURE__ */ jsxs("div", { children: [
3651
+ /* @__PURE__ */ jsx(Label, { children: "Confirmer le mot de passe" }),
3652
+ /* @__PURE__ */ jsx(Input, { type: "password", placeholder: "••••••••", value: passwordConfirm, onChange: (e) => setPasswordConfirm(e.target.value), disabled: regLoading })
3653
+ ] }),
3654
+ /* @__PURE__ */ jsx(StepIndicator, { current: 5 }),
3655
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: regLoading, style: { width: "100%" }, children: "Continuer" })
3656
+ ] })
3657
+ ] }),
3658
+ step === "confirm" && /* @__PURE__ */ jsxs(Fragment, { children: [
3659
+ /* @__PURE__ */ jsx("button", { onClick: () => goToStep("password"), style: backBtnStyle, type: "button", children: /* @__PURE__ */ jsx(IconArrowLeft, { style: { width: "1rem", height: "1rem" } }) }),
3660
+ /* @__PURE__ */ jsxs(DialogHeader, { style: { textAlign: "center" }, children: [
3661
+ /* @__PURE__ */ jsx("div", { style: iconCircle(C$1.primary + "1a"), children: /* @__PURE__ */ jsx(IconShieldCheck, { style: { width: "1.5rem", height: "1.5rem", color: C$1.primary } }) }),
3662
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Confirmer votre inscription" })
3663
+ ] }),
3664
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
3665
+ renderError(),
3666
+ /* @__PURE__ */ jsx("div", { style: { backgroundColor: C$1.gray100, borderRadius: "0.5rem", padding: "1rem" }, children: [["Nom", formData.name], ...!isPhoneOnly ? [["Email", formData.email]] : [], ["Téléphone", `${formData.ccphone} ${formData.phone}`], ["Ville", formData.town], ["Pays", formData.country]].map(([label, value]) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", padding: "0.375rem 0" }, children: [
3667
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", color: C$1.gray500 }, children: label }),
3668
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", fontWeight: 500 }, children: value })
3669
+ ] }, label)) }),
3670
+ /* @__PURE__ */ jsx(StepIndicator, { current: 6 }),
3671
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, disabled: regLoading, style: { width: "100%" }, children: regLoading ? /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
3672
+ /* @__PURE__ */ jsx(IconLoader2, { style: { width: "1rem", height: "1rem" } }),
3673
+ " Création du compte..."
3674
+ ] }) : "Créer mon compte" })
3675
+ ] })
3676
+ ] })
3677
+ ] }) }) });
3678
+ }
3679
+ const C = {
3680
+ primary: "#002147",
3681
+ accent: "#e8430a",
3682
+ gray100: "#f3f4f6",
3683
+ gray200: "#e5e7eb",
3684
+ gray500: "#6b7280",
3685
+ gray700: "#374151",
3686
+ white: "#ffffff"
3687
+ };
3688
+ function OnboardingModal({ open, onOpenChange, user, onComplete, onSkip }) {
3689
+ const needsPhoto = !user.image_url;
3690
+ const needsPhone = !user.phone;
3691
+ const needsEmail = !user.email;
3692
+ const [photoPreview, setPhotoPreview] = useState("");
3693
+ const [photoFile, setPhotoFile] = useState(null);
3694
+ const [ccphone, setCcphone] = useState(user.ccphone || "+221");
3695
+ const [phone, setPhone] = useState(user.phone || "");
3696
+ const [email, setEmail] = useState("");
3697
+ const [confirmed, setConfirmed] = useState(false);
3698
+ const [submitting, setSubmitting] = useState(false);
3699
+ const [fileError, setFileError] = useState("");
3700
+ const handleFileChange = useCallback((e) => {
3701
+ var _a;
3702
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
3703
+ if (!file) return;
3704
+ if (file.size > 2 * 1024 * 1024) {
3705
+ setFileError("Le fichier dépasse 2 Mo. Veuillez choisir une image plus légère.");
3706
+ e.target.value = "";
3707
+ return;
3708
+ }
3709
+ setFileError("");
3710
+ setPhotoFile(file);
3711
+ const reader = new FileReader();
3712
+ reader.onload = () => setPhotoPreview(reader.result);
3713
+ reader.readAsDataURL(file);
3714
+ }, []);
3715
+ const canSubmit = confirmed && ((needsPhoto ? !!photoPreview : true) && (needsPhone ? phone.length >= 7 : true));
3716
+ const handleSubmit = useCallback(() => {
3717
+ if (!canSubmit) return;
3718
+ setSubmitting(true);
3719
+ const data = {};
3720
+ if (needsPhoto && photoPreview) {
3721
+ data.image_url = photoPreview;
3722
+ }
3723
+ if (needsPhone && phone) {
3724
+ data.ccphone = ccphone;
3725
+ data.phone = phone;
3726
+ }
3727
+ if (needsEmail && email.trim()) {
3728
+ data.email = email.trim();
3729
+ }
3730
+ onComplete(data);
3731
+ }, [canSubmit, needsPhoto, needsPhone, needsEmail, photoPreview, ccphone, phone, email, onComplete]);
3732
+ const ShieldIcon = () => /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: C.accent, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3733
+ /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }),
3734
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
3735
+ ] });
3736
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
3737
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3738
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.25rem" }, children: [
3739
+ /* @__PURE__ */ jsx(ShieldIcon, {}),
3740
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Complétez votre profil" })
3741
+ ] }),
3742
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Ajoutez les informations manquantes pour finaliser votre compte." })
3743
+ ] }),
3744
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1.25rem", marginTop: "1rem" }, children: [
3745
+ needsPhoto && /* @__PURE__ */ jsxs("div", { children: [
3746
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Photo de profil" }),
3747
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "1rem" }, children: [
3748
+ /* @__PURE__ */ jsx("div", { style: {
3749
+ width: "4rem",
3750
+ height: "4rem",
3751
+ borderRadius: "50%",
3752
+ backgroundColor: C.gray100,
3753
+ border: `2px dashed ${C.gray200}`,
3754
+ overflow: "hidden",
3755
+ display: "flex",
3756
+ alignItems: "center",
3757
+ justifyContent: "center",
3758
+ flexShrink: 0
3759
+ }, children: photoPreview ? /* @__PURE__ */ jsx("img", { src: photoPreview, alt: "Preview", style: { width: "100%", height: "100%", objectFit: "cover" } }) : /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: C.gray500, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
3760
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "4" }),
3761
+ /* @__PURE__ */ jsx("path", { d: "M5.5 21a8.38 8.38 0 0 1 13 0" })
3762
+ ] }) }),
3763
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
3764
+ /* @__PURE__ */ jsxs("label", { style: {
3765
+ display: "inline-block",
3766
+ padding: "0.5rem 1rem",
3767
+ backgroundColor: C.gray100,
3768
+ border: `1px solid ${C.gray200}`,
3769
+ borderRadius: "0.375rem",
3770
+ cursor: "pointer",
3771
+ fontSize: "0.875rem",
3772
+ color: C.gray700,
3773
+ fontWeight: 500
3774
+ }, children: [
3775
+ "Choisir une photo",
3776
+ /* @__PURE__ */ jsx(
3777
+ "input",
3778
+ {
3779
+ type: "file",
3780
+ accept: "image/*",
3781
+ onChange: handleFileChange,
3782
+ style: { display: "none" }
3783
+ }
3784
+ )
3785
+ ] }),
3786
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.75rem", color: fileError ? "#dc2626" : C.gray500, marginTop: "0.25rem" }, children: fileError || "JPG, PNG. Max 2 Mo." })
3787
+ ] })
3788
+ ] })
3789
+ ] }),
3790
+ needsPhone && /* @__PURE__ */ jsxs("div", { children: [
3791
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Numéro de téléphone" }),
3792
+ /* @__PURE__ */ jsx(
3793
+ PhoneInput,
3794
+ {
3795
+ value: phone,
3796
+ onChange: setPhone,
3797
+ ccphone,
3798
+ onCcphoneChange: setCcphone
3799
+ }
3800
+ )
3801
+ ] }),
3802
+ needsEmail && /* @__PURE__ */ jsxs("div", { children: [
3803
+ /* @__PURE__ */ jsxs(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: [
3804
+ "Adresse email ",
3805
+ /* @__PURE__ */ jsx("span", { style: { color: C.gray500, fontWeight: 400 }, children: "(optionnel)" })
3806
+ ] }),
3807
+ /* @__PURE__ */ jsx(
3808
+ "input",
3809
+ {
3810
+ type: "email",
3811
+ value: email,
3812
+ onChange: (e) => setEmail(e.target.value),
3813
+ placeholder: "email@exemple.com",
3814
+ style: {
3815
+ width: "100%",
3816
+ height: "2.5rem",
3817
+ padding: "0 0.75rem",
3818
+ border: `1px solid ${C.gray200}`,
3819
+ borderRadius: "0.375rem",
3820
+ fontSize: "0.875rem",
3821
+ color: C.gray700,
3822
+ backgroundColor: C.white,
3823
+ outline: "none"
3824
+ }
3825
+ }
3826
+ )
3827
+ ] }),
3828
+ /* @__PURE__ */ jsxs("label", { style: {
3829
+ display: "flex",
3830
+ alignItems: "flex-start",
3831
+ gap: "0.5rem",
3832
+ cursor: "pointer",
3833
+ fontSize: "0.875rem",
3834
+ color: C.gray700
3835
+ }, children: [
3836
+ /* @__PURE__ */ jsx(
3837
+ "input",
3838
+ {
3839
+ type: "checkbox",
3840
+ checked: confirmed,
3841
+ onChange: (e) => setConfirmed(e.target.checked),
3842
+ style: {
3843
+ width: "1rem",
3844
+ height: "1rem",
3845
+ marginTop: "0.125rem",
3846
+ accentColor: C.primary,
3847
+ cursor: "pointer"
3848
+ }
3849
+ }
3850
+ ),
3851
+ /* @__PURE__ */ jsx("span", { children: "Je confirme que ces informations sont exactes" })
3852
+ ] }),
3853
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
3854
+ /* @__PURE__ */ jsx(
3855
+ Button,
3856
+ {
3857
+ onClick: handleSubmit,
3858
+ disabled: !canSubmit || submitting,
3859
+ style: { width: "100%", height: "2.75rem", opacity: canSubmit && !submitting ? 1 : 0.5 },
3860
+ children: submitting ? "Enregistrement..." : "Valider"
3861
+ }
3862
+ ),
3863
+ /* @__PURE__ */ jsx(
3864
+ Button,
3865
+ {
3866
+ variant: "outline",
3867
+ onClick: onSkip,
3868
+ disabled: submitting,
3869
+ style: { width: "100%", height: "2.75rem" },
3870
+ children: "Passer pour l'instant"
3871
+ }
3872
+ )
3873
+ ] })
3874
+ ] })
3875
+ ] }) });
3876
+ }
3877
+ function DebugPanel({ saasApiUrl, iamApiUrl }) {
3878
+ const [logs, setLogs] = useState([]);
3879
+ const [expanded, setExpanded] = useState(true);
3880
+ const [selectedLog, setSelectedLog] = useState(null);
3881
+ useEffect(() => {
3882
+ return subscribeToLogs(setLogs);
3883
+ }, []);
3884
+ const handleCopy = useCallback(() => {
3885
+ var _a;
3886
+ const text = exportLogsAsText();
3887
+ (_a = navigator.clipboard) == null ? void 0 : _a.writeText(text).catch(() => {
3888
+ });
3889
+ }, []);
3890
+ const handleClear = useCallback(() => {
3891
+ clearApiLogs();
3892
+ setSelectedLog(null);
3893
+ }, []);
3894
+ const statusColor = (status) => {
3895
+ if (status === null) return "#f59e0b";
3896
+ if (status >= 200 && status < 300) return "#22c55e";
3897
+ return "#ef4444";
3898
+ };
3899
+ const statusEmoji = (status) => {
3900
+ if (status === null) return "⏳";
3901
+ if (status >= 200 && status < 300) return "✅";
3902
+ return "❌";
3903
+ };
3904
+ const selected = selectedLog ? logs.find((l) => l.id === selectedLog) : null;
3905
+ return /* @__PURE__ */ jsxs("div", { style: {
3906
+ position: "fixed",
3907
+ bottom: 0,
3908
+ left: 0,
3909
+ right: 0,
3910
+ zIndex: 99999,
3911
+ fontFamily: "monospace",
3912
+ fontSize: "12px"
3913
+ }, children: [
3914
+ /* @__PURE__ */ jsxs(
3915
+ "div",
3916
+ {
3917
+ onClick: () => setExpanded(!expanded),
3918
+ style: {
3919
+ background: "#1a1a2e",
3920
+ color: "#e2e8f0",
3921
+ padding: "6px 12px",
3922
+ display: "flex",
3923
+ alignItems: "center",
3924
+ justifyContent: "space-between",
3925
+ cursor: "pointer",
3926
+ borderTop: "2px solid #6366f1"
3927
+ },
3928
+ children: [
3929
+ /* @__PURE__ */ jsxs("span", { children: [
3930
+ "🔐 Debug SSO — ",
3931
+ logs.length,
3932
+ " appel",
3933
+ logs.length > 1 ? "s" : ""
3934
+ ] }),
3935
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", alignItems: "center" }, children: [
3936
+ expanded && /* @__PURE__ */ jsxs(Fragment, { children: [
3937
+ /* @__PURE__ */ jsx(
3938
+ "button",
3939
+ {
3940
+ onClick: (e) => {
3941
+ e.stopPropagation();
3942
+ handleCopy();
3943
+ },
3944
+ style: { background: "#334155", color: "#e2e8f0", border: "none", borderRadius: "4px", padding: "2px 8px", cursor: "pointer", fontSize: "11px" },
3945
+ children: "📋 Copier"
3946
+ }
3947
+ ),
3948
+ /* @__PURE__ */ jsx(
3949
+ "button",
3950
+ {
3951
+ onClick: (e) => {
3952
+ e.stopPropagation();
3953
+ handleClear();
3954
+ },
3955
+ style: { background: "#334155", color: "#e2e8f0", border: "none", borderRadius: "4px", padding: "2px 8px", cursor: "pointer", fontSize: "11px" },
3956
+ children: "🗑️ Effacer"
3957
+ }
3958
+ )
3959
+ ] }),
3960
+ /* @__PURE__ */ jsx("span", { children: expanded ? "▼" : "▲" })
3961
+ ] })
3962
+ ]
3963
+ }
3964
+ ),
3965
+ expanded && /* @__PURE__ */ jsxs("div", { style: {
3966
+ background: "#0f0f23",
3967
+ color: "#e2e8f0",
3968
+ maxHeight: "300px",
3969
+ overflowY: "auto"
3970
+ }, children: [
3971
+ /* @__PURE__ */ jsxs("div", { style: { padding: "4px 12px", background: "#1e1e3f", fontSize: "10px", color: "#94a3b8", display: "flex", gap: "16px" }, children: [
3972
+ /* @__PURE__ */ jsxs("span", { children: [
3973
+ "SaaS: ",
3974
+ /* @__PURE__ */ jsx("span", { style: { color: "#6366f1" }, children: saasApiUrl })
3975
+ ] }),
3976
+ /* @__PURE__ */ jsxs("span", { children: [
3977
+ "IAM: ",
3978
+ /* @__PURE__ */ jsx("span", { style: { color: "#6366f1" }, children: iamApiUrl })
3979
+ ] })
3980
+ ] }),
3981
+ logs.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "20px", textAlign: "center", color: "#64748b" }, children: "Aucun appel API. Tentez une connexion pour voir les logs." }) : /* @__PURE__ */ jsx("div", { children: logs.map((log) => /* @__PURE__ */ jsxs("div", { children: [
3982
+ /* @__PURE__ */ jsxs(
3983
+ "div",
3984
+ {
3985
+ onClick: () => setSelectedLog(selectedLog === log.id ? null : log.id),
3986
+ style: {
3987
+ padding: "4px 12px",
3988
+ borderBottom: "1px solid #1e293b",
3989
+ cursor: "pointer",
3990
+ display: "flex",
3991
+ alignItems: "center",
3992
+ gap: "8px",
3993
+ background: selectedLog === log.id ? "#1e293b" : "transparent"
3994
+ },
3995
+ children: [
3996
+ /* @__PURE__ */ jsx("span", { children: statusEmoji(log.responseStatus) }),
3997
+ /* @__PURE__ */ jsx("span", { style: {
3998
+ background: log.method === "GET" ? "#22c55e" : "#3b82f6",
3999
+ color: "#fff",
4000
+ padding: "0 4px",
4001
+ borderRadius: "2px",
4002
+ fontSize: "10px",
4003
+ fontWeight: "bold"
4004
+ }, children: log.method }),
4005
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: log.endpoint }),
4006
+ /* @__PURE__ */ jsx("span", { style: { color: statusColor(log.responseStatus), minWidth: "30px" }, children: log.responseStatus ?? "..." }),
4007
+ /* @__PURE__ */ jsx("span", { style: { color: "#64748b", minWidth: "50px", textAlign: "right" }, children: log.duration ? `${log.duration}ms` : "..." })
4008
+ ]
4009
+ }
4010
+ ),
4011
+ selectedLog === log.id && selected && /* @__PURE__ */ jsxs("div", { style: { padding: "8px 12px 8px 32px", background: "#111827", borderBottom: "1px solid #1e293b" }, children: [
4012
+ /* @__PURE__ */ jsxs("div", { style: { color: "#94a3b8", marginBottom: "4px" }, children: [
4013
+ "⏰ ",
4014
+ new Date(log.timestamp).toLocaleTimeString()
4015
+ ] }),
4016
+ log.requestBody ? /* @__PURE__ */ jsxs("div", { style: { marginBottom: "4px" }, children: [
4017
+ /* @__PURE__ */ jsx("div", { style: { color: "#6366f1", fontSize: "10px" }, children: "REQUEST:" }),
4018
+ /* @__PURE__ */ jsx("pre", { style: { color: "#cbd5e1", whiteSpace: "pre-wrap", wordBreak: "break-all", margin: 0, maxHeight: "80px", overflow: "auto" }, children: JSON.stringify(log.requestBody, null, 2) })
4019
+ ] }) : null,
4020
+ log.responseBody ? /* @__PURE__ */ jsxs("div", { children: [
4021
+ /* @__PURE__ */ jsx("div", { style: { color: "#6366f1", fontSize: "10px" }, children: "RESPONSE:" }),
4022
+ /* @__PURE__ */ jsx("pre", { style: { color: "#cbd5e1", whiteSpace: "pre-wrap", wordBreak: "break-all", margin: 0, maxHeight: "120px", overflow: "auto" }, children: JSON.stringify(log.responseBody, null, 2) })
4023
+ ] }) : null,
4024
+ log.error && /* @__PURE__ */ jsxs("div", { style: { color: "#ef4444", marginTop: "4px" }, children: [
4025
+ "⚠️ ",
4026
+ log.error
4027
+ ] })
4028
+ ] })
4029
+ ] }, log.id)) })
4030
+ ] })
4031
+ ] });
4032
+ }
4033
+ const COLORS = {
4034
+ primary: "#002147",
4035
+ accent: "#e8430a",
4036
+ card: "#ffffff",
4037
+ cardForeground: "#1a2332",
4038
+ muted: "#6b7280",
4039
+ border: "#e5e7eb",
4040
+ shadow: "0 10px 25px -5px rgba(0,0,0,0.2)"
4041
+ };
4042
+ const LIGHT_VARS = {
4043
+ "--background": "40 20% 96%",
4044
+ "--foreground": "220 15% 15%",
4045
+ "--card": "0 0% 100%",
4046
+ "--card-foreground": "220 15% 15%",
4047
+ "--popover": "0 0% 100%",
4048
+ "--popover-foreground": "220 15% 15%",
4049
+ "--primary": "209 100% 13%",
4050
+ "--primary-foreground": "0 0% 100%",
4051
+ "--secondary": "40 15% 92%",
4052
+ "--secondary-foreground": "220 15% 25%",
4053
+ "--muted": "40 10% 90%",
4054
+ "--muted-foreground": "220 10% 50%",
4055
+ "--accent": "16 99% 52%",
4056
+ "--accent-foreground": "0 0% 100%",
4057
+ "--destructive": "0 84.2% 60.2%",
4058
+ "--destructive-foreground": "210 40% 98%",
4059
+ "--border": "40 15% 88%",
4060
+ "--input": "40 15% 88%",
4061
+ "--ring": "209 100% 13%"
4062
+ };
4063
+ const ShieldCheckIcon = () => /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: COLORS.accent, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4064
+ /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }),
4065
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
4066
+ ] });
4067
+ function needsOnboarding(user) {
4068
+ return !user.image_url || !user.phone;
4069
+ }
4070
+ function NativeSSOPage({
4071
+ saasApiUrl,
4072
+ iamApiUrl,
4073
+ onLoginSuccess,
4074
+ onLogout,
4075
+ onOnboardingComplete,
4076
+ debug,
4077
+ title = "Un compte, plusieurs accès",
4078
+ description = "Connectez-vous avec votre compte Ollaid pour accéder à toutes les applications partenaires.",
4079
+ logoUrl,
4080
+ hideFooter = false
4081
+ }) {
4082
+ const [modal, setModal] = useState("none");
4083
+ const [showOnboarding, setShowOnboarding] = useState(false);
4084
+ const [pendingSession, setPendingSession] = useState(null);
4085
+ const [session, setSession] = useState(() => {
4086
+ try {
4087
+ const token = localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
4088
+ const userStr = localStorage.getItem(STORAGE.USER);
4089
+ if (token && userStr) return { token, user: JSON.parse(userStr) };
4090
+ } catch {
4091
+ }
4092
+ return null;
4093
+ });
4094
+ const resolvedDebug = debug !== void 0 ? debug : isDebugMode();
4095
+ useEffect(() => {
4096
+ const root = document.documentElement;
4097
+ const originalValues = {};
4098
+ Object.keys(LIGHT_VARS).forEach((key) => {
4099
+ originalValues[key] = root.style.getPropertyValue(key);
4100
+ });
4101
+ Object.entries(LIGHT_VARS).forEach(([key, value]) => {
4102
+ root.style.setProperty(key, value);
4103
+ });
4104
+ root.classList.add("light");
4105
+ root.classList.remove("dark");
4106
+ root.style.colorScheme = "light";
4107
+ const meta = document.createElement("meta");
4108
+ meta.name = "color-scheme";
4109
+ meta.content = "light";
4110
+ document.head.appendChild(meta);
4111
+ return () => {
4112
+ Object.entries(originalValues).forEach(([key, value]) => {
4113
+ if (value) root.style.setProperty(key, value);
4114
+ else root.style.removeProperty(key);
4115
+ });
4116
+ root.classList.remove("light");
4117
+ root.style.colorScheme = "";
4118
+ meta.remove();
4119
+ };
4120
+ }, []);
4121
+ const openLogin = useCallback(() => setModal("login"), []);
4122
+ const openSignup = useCallback(() => setModal("signup"), []);
4123
+ const closeModal = useCallback(() => setModal("none"), []);
4124
+ const switchToSignup = useCallback(() => {
4125
+ setModal("none");
4126
+ setTimeout(() => setModal("signup"), 150);
4127
+ }, []);
4128
+ const switchToLogin = useCallback(() => {
4129
+ setModal("none");
4130
+ setTimeout(() => setModal("login"), 150);
4131
+ }, []);
4132
+ const handleLoginSuccess = useCallback((token, user) => {
4133
+ const userObj = {
4134
+ reference: "",
4135
+ name: user.name,
4136
+ email: user.email || void 0,
4137
+ phone: user.phone,
4138
+ ccphone: user.ccphone,
4139
+ image_url: user.image_url,
4140
+ account_type: user.account_type || "user"
4141
+ };
4142
+ setModal("none");
4143
+ if (needsOnboarding(userObj)) {
4144
+ setPendingSession({ token, user: userObj });
4145
+ setShowOnboarding(true);
4146
+ } else {
4147
+ setSession({ token, user: userObj });
4148
+ onLoginSuccess == null ? void 0 : onLoginSuccess(token, user);
4149
+ }
4150
+ }, [onLoginSuccess]);
4151
+ const handleOnboardingComplete = useCallback((data) => {
4152
+ if (!pendingSession) return;
4153
+ const updatedUser = { ...pendingSession.user, ...data };
4154
+ localStorage.setItem(STORAGE.USER, JSON.stringify(updatedUser));
4155
+ setShowOnboarding(false);
4156
+ setSession({ token: pendingSession.token, user: updatedUser });
4157
+ setPendingSession(null);
4158
+ onOnboardingComplete == null ? void 0 : onOnboardingComplete(data);
4159
+ onLoginSuccess == null ? void 0 : onLoginSuccess(pendingSession.token, updatedUser);
4160
+ }, [pendingSession, onLoginSuccess, onOnboardingComplete]);
4161
+ const handleOnboardingSkip = useCallback(() => {
4162
+ if (!pendingSession) return;
4163
+ setShowOnboarding(false);
4164
+ setSession(pendingSession);
4165
+ setPendingSession(null);
4166
+ onLoginSuccess == null ? void 0 : onLoginSuccess(pendingSession.token, pendingSession.user);
4167
+ }, [pendingSession, onLoginSuccess]);
4168
+ const handleLogout = useCallback(() => {
4169
+ localStorage.removeItem(STORAGE.AUTH_TOKEN);
4170
+ localStorage.removeItem(STORAGE.TOKEN);
4171
+ localStorage.removeItem(STORAGE.USER);
4172
+ localStorage.removeItem(STORAGE.ACCOUNT_TYPE);
4173
+ setSession(null);
4174
+ onLogout == null ? void 0 : onLogout();
4175
+ }, [onLogout]);
4176
+ const containerStyle = {
4177
+ minHeight: "100vh",
4178
+ backgroundColor: COLORS.primary,
4179
+ display: "flex",
4180
+ flexDirection: "column",
4181
+ alignItems: "center",
4182
+ justifyContent: "center",
4183
+ padding: "1rem"
4184
+ };
4185
+ const cardStyle = {
4186
+ width: "100%",
4187
+ maxWidth: "28rem",
4188
+ backgroundColor: COLORS.card,
4189
+ border: `1px solid ${COLORS.border}`,
4190
+ borderRadius: "0.75rem",
4191
+ boxShadow: COLORS.shadow,
4192
+ overflow: "hidden"
4193
+ };
4194
+ const footerStyle = {
4195
+ marginTop: "1.5rem",
4196
+ fontSize: "0.75rem",
4197
+ color: "rgba(255,255,255,0.6)",
4198
+ textAlign: "center"
4199
+ };
4200
+ const BrandingHeader = () => /* @__PURE__ */ jsx("div", { style: { textAlign: "center", marginBottom: "1.5rem" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0.5rem", marginBottom: "0.5rem" }, children: [
4201
+ /* @__PURE__ */ jsx(ShieldCheckIcon, {}),
4202
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "1.125rem", fontWeight: 700, color: COLORS.primary }, children: [
4203
+ "iam.",
4204
+ /* @__PURE__ */ jsx("span", { style: { color: COLORS.accent }, children: "ollaid" }),
4205
+ ".com"
4206
+ ] })
4207
+ ] }) });
4208
+ if (session) {
4209
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, children: [
4210
+ /* @__PURE__ */ jsx("div", { style: { width: "100%", maxWidth: "28rem", marginBottom: "1.5rem" }, children: /* @__PURE__ */ jsx(AppsLogoSlider, {}) }),
4211
+ /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { padding: "2rem 1.5rem 1.5rem" }, children: [
4212
+ /* @__PURE__ */ jsx(BrandingHeader, {}),
4213
+ /* @__PURE__ */ jsxs("h2", { style: { fontSize: "1.25rem", fontWeight: 600, textAlign: "center", color: COLORS.cardForeground }, children: [
4214
+ "Bienvenue, ",
4215
+ session.user.name
4216
+ ] }),
4217
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: COLORS.muted, textAlign: "center", marginTop: "0.25rem" }, children: "Vous êtes connecté à votre compte Ollaid SSO" }),
4218
+ /* @__PURE__ */ jsx("div", { style: { marginTop: "1.5rem" }, children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: handleLogout, style: { width: "100%" }, children: "Déconnexion" }) })
4219
+ ] }) }),
4220
+ !hideFooter && /* @__PURE__ */ jsxs("p", { style: footerStyle, children: [
4221
+ "Géré par",
4222
+ " ",
4223
+ /* @__PURE__ */ jsx("a", { href: "https://iam.ollaid.com", target: "_blank", rel: "noopener noreferrer", style: { color: COLORS.accent, textDecoration: "none" }, children: "iam.ollaid.com" }),
4224
+ " ",
4225
+ "— Identity Access Manager"
4226
+ ] })
4227
+ ] });
4228
+ }
4229
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, children: [
4230
+ /* @__PURE__ */ jsx("div", { style: { width: "100%", maxWidth: "28rem", marginBottom: "1.5rem" }, children: logoUrl ? /* @__PURE__ */ jsx("img", { src: logoUrl, alt: "Logo", style: { height: "3rem", margin: "0 auto" } }) : /* @__PURE__ */ jsx(AppsLogoSlider, {}) }),
4231
+ /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { padding: "2rem 1.5rem 1.5rem" }, children: [
4232
+ /* @__PURE__ */ jsx(BrandingHeader, {}),
4233
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.25rem", fontWeight: 600, textAlign: "center", color: COLORS.cardForeground, marginBottom: "0.5rem" }, children: title }),
4234
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: COLORS.muted, textAlign: "center", marginBottom: "1.5rem" }, children: description }),
4235
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
4236
+ /* @__PURE__ */ jsx(Button, { onClick: openLogin, style: { width: "100%", height: "2.75rem" }, children: "Connexion" }),
4237
+ /* @__PURE__ */ jsx(
4238
+ Button,
4239
+ {
4240
+ variant: "outline",
4241
+ onClick: openSignup,
4242
+ style: {
4243
+ width: "100%",
4244
+ height: "2.75rem",
4245
+ borderColor: COLORS.cardForeground,
4246
+ color: COLORS.cardForeground
4247
+ },
4248
+ children: "Inscription"
4249
+ }
4250
+ )
4251
+ ] })
4252
+ ] }) }),
4253
+ !hideFooter && /* @__PURE__ */ jsxs("p", { style: footerStyle, children: [
4254
+ "Géré par",
4255
+ " ",
4256
+ /* @__PURE__ */ jsx("a", { href: "https://iam.ollaid.com", target: "_blank", rel: "noopener noreferrer", style: { color: COLORS.accent, textDecoration: "none" }, children: "iam.ollaid.com" }),
4257
+ " ",
4258
+ "— Identity Access Manager"
4259
+ ] }),
4260
+ /* @__PURE__ */ jsx(
4261
+ LoginModal,
4262
+ {
4263
+ open: modal === "login",
4264
+ onOpenChange: (open) => {
4265
+ if (!open) closeModal();
4266
+ },
4267
+ onSwitchToSignup: switchToSignup,
4268
+ onLoginSuccess: handleLoginSuccess,
4269
+ saasApiUrl,
4270
+ iamApiUrl,
4271
+ debug: resolvedDebug
4272
+ }
4273
+ ),
4274
+ /* @__PURE__ */ jsx(
4275
+ SignupModal,
4276
+ {
4277
+ open: modal === "signup",
4278
+ onOpenChange: (open) => {
4279
+ if (!open) closeModal();
4280
+ },
4281
+ onSwitchToLogin: switchToLogin,
4282
+ onSignupSuccess: handleLoginSuccess,
4283
+ saasApiUrl,
4284
+ iamApiUrl,
4285
+ debug: resolvedDebug
4286
+ }
4287
+ ),
4288
+ pendingSession && /* @__PURE__ */ jsx(
4289
+ OnboardingModal,
4290
+ {
4291
+ open: showOnboarding,
4292
+ onOpenChange: (open) => {
4293
+ if (!open) handleOnboardingSkip();
4294
+ },
4295
+ user: pendingSession.user,
4296
+ onComplete: handleOnboardingComplete,
4297
+ onSkip: handleOnboardingSkip
4298
+ }
4299
+ ),
4300
+ resolvedDebug && /* @__PURE__ */ jsx(DebugPanel, { saasApiUrl, iamApiUrl })
4301
+ ] });
4302
+ }
4303
+ const NativeSSOContext = createContext(null);
4304
+ function NativeSSOProvider({ config: config2, children }) {
4305
+ const configuredRef = useRef(false);
4306
+ useEffect(() => {
4307
+ if (!configuredRef.current) {
4308
+ setNativeAuthConfig(config2);
4309
+ configuredRef.current = true;
4310
+ }
4311
+ }, [config2]);
4312
+ return /* @__PURE__ */ jsx(NativeSSOContext.Provider, { value: config2, children });
4313
+ }
4314
+ function useNativeSSOConfig() {
4315
+ const ctx = useContext(NativeSSOContext);
4316
+ if (!ctx) {
4317
+ throw new Error("useNativeSSOConfig doit être utilisé dans un <NativeSSOProvider>");
4318
+ }
4319
+ return ctx;
4320
+ }
4321
+ function getCredentialsOrThrow(params) {
4322
+ const app_key = params.app_key;
4323
+ const secret_key = params.secret_key;
4324
+ if (!app_key || !secret_key) {
4325
+ throw new Error("app_key et secret_key sont requis en paramètre. Ces APIs sont server-to-server et ne peuvent pas utiliser les encrypted_credentials.");
4326
+ }
4327
+ return { app_key, secret_key };
4328
+ }
4329
+ const iamAccountService = {
4330
+ /**
4331
+ * Lier un numéro de téléphone à un compte utilisateur existant
4332
+ * POST {iamApiUrl}/iam/link-phone
4333
+ */
4334
+ async linkPhone(params) {
4335
+ const config2 = getNativeAuthConfig();
4336
+ const { app_key, secret_key } = getCredentialsOrThrow(params);
4337
+ return fetchWithTimeout(
4338
+ `${config2.iamApiUrl}/iam/link-phone`,
4339
+ {
4340
+ method: "POST",
4341
+ headers: getHeaders(),
4342
+ body: JSON.stringify({
4343
+ app_key,
4344
+ secret_key,
4345
+ iam_reference: params.iam_reference,
4346
+ ccphone: params.ccphone,
4347
+ phone: params.phone
4348
+ })
4349
+ },
4350
+ config2.timeout || 3e4
4351
+ );
4352
+ },
4353
+ /**
4354
+ * Lier une adresse email à un compte utilisateur existant
4355
+ * POST {iamApiUrl}/iam/link-email
4356
+ */
4357
+ async linkEmail(params) {
4358
+ const config2 = getNativeAuthConfig();
4359
+ const { app_key, secret_key } = getCredentialsOrThrow(params);
4360
+ return fetchWithTimeout(
4361
+ `${config2.iamApiUrl}/iam/link-email`,
4362
+ {
4363
+ method: "POST",
4364
+ headers: getHeaders(),
4365
+ body: JSON.stringify({
4366
+ app_key,
4367
+ secret_key,
4368
+ iam_reference: params.iam_reference,
4369
+ email: params.email
4370
+ })
4371
+ },
4372
+ config2.timeout || 3e4
4373
+ );
4374
+ },
4375
+ /**
4376
+ * Récupérer les user_infos d'un utilisateur (mode single)
4377
+ * POST {iamApiUrl}/iam/refresh-user-info
4378
+ */
4379
+ async refreshUserInfo(params) {
4380
+ const config2 = getNativeAuthConfig();
4381
+ const secret_key = params.secret_key;
4382
+ if (!secret_key) {
4383
+ throw new Error("secret_key est requis. Chargez les credentials ou passez-la en paramètre.");
4384
+ }
4385
+ return fetchWithTimeout(
4386
+ `${config2.iamApiUrl}/iam/refresh-user-info`,
4387
+ {
4388
+ method: "POST",
4389
+ headers: getHeaders(),
4390
+ body: JSON.stringify({
4391
+ secret_key,
4392
+ alias_reference: params.alias_reference
4393
+ })
4394
+ },
4395
+ config2.timeout || 3e4
4396
+ );
4397
+ },
4398
+ /**
4399
+ * Récupérer les user_infos en bulk (max 100 alias_references)
4400
+ * POST {iamApiUrl}/iam/refresh-user-info
4401
+ */
4402
+ async refreshUserInfoBulk(params) {
4403
+ const config2 = getNativeAuthConfig();
4404
+ const secret_key = params.secret_key;
4405
+ if (!secret_key) {
4406
+ throw new Error("secret_key est requis. Chargez les credentials ou passez-la en paramètre.");
4407
+ }
4408
+ if (params.alias_references.length > 100) {
4409
+ throw new Error("Maximum 100 alias_references par requête bulk.");
4410
+ }
4411
+ return fetchWithTimeout(
4412
+ `${config2.iamApiUrl}/iam/refresh-user-info`,
4413
+ {
4414
+ method: "POST",
4415
+ headers: getHeaders(),
4416
+ body: JSON.stringify({
4417
+ secret_key,
4418
+ alias_references: params.alias_references
4419
+ })
4420
+ },
4421
+ config2.timeout || 3e4
4422
+ );
4423
+ },
4424
+ /**
4425
+ * Mettre à jour l'avatar d'un utilisateur pour une application spécifique
4426
+ * POST {iamApiUrl}/iam/update-avatar
4427
+ */
4428
+ async updateAvatar(params) {
4429
+ const config2 = getNativeAuthConfig();
4430
+ const { app_key, secret_key } = getCredentialsOrThrow(params);
4431
+ return fetchWithTimeout(
4432
+ `${config2.iamApiUrl}/iam/update-avatar`,
4433
+ {
4434
+ method: "POST",
4435
+ headers: getHeaders(),
4436
+ body: JSON.stringify({
4437
+ app_key,
4438
+ secret_key,
4439
+ alias_reference: params.alias_reference,
4440
+ avatar_url: params.avatar_url
4441
+ })
4442
+ },
4443
+ config2.timeout || 3e4
4444
+ );
4445
+ },
4446
+ /**
4447
+ * Réinitialiser l'avatar d'un utilisateur (remettre à null pour utiliser l'image globale)
4448
+ * POST {iamApiUrl}/iam/reset-avatar
4449
+ */
4450
+ async resetAvatar(params) {
4451
+ const config2 = getNativeAuthConfig();
4452
+ const { app_key, secret_key } = getCredentialsOrThrow(params);
4453
+ return fetchWithTimeout(
4454
+ `${config2.iamApiUrl}/iam/reset-avatar`,
4455
+ {
4456
+ method: "POST",
4457
+ headers: getHeaders(),
4458
+ body: JSON.stringify({
4459
+ app_key,
4460
+ secret_key,
4461
+ alias_reference: params.alias_reference
4462
+ })
4463
+ },
4464
+ config2.timeout || 3e4
4465
+ );
4466
+ }
4467
+ };
4468
+ export {
4469
+ ApiError,
4470
+ AppsLogoSlider,
4471
+ LoginModal,
4472
+ NativeSSOPage,
4473
+ NativeSSOProvider,
4474
+ OTPInput,
4475
+ OnboardingModal,
4476
+ PasswordRecoveryModal,
4477
+ PhoneInput,
4478
+ SignupModal,
4479
+ getAccountType,
4480
+ getAuthToken,
4481
+ getAuthUser,
4482
+ getNativeAuthConfig,
4483
+ iamAccountService,
4484
+ mobilePasswordService,
4485
+ nativeAuthService,
4486
+ setNativeAuthConfig,
4487
+ useMobilePassword,
4488
+ useMobileRegistration,
4489
+ useNativeAuth,
4490
+ useNativeSSOConfig,
4491
+ useTokenHealthCheck
4492
+ };
4493
+ //# sourceMappingURL=index.js.map