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