@ollaid/native-sso 2.1.5 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
5
- import { createContext, forwardRef, useEffect, useContext, useRef, useState, useCallback } from "react";
5
+ import { createContext, forwardRef, useEffect, useContext, useRef, useState, useCallback, useMemo } from "react";
6
6
  const iconProps = { width: "100%", height: "100%", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" };
7
7
  function IconShieldCheck(props) {
8
8
  return /* @__PURE__ */ jsxs("svg", { ...iconProps, ...props, children: [
@@ -119,7 +119,7 @@ function Dialog({ open, onOpenChange, children }) {
119
119
  if (!open) return null;
120
120
  return /* @__PURE__ */ jsx(DialogContext.Provider, { value: { open, onOpenChange }, children });
121
121
  }
122
- function DialogContent({ children, className = "" }) {
122
+ function DialogContent({ children, className = "", style, hideCloseButton = false }) {
123
123
  const { onOpenChange } = useContext(DialogContext);
124
124
  return /* @__PURE__ */ jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 50, display: "flex", alignItems: "center", justifyContent: "center" }, children: [
125
125
  /* @__PURE__ */ jsx(
@@ -151,10 +151,11 @@ function DialogContent({ children, className = "" }) {
151
151
  maxHeight: "90vh",
152
152
  display: "flex",
153
153
  flexDirection: "column",
154
- animation: "ollaid-modal-in 0.3s ease-out"
154
+ animation: "ollaid-modal-in 0.3s ease-out",
155
+ ...style
155
156
  },
156
157
  children: [
157
- /* @__PURE__ */ jsx(
158
+ !hideCloseButton && /* @__PURE__ */ jsx(
158
159
  "button",
159
160
  {
160
161
  onClick: () => onOpenChange(false),
@@ -479,7 +480,20 @@ const setNativeAuthConfig = (newConfig) => {
479
480
  };
480
481
  const getNativeAuthConfig = () => ({ ...config });
481
482
  const isDebugMode = () => config.debug === true;
483
+ const getIamApiBaseUrl = () => {
484
+ if (!config.iamApiUrl) {
485
+ throw new ApiError("iamApiUrl non configurée", "unknown");
486
+ }
487
+ return config.iamApiUrl;
488
+ };
482
489
  const DEVICE_ID_KEY = "native_sso_device_id";
490
+ const SESSION_UUID_KEY = "native_sso_session_uuid";
491
+ function generateUuid() {
492
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
493
+ return crypto.randomUUID();
494
+ }
495
+ return `uuid_${Date.now()}_${Math.random().toString(36).slice(2)}_${Math.random().toString(36).slice(2)}`;
496
+ }
483
497
  const getDeviceId = () => {
484
498
  if (typeof localStorage === "undefined") return "";
485
499
  let deviceId = localStorage.getItem(DEVICE_ID_KEY);
@@ -489,13 +503,30 @@ const getDeviceId = () => {
489
503
  }
490
504
  return deviceId;
491
505
  };
506
+ const getSessionUuid = () => {
507
+ if (typeof localStorage === "undefined") return "";
508
+ let uuid = localStorage.getItem(SESSION_UUID_KEY);
509
+ if (!uuid) {
510
+ uuid = generateUuid();
511
+ localStorage.setItem(SESSION_UUID_KEY, uuid);
512
+ }
513
+ return uuid;
514
+ };
492
515
  const STORAGE = {
493
516
  AUTH_TOKEN: "auth_token",
494
517
  TOKEN: "token",
495
518
  USER: "user",
496
519
  ACCOUNT_TYPE: "account_type",
497
520
  ALIAS_REFERENCE: "alias_reference",
498
- APP_ACCESS_TOKEN_REF: "app_access_token_ref"
521
+ APP_ACCESS_TOKEN_REF: "app_access_token_ref",
522
+ REFRESH_TOKEN: "refresh_token",
523
+ TOKEN_EXPIRES_AT: "token_expires_at",
524
+ REFRESH_EXPIRES_AT: "refresh_expires_at"
525
+ };
526
+ const PROFILE_STORAGE = {
527
+ IMAGE_LAST_STATUS: "sso_image_last_status",
528
+ IMAGE_LAST_CHECK: "sso_image_last_check",
529
+ IMAGE_RECHECK_AT: "sso_image_recheck_at"
499
530
  };
500
531
  const setAuthToken = (token) => {
501
532
  if (typeof localStorage !== "undefined") {
@@ -515,6 +546,9 @@ const clearAuthToken = () => {
515
546
  localStorage.removeItem(STORAGE.ACCOUNT_TYPE);
516
547
  localStorage.removeItem(STORAGE.ALIAS_REFERENCE);
517
548
  localStorage.removeItem(STORAGE.APP_ACCESS_TOKEN_REF);
549
+ localStorage.removeItem(STORAGE.REFRESH_TOKEN);
550
+ localStorage.removeItem(STORAGE.TOKEN_EXPIRES_AT);
551
+ localStorage.removeItem(STORAGE.REFRESH_EXPIRES_AT);
518
552
  }
519
553
  };
520
554
  const logout = async () => {
@@ -542,11 +576,53 @@ const getAccountType = () => {
542
576
  if (typeof localStorage === "undefined") return null;
543
577
  return localStorage.getItem(STORAGE.ACCOUNT_TYPE);
544
578
  };
579
+ function getProfilePromptState() {
580
+ if (typeof localStorage === "undefined") {
581
+ return { lastStatus: null, lastCheckAt: null, recheckAt: null };
582
+ }
583
+ const lastStatusRaw = localStorage.getItem(PROFILE_STORAGE.IMAGE_LAST_STATUS);
584
+ const lastCheckRaw = localStorage.getItem(PROFILE_STORAGE.IMAGE_LAST_CHECK);
585
+ const recheckRaw = localStorage.getItem(PROFILE_STORAGE.IMAGE_RECHECK_AT);
586
+ const parseTs = (value) => {
587
+ if (!value) return null;
588
+ const parsed = Number(value);
589
+ return Number.isFinite(parsed) ? parsed : null;
590
+ };
591
+ return {
592
+ lastStatus: lastStatusRaw === null ? null : lastStatusRaw === "true",
593
+ lastCheckAt: parseTs(lastCheckRaw),
594
+ recheckAt: parseTs(recheckRaw)
595
+ };
596
+ }
597
+ function setProfilePromptState(status, recheckAt = null) {
598
+ if (typeof localStorage === "undefined") return;
599
+ localStorage.setItem(PROFILE_STORAGE.IMAGE_LAST_STATUS, status ? "true" : "false");
600
+ localStorage.setItem(PROFILE_STORAGE.IMAGE_LAST_CHECK, String(Date.now()));
601
+ if (recheckAt && Number.isFinite(recheckAt)) {
602
+ localStorage.setItem(PROFILE_STORAGE.IMAGE_RECHECK_AT, String(recheckAt));
603
+ } else {
604
+ localStorage.removeItem(PROFILE_STORAGE.IMAGE_RECHECK_AT);
605
+ }
606
+ }
607
+ function markProfilePromptComplete() {
608
+ setProfilePromptState(true, null);
609
+ }
610
+ function snoozeProfilePrompt(hours = 8) {
611
+ setProfilePromptState(false, Date.now() + hours * 60 * 60 * 1e3);
612
+ }
545
613
  async function fetchWithTimeout(url, options, timeout) {
546
614
  const controller = new AbortController();
547
615
  const timeoutId = setTimeout(() => controller.abort(), timeout);
548
616
  const method = options.method || "GET";
549
- const logId = isDebugMode() ? logApiCall(method, url, options.body ? JSON.parse(options.body) : void 0) : null;
617
+ let parsedBody = void 0;
618
+ if (typeof options.body === "string") {
619
+ try {
620
+ parsedBody = JSON.parse(options.body);
621
+ } catch {
622
+ parsedBody = options.body;
623
+ }
624
+ }
625
+ const logId = isDebugMode() ? logApiCall(method, url, parsedBody) : null;
550
626
  const startTime = Date.now();
551
627
  try {
552
628
  const response = await fetch(url, {
@@ -602,6 +678,14 @@ function getHeaders(token, includeConfigPrefix = false) {
602
678
  if (deviceId) {
603
679
  headers["X-Device-Id"] = deviceId;
604
680
  }
681
+ const sessionUuid = getSessionUuid();
682
+ if (sessionUuid) {
683
+ headers["X-Session-UUID"] = sessionUuid;
684
+ }
685
+ const appAccessTokenRef = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE.APP_ACCESS_TOKEN_REF) : null;
686
+ if (appAccessTokenRef) {
687
+ headers["X-App-Access-Token-Ref"] = appAccessTokenRef;
688
+ }
605
689
  if (includeConfigPrefix && config.configPrefix) {
606
690
  headers["X-IAM-Config-Prefix"] = config.configPrefix;
607
691
  }
@@ -837,6 +921,15 @@ const nativeAuthService = {
837
921
  if (response.user) {
838
922
  setAuthUser(response.user);
839
923
  }
924
+ if (response.expires_at && typeof localStorage !== "undefined") {
925
+ localStorage.setItem(STORAGE.TOKEN_EXPIRES_AT, response.expires_at);
926
+ }
927
+ if (response.refresh_token && typeof localStorage !== "undefined") {
928
+ localStorage.setItem(STORAGE.REFRESH_TOKEN, response.refresh_token);
929
+ }
930
+ if (response.refresh_expires_at && typeof localStorage !== "undefined") {
931
+ localStorage.setItem(STORAGE.REFRESH_EXPIRES_AT, response.refresh_expires_at);
932
+ }
840
933
  if (response.app_access_token_ref && typeof localStorage !== "undefined") {
841
934
  localStorage.setItem(STORAGE.APP_ACCESS_TOKEN_REF, response.app_access_token_ref);
842
935
  }
@@ -881,6 +974,65 @@ const nativeAuthService = {
881
974
  throw err;
882
975
  }
883
976
  },
977
+ async refresh() {
978
+ const cfg = getNativeAuthConfig();
979
+ if (!cfg.saasApiUrl) {
980
+ throw new ApiError("saasApiUrl non configurée", "unknown");
981
+ }
982
+ const refreshToken = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE.REFRESH_TOKEN) : null;
983
+ if (!refreshToken) {
984
+ return { success: false, error_type: "missing_refresh", message: "Refresh token manquant" };
985
+ }
986
+ if (isDebugMode()) {
987
+ console.log("📤 [SaaS] POST /native/refresh");
988
+ }
989
+ let response;
990
+ try {
991
+ response = await fetchWithTimeout(
992
+ `${cfg.saasApiUrl}/native/refresh`,
993
+ {
994
+ method: "POST",
995
+ headers: getHeaders(void 0, true),
996
+ body: JSON.stringify({ refresh_token: refreshToken })
997
+ },
998
+ cfg.timeout || 3e4
999
+ );
1000
+ } catch (err) {
1001
+ if (err instanceof ApiError) {
1002
+ if (err.statusCode === 401) {
1003
+ return {
1004
+ success: false,
1005
+ error_type: err.errorType || "invalid_refresh",
1006
+ message: err.message
1007
+ };
1008
+ }
1009
+ if (err.statusCode === 404) {
1010
+ return {
1011
+ success: false,
1012
+ error_type: "not_supported",
1013
+ message: "Endpoint refresh non disponible sur ce SaaS"
1014
+ };
1015
+ }
1016
+ }
1017
+ throw err;
1018
+ }
1019
+ if (response.success) {
1020
+ if (response.token) {
1021
+ setAuthToken(response.token);
1022
+ }
1023
+ if (typeof localStorage !== "undefined") {
1024
+ if (response.expires_at) localStorage.setItem(STORAGE.TOKEN_EXPIRES_AT, response.expires_at);
1025
+ if (response.refresh_token) localStorage.setItem(STORAGE.REFRESH_TOKEN, response.refresh_token);
1026
+ if (response.refresh_expires_at) localStorage.setItem(STORAGE.REFRESH_EXPIRES_AT, response.refresh_expires_at);
1027
+ if (response.app_access_token_ref) localStorage.setItem(STORAGE.APP_ACCESS_TOKEN_REF, response.app_access_token_ref);
1028
+ if (response.alias_reference) localStorage.setItem(STORAGE.ALIAS_REFERENCE, response.alias_reference);
1029
+ }
1030
+ if (response.user) {
1031
+ setAuthUser(response.user);
1032
+ }
1033
+ }
1034
+ return response;
1035
+ },
884
1036
  async logout(token) {
885
1037
  const config2 = getNativeAuthConfig();
886
1038
  const iamToken = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN) : null;
@@ -1583,11 +1735,7 @@ async function checkTokenValidity(saasApiUrl, token, debug) {
1583
1735
  try {
1584
1736
  const response = await fetch(`${saasApiUrl}/native/check-token`, {
1585
1737
  method: "POST",
1586
- headers: {
1587
- "Content-Type": "application/json",
1588
- "Accept": "application/json",
1589
- "Authorization": `Bearer ${token}`
1590
- },
1738
+ headers: getHeaders(token, true),
1591
1739
  signal: controller.signal
1592
1740
  });
1593
1741
  clearTimeout(timeoutId);
@@ -1596,7 +1744,7 @@ async function checkTokenValidity(saasApiUrl, token, debug) {
1596
1744
  return { valid: false };
1597
1745
  }
1598
1746
  if (!response.ok) {
1599
- if (debug) console.log(`🔄 [HealthCheck] Serveur erreur ${response.status} — session conservée`);
1747
+ if (debug) console.log(`🔄 [HealthCheck] Erreur serveur ${response.status} — session conservée`);
1600
1748
  throw new Error(`server_error_${response.status}`);
1601
1749
  }
1602
1750
  const data = await response.json();
@@ -1604,11 +1752,8 @@ async function checkTokenValidity(saasApiUrl, token, debug) {
1604
1752
  if (debug) console.log("🔄 [HealthCheck] Token valide ✅ — user_infos mis à jour");
1605
1753
  return { valid: true, user: data.user };
1606
1754
  }
1607
- if (data.success !== false) {
1608
- if (debug) console.log("🔄 [HealthCheck] Token valide ✅");
1609
- return { valid: true, user: data.user };
1610
- }
1611
- return { valid: false };
1755
+ if (debug) console.log("🔄 [HealthCheck] Token valide ✅");
1756
+ return { valid: true, user: data.user };
1612
1757
  } catch (error) {
1613
1758
  clearTimeout(timeoutId);
1614
1759
  if (error instanceof Error && error.message === "offline") {
@@ -1648,7 +1793,7 @@ function revokeOnIam(iamApiUrl, sanctumToken, debug) {
1648
1793
  }
1649
1794
  }
1650
1795
  function useTokenHealthCheck(options) {
1651
- const { enabled, saasApiUrl, iamApiUrl, onTokenInvalid, onUserUpdated, debug = false } = options;
1796
+ const { enabled, saasApiUrl, iamApiUrl, onTokenInvalid, onUnauthorized, onUserUpdated, debug = false } = options;
1652
1797
  const timerRef = useRef(null);
1653
1798
  const intervalRef = useRef(null);
1654
1799
  const enabledRef = useRef(enabled);
@@ -1665,6 +1810,19 @@ function useTokenHealthCheck(options) {
1665
1810
  try {
1666
1811
  const result = await checkTokenValidity(saasApiUrl, token, debug);
1667
1812
  if (!result.valid) {
1813
+ if (onUnauthorized) {
1814
+ try {
1815
+ const decision = await onUnauthorized();
1816
+ if (decision === "recovered") {
1817
+ if (debug) console.log("🔄 [HealthCheck] Session récupérée via refresh ✅");
1818
+ return;
1819
+ }
1820
+ if (decision === "noop") {
1821
+ } else if (decision === "invalid") {
1822
+ }
1823
+ } catch {
1824
+ }
1825
+ }
1668
1826
  if (iamApiUrl) {
1669
1827
  if (debug) console.log("🔄 [HealthCheck] Revocation IAM avec sanctum_token...");
1670
1828
  revokeOnIam(iamApiUrl, token, debug);
@@ -1704,6 +1862,15 @@ function saveSession(exchangeResult, accountType) {
1704
1862
  const sanctumToken = exchangeResult.auth_token || exchangeResult.token;
1705
1863
  localStorage.setItem(STORAGE.AUTH_TOKEN, sanctumToken);
1706
1864
  localStorage.setItem(STORAGE.TOKEN, sanctumToken);
1865
+ if (exchangeResult.expires_at) {
1866
+ localStorage.setItem(STORAGE.TOKEN_EXPIRES_AT, exchangeResult.expires_at);
1867
+ }
1868
+ if (exchangeResult.refresh_token) {
1869
+ localStorage.setItem(STORAGE.REFRESH_TOKEN, exchangeResult.refresh_token);
1870
+ }
1871
+ if (exchangeResult.refresh_expires_at) {
1872
+ localStorage.setItem(STORAGE.REFRESH_EXPIRES_AT, exchangeResult.refresh_expires_at);
1873
+ }
1707
1874
  const baseUser = exchangeResult.user_infos ? { ...exchangeResult.user, ...exchangeResult.user_infos } : exchangeResult.user;
1708
1875
  const aliasRef = ((_a = exchangeResult.user) == null ? void 0 : _a.alias_reference) || exchangeResult.alias_reference || "";
1709
1876
  const iamRef = ((_b = exchangeResult.user) == null ? void 0 : _b.reference) || "";
@@ -1730,6 +1897,9 @@ function clearSession() {
1730
1897
  localStorage.removeItem(STORAGE.ACCOUNT_TYPE);
1731
1898
  localStorage.removeItem(STORAGE.ALIAS_REFERENCE);
1732
1899
  localStorage.removeItem(STORAGE.APP_ACCESS_TOKEN_REF);
1900
+ localStorage.removeItem(STORAGE.REFRESH_TOKEN);
1901
+ localStorage.removeItem(STORAGE.TOKEN_EXPIRES_AT);
1902
+ localStorage.removeItem(STORAGE.REFRESH_EXPIRES_AT);
1733
1903
  }
1734
1904
  function getErrorMessage$1(err, context) {
1735
1905
  if (err instanceof Error) {
@@ -1800,14 +1970,66 @@ function useNativeAuth(options) {
1800
1970
  }
1801
1971
  }
1802
1972
  }, []);
1973
+ const refreshingRef = useRef(false);
1974
+ const tryRefreshSession = useCallback(async () => {
1975
+ if (refreshingRef.current) return "noop";
1976
+ refreshingRef.current = true;
1977
+ try {
1978
+ const refreshToken = localStorage.getItem(STORAGE.REFRESH_TOKEN);
1979
+ if (!refreshToken) {
1980
+ if (isDebug) console.log("🔐 [Refresh] Pas de refresh_token — impossible de recuperer");
1981
+ return "noop";
1982
+ }
1983
+ if (typeof navigator !== "undefined" && !navigator.onLine) {
1984
+ if (isDebug) console.log("🔐 [Refresh] Offline — skip");
1985
+ return "noop";
1986
+ }
1987
+ const res = await nativeAuthService.refresh();
1988
+ if (res.success) {
1989
+ const storedUser = localStorage.getItem(STORAGE.USER);
1990
+ if (storedUser) {
1991
+ try {
1992
+ const user = JSON.parse(storedUser);
1993
+ setState((prev) => ({ ...prev, user, status: "completed" }));
1994
+ } catch {
1995
+ }
1996
+ }
1997
+ return "recovered";
1998
+ }
1999
+ if (res.success === false && res.error_type === "invalid_refresh") {
2000
+ if (isDebug) console.log("🔐 [Refresh] invalid_refresh — deconnexion autorisee");
2001
+ return "invalid";
2002
+ }
2003
+ return "noop";
2004
+ } catch (err) {
2005
+ if (isDebug) console.log("🔐 [Refresh] Echec refresh (non-bloquant)");
2006
+ return "noop";
2007
+ } finally {
2008
+ refreshingRef.current = false;
2009
+ }
2010
+ }, [isDebug]);
1803
2011
  useTokenHealthCheck({
1804
2012
  enabled: state.status === "completed" && state.user !== null,
1805
2013
  saasApiUrl,
1806
2014
  iamApiUrl,
1807
2015
  onTokenInvalid: handleTokenInvalid,
2016
+ onUnauthorized: tryRefreshSession,
1808
2017
  onUserUpdated: handleUserUpdated,
1809
2018
  debug: isDebug
1810
2019
  });
2020
+ useEffect(() => {
2021
+ if (state.status !== "completed" || !state.user) return;
2022
+ const expiresAt = localStorage.getItem(STORAGE.TOKEN_EXPIRES_AT);
2023
+ if (!expiresAt) return;
2024
+ const ts = Date.parse(expiresAt);
2025
+ if (Number.isNaN(ts)) return;
2026
+ const marginMs = 5 * 60 * 1e3;
2027
+ const delay = Math.max(ts - Date.now() - marginMs, 30 * 1e3);
2028
+ const t = setTimeout(() => {
2029
+ tryRefreshSession();
2030
+ }, delay);
2031
+ return () => clearTimeout(t);
2032
+ }, [state.status, state.user, tryRefreshSession]);
1811
2033
  useEffect(() => {
1812
2034
  const storedToken = localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
1813
2035
  const storedUser = localStorage.getItem(STORAGE.USER);
@@ -3178,11 +3400,13 @@ function PhoneInput({
3178
3400
  ccphone = DEFAULT_DIAL_CODE,
3179
3401
  onCcphoneChange,
3180
3402
  disabled = false,
3403
+ readOnly = false,
3181
3404
  error,
3182
3405
  placeholder = "77 123 45 67",
3183
3406
  lockCcphone = false
3184
3407
  }) {
3185
- const selectedCountry = getCountryByDialCode(ccphone) || COUNTRIES_SORTED_BY_CODE[0];
3408
+ const normalizedCcphone = ccphone || DEFAULT_DIAL_CODE;
3409
+ const selectedCountry = getCountryByDialCode(normalizedCcphone) || COUNTRIES_SORTED_BY_CODE[0];
3186
3410
  const [dropdownOpen, setDropdownOpen] = useState(false);
3187
3411
  const dropdownRef = useRef(null);
3188
3412
  useEffect(() => {
@@ -3194,16 +3418,18 @@ function PhoneInput({
3194
3418
  return () => document.removeEventListener("mousedown", handler);
3195
3419
  }, [dropdownOpen]);
3196
3420
  const handleChange = (e) => {
3421
+ if (disabled || readOnly) return;
3197
3422
  const cleaned = e.target.value.replace(/\D/g, "");
3198
3423
  onChange(cleaned.slice(0, selectedCountry.digits));
3199
3424
  };
3200
- const formatPhone = (phone) => {
3201
- if (phone.length <= 2) return phone;
3202
- if (phone.length <= 5) return `${phone.slice(0, 2)} ${phone.slice(2)}`;
3203
- if (phone.length <= 7) return `${phone.slice(0, 2)} ${phone.slice(2, 5)} ${phone.slice(5)}`;
3204
- return `${phone.slice(0, 2)} ${phone.slice(2, 5)} ${phone.slice(5, 7)} ${phone.slice(7)}`;
3425
+ const handlePaste = (e) => {
3426
+ if (disabled || readOnly) return;
3427
+ e.preventDefault();
3428
+ const pasted = e.clipboardData.getData("text").replace(/\s+/g, "").replace(/\D/g, "");
3429
+ onChange(pasted.slice(0, selectedCountry.digits));
3205
3430
  };
3206
3431
  const handleSelect = (dialCode) => {
3432
+ if (disabled || readOnly) return;
3207
3433
  if (onCcphoneChange) onCcphoneChange(dialCode);
3208
3434
  setDropdownOpen(false);
3209
3435
  };
@@ -3214,8 +3440,8 @@ function PhoneInput({
3214
3440
  border: `2px solid ${error ? "#ef4444" : "#d1d5db"}`,
3215
3441
  borderRadius: "0.5rem",
3216
3442
  overflow: "visible",
3217
- opacity: disabled ? 0.5 : 1,
3218
- backgroundColor: disabled ? "#f3f4f6" : "white",
3443
+ opacity: disabled || readOnly ? 0.75 : 1,
3444
+ backgroundColor: disabled || readOnly ? "#f9fafb" : "white",
3219
3445
  position: "relative"
3220
3446
  }, children: [
3221
3447
  onCcphoneChange && !lockCcphone ? /* @__PURE__ */ jsxs("div", { ref: dropdownRef, style: { position: "relative" }, children: [
@@ -3223,24 +3449,24 @@ function PhoneInput({
3223
3449
  "button",
3224
3450
  {
3225
3451
  type: "button",
3226
- onClick: () => !disabled && setDropdownOpen(!dropdownOpen),
3227
- disabled,
3452
+ onClick: () => !(disabled || readOnly) && setDropdownOpen(!dropdownOpen),
3453
+ disabled: disabled || readOnly,
3228
3454
  style: {
3229
3455
  display: "flex",
3230
3456
  alignItems: "center",
3231
- gap: "0.375rem",
3232
- padding: "0.75rem",
3457
+ gap: "0.35rem",
3458
+ padding: "0.45rem 0.6rem",
3233
3459
  backgroundColor: "#f9fafb",
3234
3460
  borderRight: "1px solid #e5e7eb",
3235
3461
  border: "none",
3236
- cursor: disabled ? "not-allowed" : "pointer",
3462
+ cursor: disabled || readOnly ? "not-allowed" : "pointer",
3237
3463
  fontWeight: 500,
3238
3464
  color: "#374151",
3239
- fontSize: "0.9375rem",
3465
+ fontSize: "0.85rem",
3240
3466
  whiteSpace: "nowrap"
3241
3467
  },
3242
3468
  children: [
3243
- /* @__PURE__ */ jsx("span", { style: { fontSize: "1.125rem" }, children: selectedCountry.flag }),
3469
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "1rem" }, children: selectedCountry.flag }),
3244
3470
  /* @__PURE__ */ jsx("span", { children: selectedCountry.dialCode }),
3245
3471
  /* @__PURE__ */ jsx("span", { style: { fontSize: "0.625rem", marginLeft: "0.125rem" }, children: "▼" })
3246
3472
  ]
@@ -3269,15 +3495,15 @@ function PhoneInput({
3269
3495
  alignItems: "center",
3270
3496
  gap: "0.5rem",
3271
3497
  width: "100%",
3272
- padding: "0.5rem 0.75rem",
3498
+ padding: "0.45rem 0.75rem",
3273
3499
  border: "none",
3274
3500
  cursor: "pointer",
3275
- fontSize: "0.875rem",
3276
- backgroundColor: c.dialCode === ccphone ? "#f3f4f6" : "transparent",
3501
+ fontSize: "0.85rem",
3502
+ backgroundColor: c.dialCode === normalizedCcphone ? "#f3f4f6" : "transparent",
3277
3503
  textAlign: "left"
3278
3504
  },
3279
3505
  onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "#f3f4f6",
3280
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = c.dialCode === ccphone ? "#f3f4f6" : "transparent",
3506
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = c.dialCode === normalizedCcphone ? "#f3f4f6" : "transparent",
3281
3507
  children: [
3282
3508
  /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: "#374151", minWidth: "1.75rem" }, children: c.code }),
3283
3509
  /* @__PURE__ */ jsx("span", { children: c.flag }),
@@ -3288,8 +3514,8 @@ function PhoneInput({
3288
3514
  )) })
3289
3515
  ] }) : (
3290
3516
  /* Locked view: only flag + dialCode */
3291
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.375rem", padding: "0.75rem", backgroundColor: "#f9fafb", borderRight: "1px solid #e5e7eb" }, children: [
3292
- /* @__PURE__ */ jsx("span", { style: { fontSize: "1.125rem" }, children: selectedCountry.flag }),
3517
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.35rem", padding: "0.45rem 0.6rem", backgroundColor: "#f9fafb", borderRight: "1px solid #e5e7eb" }, children: [
3518
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "1rem" }, children: selectedCountry.flag }),
3293
3519
  /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: "#374151" }, children: selectedCountry.dialCode })
3294
3520
  ] })
3295
3521
  ),
@@ -3298,11 +3524,13 @@ function PhoneInput({
3298
3524
  {
3299
3525
  type: "tel",
3300
3526
  inputMode: "numeric",
3301
- value: formatPhone(value),
3527
+ value,
3302
3528
  onChange: handleChange,
3529
+ onPaste: handlePaste,
3303
3530
  disabled,
3531
+ readOnly,
3304
3532
  placeholder,
3305
- style: { flex: 1, padding: "0.75rem", fontSize: "1.125rem", border: "none", outline: "none", backgroundColor: "transparent" }
3533
+ style: { flex: 1, padding: "0.45rem 0.6rem", fontSize: "0.95rem", border: "none", outline: "none", backgroundColor: "transparent" }
3306
3534
  }
3307
3535
  )
3308
3536
  ] }),
@@ -4273,6 +4501,414 @@ function SignupModal({ open, onOpenChange, onSwitchToLogin, onSignupSuccess, saa
4273
4501
  )
4274
4502
  ] });
4275
4503
  }
4504
+ function getBaseUrl() {
4505
+ return getIamApiBaseUrl();
4506
+ }
4507
+ function getAuthHeaders(includeConfigPrefix = false) {
4508
+ const token = getAuthToken();
4509
+ return getHeaders(token || void 0, includeConfigPrefix);
4510
+ }
4511
+ const profileChangeService = {
4512
+ async requestEmailChange(newEmail, method = "phone") {
4513
+ return fetchWithTimeout(
4514
+ `${getBaseUrl()}/backoffice/profile/email/request`,
4515
+ {
4516
+ method: "POST",
4517
+ headers: getAuthHeaders(true),
4518
+ body: JSON.stringify({ new_email: newEmail, method })
4519
+ },
4520
+ 3e4
4521
+ );
4522
+ },
4523
+ async requestPhoneChange(ccphone, phone, method = "email") {
4524
+ return fetchWithTimeout(
4525
+ `${getBaseUrl()}/backoffice/profile/phone/request`,
4526
+ {
4527
+ method: "POST",
4528
+ headers: getAuthHeaders(true),
4529
+ body: JSON.stringify({ ccphone, phone, method })
4530
+ },
4531
+ 3e4
4532
+ );
4533
+ },
4534
+ async verifyOldOTP(requestId, otpCode) {
4535
+ return fetchWithTimeout(
4536
+ `${getBaseUrl()}/backoffice/profile/change/verify-old`,
4537
+ {
4538
+ method: "POST",
4539
+ headers: getAuthHeaders(true),
4540
+ body: JSON.stringify({ request_id: requestId, otp_code: otpCode })
4541
+ },
4542
+ 3e4
4543
+ );
4544
+ },
4545
+ async verifyNewOTP(requestId, otpCode) {
4546
+ return fetchWithTimeout(
4547
+ `${getBaseUrl()}/backoffice/profile/change/verify-new`,
4548
+ {
4549
+ method: "POST",
4550
+ headers: getAuthHeaders(true),
4551
+ body: JSON.stringify({ request_id: requestId, otp_code: otpCode })
4552
+ },
4553
+ 3e4
4554
+ );
4555
+ },
4556
+ async resendOTP(requestId) {
4557
+ return fetchWithTimeout(
4558
+ `${getBaseUrl()}/backoffice/profile/change/resend-otp`,
4559
+ {
4560
+ method: "POST",
4561
+ headers: getAuthHeaders(true),
4562
+ body: JSON.stringify({ request_id: requestId })
4563
+ },
4564
+ 3e4
4565
+ );
4566
+ },
4567
+ async switchMethod(requestId, method) {
4568
+ return fetchWithTimeout(
4569
+ `${getBaseUrl()}/backoffice/profile/change/switch-method`,
4570
+ {
4571
+ method: "POST",
4572
+ headers: getAuthHeaders(true),
4573
+ body: JSON.stringify({ request_id: requestId, method })
4574
+ },
4575
+ 3e4
4576
+ );
4577
+ }
4578
+ };
4579
+ const MIN_SIZE = 88;
4580
+ const HANDLE = 16;
4581
+ const OUTPUT = 512;
4582
+ function clamp(value, min, max) {
4583
+ return Math.max(min, Math.min(max, value));
4584
+ }
4585
+ function getContainRect(container, natural) {
4586
+ if (!container.width || !container.height || !natural.width || !natural.height) {
4587
+ return { x: 0, y: 0, width: 0, height: 0, scale: 1 };
4588
+ }
4589
+ const scale = Math.min(container.width / natural.width, container.height / natural.height);
4590
+ const width = natural.width * scale;
4591
+ const height = natural.height * scale;
4592
+ return {
4593
+ x: (container.width - width) / 2,
4594
+ y: (container.height - height) / 2,
4595
+ width,
4596
+ height,
4597
+ scale
4598
+ };
4599
+ }
4600
+ function AvatarCropModal({ open, imageSrc, onOpenChange, onCancel, onConfirm }) {
4601
+ const wrapRef = useRef(null);
4602
+ const imgRef = useRef(null);
4603
+ const dragRef = useRef(null);
4604
+ const [loading, setLoading] = useState(false);
4605
+ const [error, setError] = useState("");
4606
+ const [natural, setNatural] = useState({ width: 0, height: 0 });
4607
+ const [container, setContainer] = useState({ width: 0, height: 0 });
4608
+ const [crop, setCrop] = useState(null);
4609
+ const [dragMode, setDragMode] = useState(null);
4610
+ const frame = useMemo(() => getContainRect(container, natural), [container, natural]);
4611
+ useEffect(() => {
4612
+ if (!open) return;
4613
+ setLoading(false);
4614
+ setError("");
4615
+ setNatural({ width: 0, height: 0 });
4616
+ setCrop(null);
4617
+ setDragMode(null);
4618
+ dragRef.current = null;
4619
+ }, [open, imageSrc]);
4620
+ useEffect(() => {
4621
+ if (!open) return;
4622
+ const measure = () => {
4623
+ const el = wrapRef.current;
4624
+ if (!el) return;
4625
+ const rect = el.getBoundingClientRect();
4626
+ setContainer({ width: rect.width, height: rect.width });
4627
+ };
4628
+ measure();
4629
+ window.addEventListener("resize", measure);
4630
+ return () => window.removeEventListener("resize", measure);
4631
+ }, [open]);
4632
+ useEffect(() => {
4633
+ if (!open || !frame.width || !frame.height) return;
4634
+ setCrop((current) => {
4635
+ if (current) {
4636
+ const maxSize2 = Math.min(frame.width, frame.height);
4637
+ const size2 = clamp(current.size, MIN_SIZE, maxSize2);
4638
+ const x = clamp(current.x, frame.x, frame.x + frame.width - size2);
4639
+ const y = clamp(current.y, frame.y, frame.y + frame.height - size2);
4640
+ return { x, y, size: size2 };
4641
+ }
4642
+ const maxSize = Math.min(frame.width, frame.height);
4643
+ const size = clamp(Math.round(maxSize * 0.7), MIN_SIZE, maxSize);
4644
+ return {
4645
+ x: frame.x + Math.round((frame.width - size) / 2),
4646
+ y: frame.y + Math.round((frame.height - size) / 2),
4647
+ size
4648
+ };
4649
+ });
4650
+ }, [open, frame]);
4651
+ const startDrag = (mode, event) => {
4652
+ if (!crop || !mode) return;
4653
+ event.preventDefault();
4654
+ event.stopPropagation();
4655
+ dragRef.current = {
4656
+ mode,
4657
+ pointerId: event.pointerId,
4658
+ start: { x: event.clientX, y: event.clientY },
4659
+ startCrop: crop
4660
+ };
4661
+ setDragMode(mode);
4662
+ try {
4663
+ event.currentTarget.setPointerCapture(event.pointerId);
4664
+ } catch {
4665
+ }
4666
+ };
4667
+ const moveCrop = (clientX, clientY) => {
4668
+ const state = dragRef.current;
4669
+ if (!state || !crop || !frame.width || !frame.height) return;
4670
+ const dx = clientX - state.start.x;
4671
+ const dy = clientY - state.start.y;
4672
+ const maxSize = Math.min(frame.width, frame.height);
4673
+ if (state.mode === "move") {
4674
+ setCrop({
4675
+ ...state.startCrop,
4676
+ x: clamp(state.startCrop.x + dx, frame.x, frame.x + frame.width - state.startCrop.size),
4677
+ y: clamp(state.startCrop.y + dy, frame.y, frame.y + frame.height - state.startCrop.size)
4678
+ });
4679
+ return;
4680
+ }
4681
+ let next = { ...state.startCrop };
4682
+ const anchorLeft = state.startCrop.x;
4683
+ const anchorTop = state.startCrop.y;
4684
+ const anchorRight = state.startCrop.x + state.startCrop.size;
4685
+ const anchorBottom = state.startCrop.y + state.startCrop.size;
4686
+ if (state.mode === "resize-se") {
4687
+ const size = clamp(Math.max(state.startCrop.size + dx, state.startCrop.size + dy), MIN_SIZE, maxSize);
4688
+ next = { x: anchorLeft, y: anchorTop, size };
4689
+ } else if (state.mode === "resize-sw") {
4690
+ const size = clamp(Math.max(state.startCrop.size - dx, state.startCrop.size + dy), MIN_SIZE, maxSize);
4691
+ next = { x: anchorRight - size, y: anchorTop, size };
4692
+ } else if (state.mode === "resize-ne") {
4693
+ const size = clamp(Math.max(state.startCrop.size + dx, state.startCrop.size - dy), MIN_SIZE, maxSize);
4694
+ next = { x: anchorLeft, y: anchorBottom - size, size };
4695
+ } else if (state.mode === "resize-nw") {
4696
+ const size = clamp(Math.max(state.startCrop.size - dx, state.startCrop.size - dy), MIN_SIZE, maxSize);
4697
+ next = { x: anchorRight - size, y: anchorBottom - size, size };
4698
+ }
4699
+ next.x = clamp(next.x, frame.x, frame.x + frame.width - next.size);
4700
+ next.y = clamp(next.y, frame.y, frame.y + frame.height - next.size);
4701
+ setCrop(next);
4702
+ };
4703
+ useEffect(() => {
4704
+ if (!dragMode) return;
4705
+ const onMove = (event) => {
4706
+ if (!dragRef.current || event.pointerId !== dragRef.current.pointerId) return;
4707
+ moveCrop(event.clientX, event.clientY);
4708
+ };
4709
+ const onUp = (event) => {
4710
+ if (!dragRef.current || event.pointerId !== dragRef.current.pointerId) return;
4711
+ dragRef.current = null;
4712
+ setDragMode(null);
4713
+ window.removeEventListener("pointermove", onMove);
4714
+ window.removeEventListener("pointerup", onUp);
4715
+ window.removeEventListener("pointercancel", onUp);
4716
+ };
4717
+ window.addEventListener("pointermove", onMove);
4718
+ window.addEventListener("pointerup", onUp);
4719
+ window.addEventListener("pointercancel", onUp);
4720
+ return () => {
4721
+ window.removeEventListener("pointermove", onMove);
4722
+ window.removeEventListener("pointerup", onUp);
4723
+ window.removeEventListener("pointercancel", onUp);
4724
+ };
4725
+ }, [dragMode]);
4726
+ const handleConfirm = async () => {
4727
+ if (!imageSrc || !imgRef.current || !crop || !natural.width || !natural.height) {
4728
+ setError("L’image n’est pas encore prête.");
4729
+ return;
4730
+ }
4731
+ const canvas = document.createElement("canvas");
4732
+ canvas.width = OUTPUT;
4733
+ canvas.height = OUTPUT;
4734
+ const ctx = canvas.getContext("2d");
4735
+ if (!ctx) {
4736
+ setError("Impossible de préparer le recadrage.");
4737
+ return;
4738
+ }
4739
+ const sx = (crop.x - frame.x) / frame.scale;
4740
+ const sy = (crop.y - frame.y) / frame.scale;
4741
+ const sSize = crop.size / frame.scale;
4742
+ setLoading(true);
4743
+ setError("");
4744
+ try {
4745
+ ctx.drawImage(imgRef.current, sx, sy, sSize, sSize, 0, 0, OUTPUT, OUTPUT);
4746
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/jpeg", 0.92));
4747
+ if (!blob) {
4748
+ throw new Error("Impossible de générer l’image recadrée.");
4749
+ }
4750
+ await onConfirm(blob);
4751
+ } catch (err) {
4752
+ setError(err instanceof Error ? err.message : "Erreur lors de la validation.");
4753
+ } finally {
4754
+ setLoading(false);
4755
+ }
4756
+ };
4757
+ const overlayStyle = {
4758
+ position: "absolute",
4759
+ inset: 0,
4760
+ cursor: dragMode ? "grabbing" : "grab",
4761
+ touchAction: "none"
4762
+ };
4763
+ const cropStyle = crop ? {
4764
+ position: "absolute",
4765
+ left: `${crop.x}px`,
4766
+ top: `${crop.y}px`,
4767
+ width: `${crop.size}px`,
4768
+ height: `${crop.size}px`,
4769
+ border: "2px solid #ffffff",
4770
+ borderRadius: "0.5rem",
4771
+ boxShadow: "0 0 0 9999px rgba(0, 0, 0, 0.40)",
4772
+ boxSizing: "border-box",
4773
+ touchAction: "none"
4774
+ } : { display: "none" };
4775
+ const handleOuter = {
4776
+ position: "absolute",
4777
+ width: `${HANDLE * 2}px`,
4778
+ height: `${HANDLE * 2}px`,
4779
+ display: "flex",
4780
+ alignItems: "center",
4781
+ justifyContent: "center",
4782
+ zIndex: 2,
4783
+ touchAction: "none",
4784
+ userSelect: "none"
4785
+ };
4786
+ const handleInner = {
4787
+ width: `${HANDLE}px`,
4788
+ height: `${HANDLE}px`,
4789
+ borderRadius: "999px",
4790
+ border: "2px solid #002147",
4791
+ background: "#fff",
4792
+ boxShadow: "0 1px 2px rgba(0,0,0,0.18)"
4793
+ };
4794
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => {
4795
+ if (!next) onCancel();
4796
+ onOpenChange(next);
4797
+ }, children: /* @__PURE__ */ jsxs(DialogContent, { style: { maxWidth: "28rem", padding: "0.8rem", maxHeight: "90vh" }, children: [
4798
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
4799
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Recadrer la photo" }),
4800
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Déplacez le carré, puis validez pour enregistrer l’avatar." })
4801
+ ] }),
4802
+ /* @__PURE__ */ jsxs(DialogBody, { style: { maxHeight: "64vh", overflowY: "auto", paddingBottom: "0.25rem" }, children: [
4803
+ /* @__PURE__ */ jsxs(
4804
+ "div",
4805
+ {
4806
+ ref: wrapRef,
4807
+ style: {
4808
+ position: "relative",
4809
+ width: "100%",
4810
+ aspectRatio: "1 / 1",
4811
+ overflow: "hidden",
4812
+ borderRadius: "0.8rem",
4813
+ border: "1px solid #e5e7eb",
4814
+ background: "#0f172a"
4815
+ },
4816
+ children: [
4817
+ imageSrc && /* @__PURE__ */ jsx(
4818
+ "img",
4819
+ {
4820
+ ref: imgRef,
4821
+ src: imageSrc,
4822
+ alt: "Aperçu",
4823
+ onLoad: (e) => {
4824
+ setNatural({ width: e.currentTarget.naturalWidth, height: e.currentTarget.naturalHeight });
4825
+ },
4826
+ style: {
4827
+ position: "absolute",
4828
+ inset: 0,
4829
+ width: "100%",
4830
+ height: "100%",
4831
+ objectFit: "contain",
4832
+ userSelect: "none",
4833
+ pointerEvents: "none"
4834
+ }
4835
+ }
4836
+ ),
4837
+ crop && /* @__PURE__ */ jsx("div", { style: overlayStyle, children: /* @__PURE__ */ jsxs("div", { style: cropStyle, onPointerDown: (e) => startDrag("move", e), children: [
4838
+ /* @__PURE__ */ jsx("div", { style: { ...handleOuter, left: `-${HANDLE * 0.5}px`, top: `-${HANDLE * 0.5}px`, cursor: "nwse-resize" }, onPointerDown: (e) => startDrag("resize-nw", e), children: /* @__PURE__ */ jsx("div", { style: handleInner }) }),
4839
+ /* @__PURE__ */ jsx("div", { style: { ...handleOuter, right: `-${HANDLE * 0.5}px`, top: `-${HANDLE * 0.5}px`, cursor: "nesw-resize" }, onPointerDown: (e) => startDrag("resize-ne", e), children: /* @__PURE__ */ jsx("div", { style: handleInner }) }),
4840
+ /* @__PURE__ */ jsx("div", { style: { ...handleOuter, left: `-${HANDLE * 0.5}px`, bottom: `-${HANDLE * 0.5}px`, cursor: "nesw-resize" }, onPointerDown: (e) => startDrag("resize-sw", e), children: /* @__PURE__ */ jsx("div", { style: handleInner }) }),
4841
+ /* @__PURE__ */ jsx("div", { style: { ...handleOuter, right: `-${HANDLE * 0.5}px`, bottom: `-${HANDLE * 0.5}px`, cursor: "nwse-resize" }, onPointerDown: (e) => startDrag("resize-se", e), children: /* @__PURE__ */ jsx("div", { style: handleInner }) })
4842
+ ] }) })
4843
+ ]
4844
+ }
4845
+ ),
4846
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "0.9rem", display: "grid", gap: "0.6rem" }, children: [
4847
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.8rem", color: "#6b7280", lineHeight: 1.4 }, children: "Faites glisser le carré pour cadrer l’image. Les coins redimensionnent le carré." }),
4848
+ error && /* @__PURE__ */ jsx("div", { style: { padding: "0.65rem 0.75rem", borderRadius: "0.5rem", border: "1px solid #fecaca", background: "#fef2f2", color: "#b91c1c", fontSize: "0.85rem", lineHeight: 1.4 }, children: error })
4849
+ ] })
4850
+ ] }),
4851
+ /* @__PURE__ */ jsxs(DialogFooter, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: [
4852
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onCancel, disabled: loading, style: { minWidth: "6.5rem", height: "2.35rem" }, children: "Annuler" }),
4853
+ /* @__PURE__ */ jsx(Button, { onClick: handleConfirm, disabled: !imageSrc || loading || !crop, style: { minWidth: "7rem", height: "2.35rem" }, children: loading ? "Chargement..." : "Valider" })
4854
+ ] })
4855
+ ] }) });
4856
+ }
4857
+ const profileMediaService = {
4858
+ async uploadProfileImage(image, filename = "avatar.jpg") {
4859
+ const baseUrl = getIamApiBaseUrl();
4860
+ const token = getAuthToken();
4861
+ if (!token) {
4862
+ throw new Error("Token manquant");
4863
+ }
4864
+ const formData = new FormData();
4865
+ formData.append("image", image, filename);
4866
+ const headers = getHeaders(token, true);
4867
+ delete headers["Content-Type"];
4868
+ return fetchWithTimeout(
4869
+ `${baseUrl}/backoffice/profile/image`,
4870
+ {
4871
+ method: "POST",
4872
+ headers,
4873
+ body: formData
4874
+ },
4875
+ 3e4
4876
+ );
4877
+ }
4878
+ };
4879
+ const profileService = {
4880
+ async getProfile() {
4881
+ const baseUrl = getIamApiBaseUrl();
4882
+ const token = getAuthToken();
4883
+ if (!token) {
4884
+ throw new Error("Token manquant");
4885
+ }
4886
+ return fetchWithTimeout(
4887
+ `${baseUrl}/backoffice/profile`,
4888
+ {
4889
+ method: "GET",
4890
+ headers: getHeaders(token, true)
4891
+ },
4892
+ 2e4
4893
+ );
4894
+ },
4895
+ async updateProfile(data) {
4896
+ const baseUrl = getIamApiBaseUrl();
4897
+ const token = getAuthToken();
4898
+ if (!token) {
4899
+ throw new Error("Token manquant");
4900
+ }
4901
+ return fetchWithTimeout(
4902
+ `${baseUrl}/backoffice/profile`,
4903
+ {
4904
+ method: "PUT",
4905
+ headers: getHeaders(token, true),
4906
+ body: JSON.stringify(data)
4907
+ },
4908
+ 3e4
4909
+ );
4910
+ }
4911
+ };
4276
4912
  const C = {
4277
4913
  primary: "#002147",
4278
4914
  accent: "#e8430a",
@@ -4282,21 +4918,87 @@ const C = {
4282
4918
  gray700: "#374151",
4283
4919
  white: "#ffffff"
4284
4920
  };
4285
- function OnboardingModal({ open, onOpenChange, user, onComplete, onSkip }) {
4286
- const needsPhoto = !user.image_url;
4287
- const needsPhone = !user.phone;
4288
- const needsEmail = !user.email;
4921
+ function OnboardingModal({ open, onOpenChange, onDismiss, user, variant = "missing", profileHydrating = false, onComplete, onSkip }) {
4922
+ var _a;
4923
+ const isEditMode = variant === "edit";
4924
+ const hasCurrentPhone = Boolean(user.phone);
4925
+ const hasCurrentEmail = Boolean(user.email);
4926
+ const directPhoneEdit = !isEditMode || !hasCurrentPhone;
4927
+ const directEmailEdit = !isEditMode || !hasCurrentEmail;
4928
+ !isEditMode ? !((_a = user.name) == null ? void 0 : _a.trim()) : true;
4929
+ const [name, setName] = useState(user.name || "");
4289
4930
  const [photoPreview, setPhotoPreview] = useState("");
4290
- const [photoFile, setPhotoFile] = useState(null);
4931
+ const [avatarPickerOpen, setAvatarPickerOpen] = useState(false);
4932
+ const [pendingAvatarSrc, setPendingAvatarSrc] = useState(null);
4933
+ const [avatarUploading, setAvatarUploading] = useState(false);
4934
+ const avatarFileInputRef = useRef(null);
4291
4935
  const [ccphone, setCcphone] = useState(user.ccphone || "+221");
4292
4936
  const [phone, setPhone] = useState(user.phone || "");
4293
- const [email, setEmail] = useState("");
4294
- const [confirmed, setConfirmed] = useState(false);
4937
+ const [email, setEmail] = useState(user.email || "");
4938
+ const [address, setAddress] = useState(user.address || "");
4939
+ const [town, setTown] = useState(user.town || "");
4940
+ const [country, setCountry] = useState(user.country || DEFAULT_COUNTRY_CODE);
4295
4941
  const [submitting, setSubmitting] = useState(false);
4296
4942
  const [fileError, setFileError] = useState("");
4297
- const handleFileChange = useCallback((e) => {
4298
- var _a;
4299
- const file = (_a = e.target.files) == null ? void 0 : _a[0];
4943
+ const [contactFlow, setContactFlow] = useState(null);
4944
+ const [submitState, setSubmitState] = useState("idle");
4945
+ const [submitMessage, setSubmitMessage] = useState("");
4946
+ const [submitError, setSubmitError] = useState("");
4947
+ const [confirmAction, setConfirmAction] = useState(null);
4948
+ const [nowTick, setNowTick] = useState(Date.now());
4949
+ const userHydrationKey = [
4950
+ user.reference || "",
4951
+ user.name || "",
4952
+ user.email || "",
4953
+ user.ccphone || "",
4954
+ user.phone || "",
4955
+ user.address || "",
4956
+ user.town || "",
4957
+ user.country || "",
4958
+ user.image_url || "",
4959
+ user.auth_2fa ? "1" : "0"
4960
+ ].join("|");
4961
+ const currentDialCode = (user.ccphone || ccphone || "+221").trim();
4962
+ const baseSmsAllowed = currentDialCode === "+221";
4963
+ const contactSmsAllowed = contactFlow ? contactFlow.kind === "phone" ? contactFlow.newCcphone.trim() === "+221" : baseSmsAllowed : baseSmsAllowed;
4964
+ const lockModalClose = profileHydrating || submitting || submitState === "success" || avatarUploading || Boolean(contactFlow == null ? void 0 : contactFlow.loading);
4965
+ useEffect(() => {
4966
+ if (!open) return;
4967
+ setName(user.name || "");
4968
+ setPhotoPreview(isEditMode ? user.image_url || "" : "");
4969
+ setPendingAvatarSrc(null);
4970
+ setAvatarPickerOpen(false);
4971
+ setAvatarUploading(false);
4972
+ setCcphone(user.ccphone || "+221");
4973
+ setPhone(user.phone || "");
4974
+ setEmail(user.email || "");
4975
+ setAddress(user.address || "");
4976
+ setTown(user.town || "");
4977
+ setCountry(user.country || DEFAULT_COUNTRY_CODE);
4978
+ setSubmitting(false);
4979
+ setFileError("");
4980
+ setContactFlow(null);
4981
+ setSubmitState("idle");
4982
+ setSubmitMessage("");
4983
+ setSubmitError("");
4984
+ setConfirmAction(null);
4985
+ }, [open, isEditMode, userHydrationKey]);
4986
+ useEffect(() => {
4987
+ if (!(contactFlow == null ? void 0 : contactFlow.resendAvailableAt)) return;
4988
+ const timer = window.setInterval(() => {
4989
+ setNowTick(Date.now());
4990
+ }, 1e3);
4991
+ return () => window.clearInterval(timer);
4992
+ }, [contactFlow == null ? void 0 : contactFlow.resendAvailableAt]);
4993
+ const markSuccess = useCallback((message) => {
4994
+ setSubmitting(false);
4995
+ setSubmitState("success");
4996
+ setSubmitMessage(message);
4997
+ setSubmitError("");
4998
+ }, []);
4999
+ useCallback((e) => {
5000
+ var _a2;
5001
+ const file = (_a2 = e.target.files) == null ? void 0 : _a2[0];
4300
5002
  if (!file) return;
4301
5003
  if (file.size > 2 * 1024 * 1024) {
4302
5004
  setFileError("Le fichier dépasse 2 Mo. Veuillez choisir une image plus légère.");
@@ -4304,87 +5006,524 @@ function OnboardingModal({ open, onOpenChange, user, onComplete, onSkip }) {
4304
5006
  return;
4305
5007
  }
4306
5008
  setFileError("");
4307
- setPhotoFile(file);
4308
5009
  const reader = new FileReader();
4309
5010
  reader.onload = () => setPhotoPreview(reader.result);
4310
5011
  reader.readAsDataURL(file);
4311
5012
  }, []);
4312
- const canSubmit = confirmed && ((needsPhoto ? !!photoPreview : true) && (needsPhone ? phone.length >= 7 : true));
4313
- const handleSubmit = useCallback(() => {
5013
+ const canSubmit = !avatarUploading && (name.trim().length > 0 && (directPhoneEdit ? phone.length >= 7 : true) && (directEmailEdit ? email.trim().length > 0 : true) && town.trim().length > 0);
5014
+ const handleSubmit = useCallback(async () => {
4314
5015
  if (!canSubmit) return;
4315
5016
  setSubmitting(true);
4316
- const data = {};
4317
- if (needsPhoto && photoPreview) {
4318
- data.image_url = photoPreview;
5017
+ setSubmitState("submitting");
5018
+ setSubmitError("");
5019
+ const data = {
5020
+ name: name.trim(),
5021
+ ccphone: directPhoneEdit ? ccphone : user.ccphone || void 0,
5022
+ phone: directPhoneEdit ? phone : user.phone || void 0,
5023
+ email: directEmailEdit ? email.trim() : user.email || void 0,
5024
+ ...address.trim() ? { address: address.trim() } : {},
5025
+ ...town.trim() ? { town: town.trim() } : {},
5026
+ ...country ? { country } : {}
5027
+ };
5028
+ try {
5029
+ const response = await profileService.updateProfile(data);
5030
+ const payload = response.user_infos || response.user || {};
5031
+ const updatedUser = {
5032
+ ...user,
5033
+ name: payload.name || data.name || user.name,
5034
+ email: payload.email ?? data.email ?? user.email,
5035
+ ccphone: payload.ccphone || data.ccphone || user.ccphone,
5036
+ phone: payload.phone || data.phone || user.phone,
5037
+ address: payload.address || data.address || user.address,
5038
+ town: payload.town || data.town || user.town,
5039
+ country: payload.country || data.country || user.country,
5040
+ image_url: payload.image_url || user.image_url,
5041
+ auth_2fa: payload.auth_2fa ?? user.auth_2fa,
5042
+ alias_reference: payload.alias_reference || user.alias_reference,
5043
+ iam_reference: payload.iam_reference || user.iam_reference
5044
+ };
5045
+ onComplete({
5046
+ name: updatedUser.name,
5047
+ image_url: updatedUser.image_url,
5048
+ ccphone: updatedUser.ccphone,
5049
+ phone: updatedUser.phone,
5050
+ email: updatedUser.email || void 0,
5051
+ address: updatedUser.address,
5052
+ town: updatedUser.town,
5053
+ country: updatedUser.country,
5054
+ user_infos: {
5055
+ reference: updatedUser.reference,
5056
+ name: updatedUser.name,
5057
+ email: updatedUser.email || null,
5058
+ ccphone: updatedUser.ccphone,
5059
+ phone: updatedUser.phone,
5060
+ address: updatedUser.address,
5061
+ town: updatedUser.town,
5062
+ country: updatedUser.country,
5063
+ image_url: updatedUser.image_url,
5064
+ auth_2fa: updatedUser.auth_2fa,
5065
+ alias_reference: updatedUser.alias_reference,
5066
+ iam_reference: updatedUser.iam_reference
5067
+ }
5068
+ });
5069
+ markSuccess(response.message || "Mise à jour réussie");
5070
+ } catch (error) {
5071
+ setSubmitting(false);
5072
+ setSubmitState("error");
5073
+ setSubmitError(error instanceof Error ? error.message : "Erreur lors de l’enregistrement");
5074
+ }
5075
+ }, [canSubmit, name, user, directPhoneEdit, directEmailEdit, ccphone, phone, email, address, town, country, onComplete, markSuccess]);
5076
+ const openAvatarPicker = useCallback(() => {
5077
+ var _a2;
5078
+ (_a2 = avatarFileInputRef.current) == null ? void 0 : _a2.click();
5079
+ }, []);
5080
+ const handleAvatarSelected = useCallback((event) => {
5081
+ var _a2;
5082
+ const file = (_a2 = event.target.files) == null ? void 0 : _a2[0];
5083
+ if (!file) return;
5084
+ const reader = new FileReader();
5085
+ reader.onload = () => {
5086
+ setPendingAvatarSrc(reader.result);
5087
+ setAvatarPickerOpen(true);
5088
+ };
5089
+ reader.readAsDataURL(file);
5090
+ event.target.value = "";
5091
+ }, []);
5092
+ const handleAvatarConfirm = useCallback(async (blob) => {
5093
+ setAvatarUploading(true);
5094
+ try {
5095
+ const filename = `avatar-${Date.now()}.jpg`;
5096
+ const response = await profileMediaService.uploadProfileImage(blob, filename);
5097
+ const payload = response.user_infos || response.user || {};
5098
+ const nextImage = payload.image_url || payload.image;
5099
+ if (nextImage) {
5100
+ setPhotoPreview(nextImage);
5101
+ } else {
5102
+ setPhotoPreview(URL.createObjectURL(blob));
5103
+ }
5104
+ if (Object.keys(payload).length > 0) {
5105
+ onComplete({
5106
+ name: payload.name,
5107
+ image_url: nextImage || void 0,
5108
+ ccphone: payload.ccphone || void 0,
5109
+ phone: payload.phone || void 0,
5110
+ email: payload.email || void 0,
5111
+ address: payload.address || void 0,
5112
+ town: payload.town || void 0,
5113
+ country: payload.country || void 0,
5114
+ user_infos: {
5115
+ reference: payload.reference || user.reference,
5116
+ name: payload.name || user.name,
5117
+ email: payload.email ?? user.email ?? null,
5118
+ ccphone: payload.ccphone || user.ccphone,
5119
+ phone: payload.phone || user.phone,
5120
+ address: payload.address || user.address,
5121
+ town: payload.town || user.town,
5122
+ country: payload.country || user.country,
5123
+ image_url: nextImage || user.image_url,
5124
+ auth_2fa: payload.auth_2fa ?? user.auth_2fa,
5125
+ alias_reference: payload.alias_reference || user.alias_reference,
5126
+ iam_reference: payload.iam_reference || user.iam_reference
5127
+ }
5128
+ });
5129
+ }
5130
+ markSuccess("Mise à jour réussie");
5131
+ setPendingAvatarSrc(null);
5132
+ setAvatarPickerOpen(false);
5133
+ } finally {
5134
+ setAvatarUploading(false);
5135
+ }
5136
+ }, [onComplete, markSuccess]);
5137
+ const openContactFlow = useCallback((kind) => {
5138
+ setContactFlow({
5139
+ kind,
5140
+ phase: "request",
5141
+ requestId: null,
5142
+ method: kind === "email" ? "email" : currentDialCode === "+221" ? "phone" : "email",
5143
+ newEmail: "",
5144
+ newCcphone: kind === "phone" ? user.ccphone || "+221" : "+221",
5145
+ newPhone: "",
5146
+ oldOtp: "",
5147
+ newOtp: "",
5148
+ loading: false,
5149
+ error: "",
5150
+ resendAvailableAt: null
5151
+ });
5152
+ }, [currentDialCode, user.email, user.phone, user.ccphone]);
5153
+ const closeContactFlow = useCallback(() => {
5154
+ setContactFlow(null);
5155
+ }, []);
5156
+ const updateFlow = useCallback((patch) => {
5157
+ setContactFlow((prev) => prev ? { ...prev, ...patch } : prev);
5158
+ }, []);
5159
+ const currentPhoneDialDisplay = user.ccphone || ccphone || "+221";
5160
+ const currentPhoneNumberDisplay = user.phone || phone || "";
5161
+ const currentEmailLabel = user.email || email || "";
5162
+ const currentPhoneDialCode = currentPhoneDialDisplay.trim() || "+221";
5163
+ const currentPhoneDigits = (user.phone || phone || "").replace(/\D/g, "");
5164
+ const formatPhoneDisplay = useCallback((dial, digits) => {
5165
+ const cleanDial = (dial || "+221").trim() || "+221";
5166
+ const cleanDigits = (digits || "").replace(/\D/g, "");
5167
+ return cleanDigits ? `${cleanDial} ${cleanDigits}` : cleanDial;
5168
+ }, []);
5169
+ const currentEmailNormalized = currentEmailLabel.trim().toLowerCase();
5170
+ const requestEmailNormalized = (contactFlow == null ? void 0 : contactFlow.kind) === "email" ? contactFlow.newEmail.trim().toLowerCase() : "";
5171
+ const requestPhoneDialCode = (contactFlow == null ? void 0 : contactFlow.kind) === "phone" ? contactFlow.newCcphone.trim() || "+221" : "";
5172
+ const requestPhoneDigits = (contactFlow == null ? void 0 : contactFlow.kind) === "phone" ? contactFlow.newPhone.replace(/\D/g, "") : "";
5173
+ const emailIsCurrent = Boolean((contactFlow == null ? void 0 : contactFlow.kind) === "email" && requestEmailNormalized && requestEmailNormalized === currentEmailNormalized);
5174
+ const phoneIsCurrent = Boolean(
5175
+ (contactFlow == null ? void 0 : contactFlow.kind) === "phone" && requestPhoneDigits && requestPhoneDialCode === currentPhoneDialCode && requestPhoneDigits === currentPhoneDigits
5176
+ );
5177
+ const requestDuplicateMessage = emailIsCurrent ? "Vous ne pouvez pas écrire votre actuelle adresse email. Veuillez en choisir une autre." : phoneIsCurrent ? "Vous ne pouvez pas écrire votre actuel numéro de téléphone. Veuillez en choisir un autre." : "";
5178
+ const requestChange = useCallback(async () => {
5179
+ if (!contactFlow || contactFlow.loading) return;
5180
+ const sameEmail = contactFlow.kind === "email" && contactFlow.newEmail.trim().toLowerCase() === currentEmailNormalized;
5181
+ const samePhone = contactFlow.kind === "phone" && contactFlow.newCcphone.trim() === currentPhoneDialCode && contactFlow.newPhone.replace(/\D/g, "") === currentPhoneDigits;
5182
+ if (sameEmail || samePhone) {
5183
+ updateFlow({
5184
+ loading: false,
5185
+ error: sameEmail ? "Vous ne pouvez pas écrire votre actuelle adresse email. Veuillez en choisir une autre." : "Vous ne pouvez pas écrire votre actuel numéro de téléphone. Veuillez en choisir un autre."
5186
+ });
5187
+ return;
4319
5188
  }
4320
- if (needsPhone && phone) {
4321
- data.ccphone = ccphone;
4322
- data.phone = phone;
5189
+ updateFlow({ loading: true, error: "" });
5190
+ try {
5191
+ const method = contactSmsAllowed ? contactFlow.method : "email";
5192
+ const response = contactFlow.kind === "email" ? await profileChangeService.requestEmailChange(contactFlow.newEmail.trim(), method) : await profileChangeService.requestPhoneChange(contactFlow.newCcphone, contactFlow.newPhone.trim(), method);
5193
+ if (!response.success || !response.request_id) {
5194
+ throw new Error(response.message || "Impossible de démarrer le changement");
5195
+ }
5196
+ updateFlow({ requestId: response.request_id, phase: "verify-old", loading: false, error: "", resendAvailableAt: Date.now() + 12e4 });
5197
+ } catch (error) {
5198
+ updateFlow({ loading: false, error: error instanceof Error ? error.message : "Erreur inconnue" });
5199
+ }
5200
+ }, [contactFlow, updateFlow, contactSmsAllowed, currentEmailNormalized, currentPhoneDialCode, currentPhoneDigits]);
5201
+ const verifyOld = useCallback(async () => {
5202
+ if (!(contactFlow == null ? void 0 : contactFlow.requestId) || contactFlow.loading) return;
5203
+ updateFlow({ loading: true, error: "" });
5204
+ try {
5205
+ const response = await profileChangeService.verifyOldOTP(contactFlow.requestId, contactFlow.oldOtp);
5206
+ if (!response.success) throw new Error(response.message || "Code OTP incorrect");
5207
+ updateFlow({ phase: "verify-new", loading: false, error: "", resendAvailableAt: Date.now() + 12e4 });
5208
+ } catch (error) {
5209
+ updateFlow({ loading: false, error: error instanceof Error ? error.message : "Code OTP incorrect" });
5210
+ }
5211
+ }, [contactFlow, updateFlow]);
5212
+ const verifyNew = useCallback(async () => {
5213
+ if (!(contactFlow == null ? void 0 : contactFlow.requestId) || contactFlow.loading) return;
5214
+ updateFlow({ loading: true, error: "" });
5215
+ try {
5216
+ const response = await profileChangeService.verifyNewOTP(contactFlow.requestId, contactFlow.newOtp);
5217
+ if (!response.success) throw new Error(response.message || "Code OTP incorrect");
5218
+ const updatedUser = {
5219
+ ...user,
5220
+ ...contactFlow.kind === "email" ? { email: contactFlow.newEmail.trim() } : { ccphone: contactFlow.newCcphone, phone: contactFlow.newPhone.trim() }
5221
+ };
5222
+ if (response.user && typeof response.user === "object") {
5223
+ Object.assign(updatedUser, response.user);
5224
+ }
5225
+ setContactFlow(null);
5226
+ onComplete({
5227
+ name: updatedUser.name,
5228
+ image_url: updatedUser.image_url,
5229
+ ccphone: updatedUser.ccphone,
5230
+ phone: updatedUser.phone,
5231
+ email: updatedUser.email || void 0,
5232
+ user_infos: {
5233
+ reference: updatedUser.reference,
5234
+ name: updatedUser.name,
5235
+ email: updatedUser.email || null,
5236
+ ccphone: updatedUser.ccphone,
5237
+ phone: updatedUser.phone,
5238
+ address: updatedUser.address,
5239
+ town: updatedUser.town,
5240
+ country: updatedUser.country,
5241
+ image_url: updatedUser.image_url,
5242
+ auth_2fa: updatedUser.auth_2fa,
5243
+ alias_reference: updatedUser.alias_reference,
5244
+ iam_reference: updatedUser.iam_reference
5245
+ }
5246
+ });
5247
+ markSuccess(contactFlow.kind === "email" ? "Email mis à jour avec succès." : "Téléphone mis à jour avec succès.");
5248
+ } catch (error) {
5249
+ updateFlow({ loading: false, error: error instanceof Error ? error.message : "Code OTP incorrect" });
5250
+ }
5251
+ }, [contactFlow, onComplete, updateFlow, user, markSuccess]);
5252
+ const resendFlowOtp = useCallback(async () => {
5253
+ if (!(contactFlow == null ? void 0 : contactFlow.requestId) || contactFlow.loading) return;
5254
+ updateFlow({ loading: true, error: "" });
5255
+ try {
5256
+ const response = await profileChangeService.resendOTP(contactFlow.requestId);
5257
+ if (!response.success) throw new Error(response.message || "Impossible de renvoyer le code");
5258
+ updateFlow({ loading: false, error: "", resendAvailableAt: Date.now() + 12e4 });
5259
+ } catch (error) {
5260
+ updateFlow({ loading: false, error: error instanceof Error ? error.message : "Erreur inconnue" });
5261
+ }
5262
+ }, [contactFlow, updateFlow]);
5263
+ const switchFlowMethod = useCallback(async (method) => {
5264
+ if (!contactFlow) return;
5265
+ if (contactFlow.phase === "request" || !contactFlow.requestId) {
5266
+ updateFlow({ method, error: "" });
5267
+ return;
4323
5268
  }
4324
- if (needsEmail && email.trim()) {
4325
- data.email = email.trim();
5269
+ if (contactFlow.loading) return;
5270
+ updateFlow({ loading: true, error: "", method });
5271
+ try {
5272
+ const response = await profileChangeService.switchMethod(contactFlow.requestId, method);
5273
+ if (!response.success) throw new Error(response.message || "Impossible de changer la méthode");
5274
+ updateFlow({ loading: false, error: "" });
5275
+ } catch (error) {
5276
+ updateFlow({ loading: false, error: error instanceof Error ? error.message : "Erreur inconnue" });
4326
5277
  }
4327
- onComplete(data);
4328
- }, [canSubmit, needsPhoto, needsPhone, needsEmail, photoPreview, ccphone, phone, email, onComplete]);
4329
- const ShieldIcon = () => /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: C.accent, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
4330
- /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }),
4331
- /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
5278
+ }, [contactFlow, updateFlow]);
5279
+ useEffect(() => {
5280
+ if (!contactFlow || contactFlow.kind !== "phone") return;
5281
+ if (contactFlow.newCcphone.trim() !== "+221" && contactFlow.method !== "email") {
5282
+ updateFlow({ method: "email" });
5283
+ }
5284
+ }, [contactFlow == null ? void 0 : contactFlow.kind, contactFlow == null ? void 0 : contactFlow.newCcphone, contactFlow == null ? void 0 : contactFlow.method, updateFlow]);
5285
+ const confirmBackToRequest = useCallback(() => {
5286
+ if (!contactFlow) return;
5287
+ updateFlow({
5288
+ phase: "request",
5289
+ requestId: null,
5290
+ oldOtp: "",
5291
+ newOtp: "",
5292
+ error: "",
5293
+ loading: false,
5294
+ resendAvailableAt: null
5295
+ });
5296
+ setConfirmAction(null);
5297
+ }, [contactFlow, updateFlow]);
5298
+ const confirmResendOtp = useCallback(async () => {
5299
+ if (!contactFlow) return;
5300
+ setConfirmAction(null);
5301
+ await resendFlowOtp();
5302
+ }, [contactFlow, resendFlowOtp]);
5303
+ const handleDialogOpenChange = useCallback((nextOpen) => {
5304
+ if (nextOpen) {
5305
+ onOpenChange(true);
5306
+ return;
5307
+ }
5308
+ if (lockModalClose) return;
5309
+ onOpenChange(false);
5310
+ }, [lockModalClose, onOpenChange]);
5311
+ const contactTargetLabel = contactFlow ? contactFlow.kind === "email" ? contactFlow.newEmail.trim() || currentEmailLabel || "votre adresse email" : formatPhoneDisplay(contactFlow.newCcphone, contactFlow.newPhone) : "";
5312
+ const currentReceiveLabel = (contactFlow ? contactSmsAllowed ? contactFlow.method : "email" : "email") === "phone" ? `SMS au ${(contactFlow == null ? void 0 : contactFlow.kind) === "phone" ? contactTargetLabel : formatPhoneDisplay(currentPhoneDialDisplay, currentPhoneNumberDisplay)}` : `email à ${(contactFlow == null ? void 0 : contactFlow.kind) === "email" ? contactTargetLabel : currentEmailLabel || "votre adresse email"}`;
5313
+ const resendCountdown = (contactFlow == null ? void 0 : contactFlow.resendAvailableAt) ? Math.max(0, Math.ceil((contactFlow.resendAvailableAt - nowTick) / 1e3)) : 0;
5314
+ const canResendOtp = Boolean(contactFlow == null ? void 0 : contactFlow.requestId) && !(contactFlow == null ? void 0 : contactFlow.loading) && resendCountdown === 0;
5315
+ const ShieldIcon = () => /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: C.accent, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
5316
+ /* @__PURE__ */ jsx("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" }),
5317
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
4332
5318
  ] });
4333
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
5319
+ const renderSuccess = () => /* @__PURE__ */ jsxs(Fragment, { children: [
4334
5320
  /* @__PURE__ */ jsxs(DialogHeader, { children: [
4335
5321
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.25rem" }, children: [
4336
5322
  /* @__PURE__ */ jsx(ShieldIcon, {}),
4337
- /* @__PURE__ */ jsx(DialogTitle, { children: "Complétez votre profil" })
5323
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Profil enregistré" })
4338
5324
  ] }),
4339
- /* @__PURE__ */ jsx(DialogDescription, { children: "Ajoutez les informations manquantes pour finaliser votre compte." })
5325
+ /* @__PURE__ */ jsx(DialogDescription, { children: submitMessage || "Vos informations ont été mises à jour." })
4340
5326
  ] }),
4341
- /* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1.25rem", marginTop: "0.75rem" }, children: [
4342
- needsPhoto && /* @__PURE__ */ jsxs("div", { children: [
4343
- /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Photo de profil" }),
4344
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "1rem" }, children: [
4345
- /* @__PURE__ */ jsx("div", { style: {
4346
- width: "4rem",
4347
- height: "4rem",
4348
- borderRadius: "50%",
4349
- backgroundColor: C.gray100,
4350
- border: `2px dashed ${C.gray200}`,
4351
- overflow: "hidden",
4352
- display: "flex",
4353
- alignItems: "center",
4354
- justifyContent: "center",
4355
- flexShrink: 0
4356
- }, children: photoPreview ? /* @__PURE__ */ jsx("img", { src: photoPreview, alt: "Preview", style: { width: "100%", height: "100%", objectFit: "cover" } }) : /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: C.gray500, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
4357
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "4" }),
4358
- /* @__PURE__ */ jsx("path", { d: "M5.5 21a8.38 8.38 0 0 1 13 0" })
4359
- ] }) }),
4360
- /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
4361
- /* @__PURE__ */ jsxs("label", { style: {
4362
- display: "inline-block",
4363
- padding: "0.5rem 1rem",
4364
- backgroundColor: C.gray100,
5327
+ /* @__PURE__ */ jsx(DialogBody, { style: { maxHeight: "52vh", overflowY: "auto", paddingBottom: "0.25rem" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "0.75rem", padding: "1.5rem 1rem", textAlign: "center" }, children: [
5328
+ /* @__PURE__ */ jsxs("svg", { width: "42", height: "42", viewBox: "0 0 24 24", fill: "none", stroke: "#16a34a", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
5329
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
5330
+ /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
5331
+ ] }),
5332
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "1rem", fontWeight: 700, color: C.gray700 }, children: "Mise à jour réussie" }),
5333
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", color: C.gray500 }, children: "Vous pouvez fermer cette fenêtre maintenant." })
5334
+ ] }) }),
5335
+ /* @__PURE__ */ jsx(DialogFooter, { style: { flexDirection: "row", alignItems: "center" }, children: /* @__PURE__ */ jsx(
5336
+ Button,
5337
+ {
5338
+ type: "button",
5339
+ variant: "outline",
5340
+ onClick: onDismiss,
5341
+ style: { width: "100%", height: "2.6rem" },
5342
+ children: "Fermer"
5343
+ }
5344
+ ) })
5345
+ ] });
5346
+ const renderHydration = () => /* @__PURE__ */ jsxs(Fragment, { children: [
5347
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
5348
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.25rem" }, children: [
5349
+ /* @__PURE__ */ jsx(ShieldIcon, {}),
5350
+ /* @__PURE__ */ jsx(DialogTitle, { children: isEditMode ? "Modifier votre profil" : "Complétez votre profil" })
5351
+ ] }),
5352
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Récupération de vos infos depuis iam.ollaid.com en cours. Le SSO synchronise vos données avant l’affichage du formulaire." })
5353
+ ] }),
5354
+ /* @__PURE__ */ jsx(DialogBody, { style: { maxHeight: "52vh", overflowY: "auto", paddingBottom: "0.25rem" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "0.9rem", padding: "2rem 1rem", textAlign: "center" }, children: [
5355
+ /* @__PURE__ */ jsx("svg", { width: "42", height: "42", viewBox: "0 0 24 24", fill: "none", stroke: C.primary, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }),
5356
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "1rem", fontWeight: 700, color: C.gray700 }, children: "Récupération de vos informations" }),
5357
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.875rem", color: C.gray500, lineHeight: 1.5 }, children: "Le modal sera rempli automatiquement dès que les données IAM seront prêtes." })
5358
+ ] }) }),
5359
+ /* @__PURE__ */ jsx(DialogFooter, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: /* @__PURE__ */ jsx(
5360
+ Button,
5361
+ {
5362
+ type: "button",
5363
+ variant: "outline",
5364
+ onClick: () => setConfirmAction("cancel-load"),
5365
+ style: { minWidth: "6.5rem", height: "2.35rem" },
5366
+ children: "Fermer"
5367
+ }
5368
+ ) })
5369
+ ] });
5370
+ const renderBase = () => /* @__PURE__ */ jsxs(Fragment, { children: [
5371
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
5372
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.25rem" }, children: [
5373
+ /* @__PURE__ */ jsx(ShieldIcon, {}),
5374
+ /* @__PURE__ */ jsx(DialogTitle, { children: isEditMode ? "Modifier votre profil" : "Complétez votre profil" })
5375
+ ] }),
5376
+ /* @__PURE__ */ jsx(DialogDescription, { children: isEditMode ? "Mettez à jour vos informations sans quitter le SaaS." : "Ajoutez les informations manquantes pour finaliser votre compte." })
5377
+ ] }),
5378
+ /* @__PURE__ */ jsx(DialogBody, { style: { maxHeight: "52vh", overflowY: "auto", paddingBottom: "0.25rem" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.85rem", marginTop: "0.25rem", paddingRight: "0.25rem" }, children: [
5379
+ submitError && /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.5rem", backgroundColor: "#fef2f2", border: "1px solid #fecaca", color: "#b91c1c", fontSize: "0.875rem" }, children: submitError }),
5380
+ submitState === "submitting" && /* @__PURE__ */ jsxs("div", { style: { padding: "0.75rem", borderRadius: "0.5rem", backgroundColor: "#eff6ff", border: "1px solid #bfdbfe", color: "#1d4ed8", fontSize: "0.875rem", display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
5381
+ /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }),
5382
+ "Enregistrement en cours..."
5383
+ ] }),
5384
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "4.25rem 1fr", gap: "0.75rem", alignItems: "center" }, children: [
5385
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
5386
+ /* @__PURE__ */ jsxs(
5387
+ "button",
5388
+ {
5389
+ type: "button",
5390
+ onClick: openAvatarPicker,
5391
+ disabled: avatarUploading,
5392
+ style: {
5393
+ width: "4.25rem",
5394
+ height: "4.25rem",
5395
+ borderRadius: "50%",
5396
+ border: `1px solid ${C.gray200}`,
5397
+ backgroundColor: C.gray100,
5398
+ overflow: "hidden",
5399
+ position: "relative",
5400
+ padding: 0,
5401
+ cursor: avatarUploading ? "not-allowed" : "pointer"
5402
+ },
5403
+ children: [
5404
+ photoPreview || user.image_url ? /* @__PURE__ */ jsx("img", { src: photoPreview || user.image_url, alt: "Photo de profil", style: { width: "100%", height: "100%", objectFit: "cover" } }) : /* @__PURE__ */ jsx("div", { style: { width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: C.gray500 }, children: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", children: [
5405
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "4" }),
5406
+ /* @__PURE__ */ jsx("path", { d: "M5.5 21a8.38 8.38 0 0 1 13 0" })
5407
+ ] }) }),
5408
+ /* @__PURE__ */ jsx("div", { style: {
5409
+ position: "absolute",
5410
+ inset: 0,
5411
+ display: "flex",
5412
+ alignItems: "center",
5413
+ justifyContent: "center",
5414
+ background: avatarUploading ? "rgba(255,255,255,0.55)" : "rgba(0,0,0,0.08)"
5415
+ }, children: avatarUploading ? /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: C.primary, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }) : /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: C.white, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
5416
+ /* @__PURE__ */ jsx("path", { d: "M15 8h.01" }),
5417
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
5418
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
5419
+ /* @__PURE__ */ jsx("path", { d: "m21 15-5-5L5 21" })
5420
+ ] }) })
5421
+ ]
5422
+ }
5423
+ ),
5424
+ /* @__PURE__ */ jsx("input", { ref: avatarFileInputRef, type: "file", accept: "image/*", onChange: handleAvatarSelected, style: { display: "none" } })
5425
+ ] }),
5426
+ /* @__PURE__ */ jsxs("div", { children: [
5427
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.35rem", color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Nom complet" }),
5428
+ /* @__PURE__ */ jsx(
5429
+ "input",
5430
+ {
5431
+ type: "text",
5432
+ value: name,
5433
+ onChange: (e) => setName(e.target.value),
5434
+ placeholder: "Jean Dupont",
5435
+ style: {
5436
+ width: "100%",
5437
+ height: "2.25rem",
5438
+ padding: "0 0.75rem",
5439
+ border: `1px solid ${C.gray200}`,
5440
+ borderRadius: "0.375rem",
5441
+ fontSize: "0.875rem",
5442
+ color: C.gray700,
5443
+ backgroundColor: C.white,
5444
+ outline: "none"
5445
+ }
5446
+ }
5447
+ )
5448
+ ] })
5449
+ ] }),
5450
+ isEditMode && hasCurrentPhone && !contactFlow && /* @__PURE__ */ jsxs("div", { children: [
5451
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "0.35rem" }, children: [
5452
+ /* @__PURE__ */ jsx(Label, { style: { color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Numéro de téléphone" }),
5453
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => openContactFlow("phone"), style: { border: "none", background: "transparent", padding: 0, color: C.accent, fontWeight: 700, fontSize: "0.8rem", cursor: "pointer" }, children: "Changer" })
5454
+ ] }),
5455
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "6rem 1fr", gap: "0.5rem" }, children: [
5456
+ /* @__PURE__ */ jsx(
5457
+ "input",
5458
+ {
5459
+ type: "text",
5460
+ value: currentPhoneDialDisplay,
5461
+ readOnly: true,
5462
+ style: {
5463
+ width: "100%",
5464
+ height: "2.25rem",
5465
+ padding: "0 0.65rem",
5466
+ border: `1px solid ${C.gray200}`,
5467
+ borderRadius: "0.375rem",
5468
+ fontSize: "0.875rem",
5469
+ color: C.gray700,
5470
+ backgroundColor: "#f9fafb",
5471
+ outline: "none"
5472
+ }
5473
+ }
5474
+ ),
5475
+ /* @__PURE__ */ jsx(
5476
+ "input",
5477
+ {
5478
+ type: "text",
5479
+ value: currentPhoneNumberDisplay,
5480
+ readOnly: true,
5481
+ style: {
5482
+ width: "100%",
5483
+ height: "2.25rem",
5484
+ padding: "0 0.75rem",
5485
+ border: `1px solid ${C.gray200}`,
5486
+ borderRadius: "0.375rem",
5487
+ fontSize: "0.875rem",
5488
+ color: C.gray700,
5489
+ backgroundColor: "#f9fafb",
5490
+ outline: "none"
5491
+ }
5492
+ }
5493
+ )
5494
+ ] })
5495
+ ] }),
5496
+ isEditMode && hasCurrentEmail && !contactFlow && /* @__PURE__ */ jsxs("div", { children: [
5497
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "0.35rem" }, children: [
5498
+ /* @__PURE__ */ jsx(Label, { style: { color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Adresse email" }),
5499
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => openContactFlow("email"), style: { border: "none", background: "transparent", padding: 0, color: C.accent, fontWeight: 700, fontSize: "0.8rem", cursor: "pointer" }, children: "Changer" })
5500
+ ] }),
5501
+ /* @__PURE__ */ jsx(
5502
+ "input",
5503
+ {
5504
+ type: "email",
5505
+ value: currentEmailLabel,
5506
+ readOnly: true,
5507
+ style: {
5508
+ width: "100%",
5509
+ height: "2.25rem",
5510
+ padding: "0 0.75rem",
4365
5511
  border: `1px solid ${C.gray200}`,
4366
5512
  borderRadius: "0.375rem",
4367
- cursor: "pointer",
4368
5513
  fontSize: "0.875rem",
4369
5514
  color: C.gray700,
4370
- fontWeight: 500
4371
- }, children: [
4372
- "Choisir une photo",
4373
- /* @__PURE__ */ jsx("input", { type: "file", accept: "image/*", onChange: handleFileChange, style: { display: "none" } })
4374
- ] }),
4375
- /* @__PURE__ */ jsx("p", { style: { fontSize: "0.75rem", color: fileError ? "#dc2626" : C.gray500, marginTop: "0.25rem" }, children: fileError || "JPG, PNG. Max 2 Mo." })
4376
- ] })
4377
- ] })
5515
+ backgroundColor: "#f9fafb",
5516
+ outline: "none"
5517
+ }
5518
+ }
5519
+ )
4378
5520
  ] }),
4379
- needsPhone && /* @__PURE__ */ jsxs("div", { children: [
4380
- /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Numéro de téléphone" }),
5521
+ (!isEditMode || !hasCurrentPhone) && /* @__PURE__ */ jsxs("div", { children: [
5522
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.35rem", color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Numéro de téléphone" }),
4381
5523
  /* @__PURE__ */ jsx(PhoneInput, { value: phone, onChange: setPhone, ccphone, onCcphoneChange: setCcphone })
4382
5524
  ] }),
4383
- needsEmail && /* @__PURE__ */ jsxs("div", { children: [
4384
- /* @__PURE__ */ jsxs(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: [
4385
- "Adresse email ",
4386
- /* @__PURE__ */ jsx("span", { style: { color: C.gray500, fontWeight: 400 }, children: "(optionnel)" })
4387
- ] }),
5525
+ (!isEditMode || !hasCurrentEmail) && /* @__PURE__ */ jsxs("div", { children: [
5526
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.35rem", color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Adresse email" }),
4388
5527
  /* @__PURE__ */ jsx(
4389
5528
  "input",
4390
5529
  {
@@ -4394,7 +5533,7 @@ function OnboardingModal({ open, onOpenChange, user, onComplete, onSkip }) {
4394
5533
  placeholder: "email@exemple.com",
4395
5534
  style: {
4396
5535
  width: "100%",
4397
- height: "2.5rem",
5536
+ height: "2.25rem",
4398
5537
  padding: "0 0.75rem",
4399
5538
  border: `1px solid ${C.gray200}`,
4400
5539
  borderRadius: "0.375rem",
@@ -4406,50 +5545,431 @@ function OnboardingModal({ open, onOpenChange, user, onComplete, onSkip }) {
4406
5545
  }
4407
5546
  )
4408
5547
  ] }),
4409
- /* @__PURE__ */ jsxs("label", { style: {
4410
- display: "flex",
4411
- alignItems: "flex-start",
4412
- gap: "0.5rem",
4413
- cursor: "pointer",
4414
- fontSize: "0.875rem",
4415
- color: C.gray700
4416
- }, children: [
4417
- /* @__PURE__ */ jsx(
4418
- "input",
4419
- {
4420
- type: "checkbox",
4421
- checked: confirmed,
4422
- onChange: (e) => setConfirmed(e.target.checked),
4423
- style: { width: "1rem", height: "1rem", marginTop: "0.125rem", accentColor: C.primary, cursor: "pointer" }
4424
- }
4425
- ),
4426
- /* @__PURE__ */ jsx("span", { children: "Je confirme que ces informations sont exactes" })
5548
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.85rem" }, children: [
5549
+ /* @__PURE__ */ jsxs("div", { children: [
5550
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.35rem", color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Adresse" }),
5551
+ /* @__PURE__ */ jsx(
5552
+ "input",
5553
+ {
5554
+ type: "text",
5555
+ value: address,
5556
+ onChange: (e) => setAddress(e.target.value),
5557
+ placeholder: "Rue, quartier, numéro",
5558
+ style: {
5559
+ width: "100%",
5560
+ height: "2.25rem",
5561
+ padding: "0 0.75rem",
5562
+ border: `1px solid ${C.gray200}`,
5563
+ borderRadius: "0.375rem",
5564
+ fontSize: "0.875rem",
5565
+ color: C.gray700,
5566
+ backgroundColor: C.white,
5567
+ outline: "none"
5568
+ }
5569
+ }
5570
+ )
5571
+ ] }),
5572
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.75rem" }, children: [
5573
+ /* @__PURE__ */ jsxs("div", { children: [
5574
+ /* @__PURE__ */ jsxs(Label, { style: { display: "block", marginBottom: "0.35rem", color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: [
5575
+ "Ville ",
5576
+ /* @__PURE__ */ jsx("span", { style: { color: "#dc2626" }, children: "*" })
5577
+ ] }),
5578
+ /* @__PURE__ */ jsx(
5579
+ "input",
5580
+ {
5581
+ type: "text",
5582
+ value: town,
5583
+ onChange: (e) => setTown(e.target.value),
5584
+ required: true,
5585
+ placeholder: "Dakar",
5586
+ style: {
5587
+ width: "100%",
5588
+ height: "2.25rem",
5589
+ padding: "0 0.75rem",
5590
+ border: `1px solid ${C.gray200}`,
5591
+ borderRadius: "0.375rem",
5592
+ fontSize: "0.875rem",
5593
+ color: C.gray700,
5594
+ backgroundColor: C.white,
5595
+ outline: "none"
5596
+ }
5597
+ }
5598
+ )
5599
+ ] }),
5600
+ /* @__PURE__ */ jsxs("div", { children: [
5601
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.35rem", color: C.gray700, fontWeight: 500, fontSize: "0.875rem" }, children: "Pays" }),
5602
+ /* @__PURE__ */ jsx(
5603
+ "select",
5604
+ {
5605
+ value: country,
5606
+ onChange: (e) => setCountry(e.target.value),
5607
+ style: {
5608
+ width: "100%",
5609
+ height: "2.25rem",
5610
+ padding: "0 0.6rem",
5611
+ border: `1px solid ${C.gray200}`,
5612
+ borderRadius: "0.375rem",
5613
+ fontSize: "0.875rem",
5614
+ color: C.gray700,
5615
+ backgroundColor: C.white,
5616
+ outline: "none"
5617
+ },
5618
+ children: COUNTRIES_SORTED_BY_CODE.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
5619
+ c.flag,
5620
+ " ",
5621
+ c.name
5622
+ ] }, c.code))
5623
+ }
5624
+ )
5625
+ ] })
5626
+ ] })
4427
5627
  ] })
4428
5628
  ] }) }),
4429
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
5629
+ /* @__PURE__ */ jsx(DialogFooter, { style: { flexDirection: "row", justifyContent: submitState === "success" ? "center" : "space-between", alignItems: "center" }, children: submitState === "success" ? /* @__PURE__ */ jsx(
5630
+ Button,
5631
+ {
5632
+ type: "button",
5633
+ variant: "outline",
5634
+ onClick: onDismiss,
5635
+ style: { width: "100%", height: "2.6rem" },
5636
+ children: "Fermer"
5637
+ }
5638
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
4430
5639
  /* @__PURE__ */ jsx(
4431
5640
  Button,
4432
5641
  {
4433
- onClick: handleSubmit,
4434
- disabled: !canSubmit || submitting,
4435
- style: { width: "100%", height: "2.75rem", opacity: canSubmit && !submitting ? 1 : 0.5 },
4436
- children: submitting ? "Enregistrement..." : "Valider"
5642
+ type: "button",
5643
+ variant: "outline",
5644
+ onClick: () => onOpenChange(false),
5645
+ disabled: submitting,
5646
+ style: { minWidth: "6.5rem", height: "2.35rem" },
5647
+ children: "Fermer"
4437
5648
  }
4438
5649
  ),
4439
5650
  /* @__PURE__ */ jsx(
4440
5651
  Button,
4441
5652
  {
4442
- variant: "outline",
4443
- onClick: onSkip,
4444
- disabled: submitting,
4445
- style: { width: "100%", height: "2.75rem" },
4446
- children: "Passer pour l'instant"
5653
+ type: "button",
5654
+ onClick: isEditMode ? handleSubmit : onSkip,
5655
+ disabled: isEditMode ? !canSubmit || submitting : submitting,
5656
+ style: { minWidth: "7.25rem", height: "2.35rem", opacity: (isEditMode ? canSubmit && !submitting : !submitting) ? 1 : 0.5 },
5657
+ children: submitting ? /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.45rem" }, children: [
5658
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }),
5659
+ "Enregistrement..."
5660
+ ] }) : isEditMode ? "Enregistrer" : "Plus tard"
4447
5661
  }
4448
5662
  )
4449
- ] })
4450
- ] }) });
5663
+ ] }) }),
5664
+ /* @__PURE__ */ jsx(
5665
+ AvatarCropModal,
5666
+ {
5667
+ open: avatarPickerOpen,
5668
+ imageSrc: pendingAvatarSrc,
5669
+ onOpenChange: setAvatarPickerOpen,
5670
+ onCancel: () => {
5671
+ setAvatarPickerOpen(false);
5672
+ setPendingAvatarSrc(null);
5673
+ },
5674
+ onConfirm: handleAvatarConfirm
5675
+ }
5676
+ )
5677
+ ] });
5678
+ const renderContactFlow = () => {
5679
+ if (!contactFlow) return null;
5680
+ const isEmail = contactFlow.kind === "email";
5681
+ const targetLabel = isEmail ? contactFlow.newEmail || "la nouvelle adresse" : `${contactFlow.newCcphone} ${contactFlow.newPhone}`.trim();
5682
+ const sentToLabel = currentReceiveLabel;
5683
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5684
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
5685
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "0.25rem" }, children: [
5686
+ /* @__PURE__ */ jsx(ShieldIcon, {}),
5687
+ /* @__PURE__ */ jsx(DialogTitle, { children: isEmail ? "Changer l’email" : "Changer le téléphone" })
5688
+ ] }),
5689
+ /* @__PURE__ */ jsx(DialogDescription, { children: contactFlow.phase === "request" ? `Veuillez renseigner le nouveau ${isEmail ? "email" : "numéro de téléphone"} pour lancer la vérification.` : contactFlow.phase === "verify-old" ? `Le code OTP a été envoyé sur : ${sentToLabel}.` : `Le second code OTP a été envoyé sur : ${sentToLabel}.` })
5690
+ ] }),
5691
+ /* @__PURE__ */ jsx(DialogBody, { style: { maxHeight: "52vh", overflowY: "auto", paddingBottom: "0.25rem" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", marginTop: "0.75rem" }, children: [
5692
+ contactFlow.error && /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.5rem", backgroundColor: "#fef2f2", color: "#b91c1c", fontSize: "0.875rem" }, children: contactFlow.error }),
5693
+ contactFlow.phase === "request" && /* @__PURE__ */ jsxs(Fragment, { children: [
5694
+ isEmail ? /* @__PURE__ */ jsxs("div", { children: [
5695
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "0.5rem", padding: "0.7rem 0.75rem", borderRadius: "0.5rem", backgroundColor: "#f9fafb", border: "1px solid #e5e7eb", fontSize: "0.85rem", color: C.gray700, lineHeight: 1.45 }, children: [
5696
+ /* @__PURE__ */ jsxs("div", { children: [
5697
+ /* @__PURE__ */ jsx("strong", { children: "Email actuel :" }),
5698
+ " ",
5699
+ currentEmailLabel || "non renseigné"
5700
+ ] }),
5701
+ /* @__PURE__ */ jsx("div", { style: { marginTop: "0.2rem", color: C.gray500 }, children: "Veuillez renseigner le nouveau email pour lancer la vérification." })
5702
+ ] }),
5703
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Nouvel email" }),
5704
+ /* @__PURE__ */ jsx(
5705
+ "input",
5706
+ {
5707
+ type: "email",
5708
+ value: contactFlow.newEmail,
5709
+ onChange: (e) => updateFlow({ newEmail: e.target.value }),
5710
+ placeholder: "nouvel@email.com",
5711
+ style: {
5712
+ width: "100%",
5713
+ height: "2.5rem",
5714
+ padding: "0 0.75rem",
5715
+ border: `1px solid ${C.gray200}`,
5716
+ borderRadius: "0.375rem",
5717
+ fontSize: "0.875rem",
5718
+ color: C.gray700,
5719
+ backgroundColor: C.white,
5720
+ outline: "none"
5721
+ }
5722
+ }
5723
+ ),
5724
+ emailIsCurrent && /* @__PURE__ */ jsx("span", { style: { display: "block", marginTop: "0.35rem", color: "#b91c1c", fontSize: "0.8rem", lineHeight: 1.35 }, children: requestDuplicateMessage })
5725
+ ] }) : /* @__PURE__ */ jsxs("div", { children: [
5726
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "0.5rem", padding: "0.7rem 0.75rem", borderRadius: "0.5rem", backgroundColor: "#f9fafb", border: "1px solid #e5e7eb", fontSize: "0.85rem", color: C.gray700, lineHeight: 1.45 }, children: [
5727
+ /* @__PURE__ */ jsxs("div", { children: [
5728
+ /* @__PURE__ */ jsx("strong", { children: "Numéro actuel :" }),
5729
+ " ",
5730
+ formatPhoneDisplay(currentPhoneDialDisplay, currentPhoneNumberDisplay) || "non renseigné"
5731
+ ] }),
5732
+ /* @__PURE__ */ jsx("div", { style: { marginTop: "0.2rem", color: C.gray500 }, children: "Veuillez renseigner le nouveau numéro de téléphone pour lancer la vérification." })
5733
+ ] }),
5734
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Nouveau téléphone" }),
5735
+ /* @__PURE__ */ jsx(
5736
+ PhoneInput,
5737
+ {
5738
+ value: contactFlow.newPhone,
5739
+ onChange: (value) => updateFlow({ newPhone: value }),
5740
+ ccphone: contactFlow.newCcphone,
5741
+ onCcphoneChange: (value) => updateFlow({ newCcphone: value, method: value.trim() === "+221" ? contactFlow.method : "email" })
5742
+ }
5743
+ ),
5744
+ phoneIsCurrent && /* @__PURE__ */ jsx("span", { style: { display: "block", marginTop: "0.35rem", color: "#b91c1c", fontSize: "0.8rem", lineHeight: 1.35 }, children: requestDuplicateMessage })
5745
+ ] }),
5746
+ contactSmsAllowed ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", flexWrap: "wrap" }, children: [
5747
+ /* @__PURE__ */ jsx(
5748
+ Button,
5749
+ {
5750
+ type: "button",
5751
+ variant: contactFlow.method === "email" ? "default" : "outline",
5752
+ onClick: () => switchFlowMethod("email"),
5753
+ style: { flex: 1 },
5754
+ children: "Code par email"
5755
+ }
5756
+ ),
5757
+ /* @__PURE__ */ jsx(
5758
+ Button,
5759
+ {
5760
+ type: "button",
5761
+ variant: contactFlow.method === "phone" ? "default" : "outline",
5762
+ onClick: () => switchFlowMethod("phone"),
5763
+ style: { flex: 1 },
5764
+ children: "Code par téléphone"
5765
+ }
5766
+ )
5767
+ ] }) : /* @__PURE__ */ jsx("div", { style: { padding: "0.75rem", borderRadius: "0.5rem", backgroundColor: "#f9fafb", border: "1px solid #e5e7eb", color: C.gray700, fontSize: "0.875rem", lineHeight: 1.45 }, children: "Le SMS est disponible uniquement pour les numéros sénégalais (+221). Le code sera envoyé par email." }),
5768
+ /* @__PURE__ */ jsxs("span", { style: { color: C.gray500, fontSize: "0.8rem", lineHeight: 1.35 }, children: [
5769
+ "Vous allez recevoir le code par ",
5770
+ currentReceiveLabel,
5771
+ "."
5772
+ ] })
5773
+ ] }),
5774
+ contactFlow.phase === "verify-old" && /* @__PURE__ */ jsxs(Fragment, { children: [
5775
+ /* @__PURE__ */ jsxs("div", { style: { padding: "0.75rem", borderRadius: "0.5rem", backgroundColor: "#f9fafb", border: "1px solid #e5e7eb", fontSize: "0.875rem", color: C.gray700 }, children: [
5776
+ /* @__PURE__ */ jsxs("div", { children: [
5777
+ /* @__PURE__ */ jsxs("strong", { children: [
5778
+ isEmail ? "Email actuel" : "Numéro de téléphone actuel",
5779
+ " :"
5780
+ ] }),
5781
+ " ",
5782
+ isEmail ? currentEmailLabel : formatPhoneDisplay(currentPhoneDialDisplay, currentPhoneNumberDisplay)
5783
+ ] }),
5784
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "0.25rem" }, children: [
5785
+ /* @__PURE__ */ jsx("strong", { children: "Nouveau :" }),
5786
+ " ",
5787
+ targetLabel
5788
+ ] }),
5789
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "0.25rem" }, children: [
5790
+ /* @__PURE__ */ jsx("strong", { children: "Envoi :" }),
5791
+ " ",
5792
+ sentToLabel
5793
+ ] })
5794
+ ] }),
5795
+ /* @__PURE__ */ jsxs("div", { children: [
5796
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Code reçu sur l'ancien contact" }),
5797
+ /* @__PURE__ */ jsx(
5798
+ OTPInput,
5799
+ {
5800
+ value: contactFlow.oldOtp,
5801
+ onChange: (value) => updateFlow({ oldOtp: value }),
5802
+ disabled: contactFlow.loading
5803
+ }
5804
+ )
5805
+ ] }),
5806
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "0.75rem", flexWrap: "wrap" }, children: [
5807
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.82rem", color: C.gray500 }, children: canResendOtp ? "Vous pouvez renvoyer un nouveau code." : `Renvoyer un code dans ${resendCountdown}s` }),
5808
+ canResendOtp && /* @__PURE__ */ jsx(
5809
+ Button,
5810
+ {
5811
+ type: "button",
5812
+ variant: "outline",
5813
+ onClick: () => setConfirmAction("resend"),
5814
+ disabled: contactFlow.loading,
5815
+ style: { height: "2.35rem" },
5816
+ children: "Renvoyer le code"
5817
+ }
5818
+ )
5819
+ ] })
5820
+ ] }),
5821
+ contactFlow.phase === "verify-new" && /* @__PURE__ */ jsxs(Fragment, { children: [
5822
+ /* @__PURE__ */ jsxs("div", { style: { padding: "0.75rem", borderRadius: "0.5rem", backgroundColor: "#f9fafb", border: "1px solid #e5e7eb", fontSize: "0.875rem", color: C.gray700 }, children: [
5823
+ /* @__PURE__ */ jsxs("div", { children: [
5824
+ /* @__PURE__ */ jsxs("strong", { children: [
5825
+ isEmail ? "Email actuel" : "Numéro de téléphone actuel",
5826
+ " :"
5827
+ ] }),
5828
+ " ",
5829
+ isEmail ? currentEmailLabel : formatPhoneDisplay(currentPhoneDialDisplay, currentPhoneNumberDisplay)
5830
+ ] }),
5831
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "0.25rem" }, children: [
5832
+ /* @__PURE__ */ jsx("strong", { children: "Nouveau :" }),
5833
+ " ",
5834
+ targetLabel
5835
+ ] }),
5836
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: "0.25rem" }, children: [
5837
+ /* @__PURE__ */ jsx("strong", { children: "Envoi :" }),
5838
+ " ",
5839
+ sentToLabel
5840
+ ] })
5841
+ ] }),
5842
+ /* @__PURE__ */ jsxs("div", { children: [
5843
+ /* @__PURE__ */ jsx(Label, { style: { display: "block", marginBottom: "0.5rem", color: C.gray700, fontWeight: 500 }, children: "Code reçu sur le nouveau contact" }),
5844
+ /* @__PURE__ */ jsx(
5845
+ OTPInput,
5846
+ {
5847
+ value: contactFlow.newOtp,
5848
+ onChange: (value) => updateFlow({ newOtp: value }),
5849
+ disabled: contactFlow.loading
5850
+ }
5851
+ )
5852
+ ] }),
5853
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "0.75rem", flexWrap: "wrap" }, children: [
5854
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "0.82rem", color: C.gray500 }, children: canResendOtp ? "Vous pouvez renvoyer un nouveau code." : `Renvoyer un code dans ${resendCountdown}s` }),
5855
+ canResendOtp && /* @__PURE__ */ jsx(
5856
+ Button,
5857
+ {
5858
+ type: "button",
5859
+ variant: "outline",
5860
+ onClick: () => setConfirmAction("resend"),
5861
+ disabled: contactFlow.loading,
5862
+ style: { height: "2.35rem" },
5863
+ children: "Renvoyer le code"
5864
+ }
5865
+ )
5866
+ ] })
5867
+ ] })
5868
+ ] }) }),
5869
+ /* @__PURE__ */ jsx(DialogFooter, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: contactFlow.phase === "request" ? /* @__PURE__ */ jsxs(Fragment, { children: [
5870
+ /* @__PURE__ */ jsx(
5871
+ Button,
5872
+ {
5873
+ type: "button",
5874
+ variant: "outline",
5875
+ onClick: closeContactFlow,
5876
+ disabled: contactFlow.loading,
5877
+ style: { minWidth: "6.5rem", height: "2.35rem" },
5878
+ children: "Fermer"
5879
+ }
5880
+ ),
5881
+ /* @__PURE__ */ jsx(
5882
+ Button,
5883
+ {
5884
+ type: "button",
5885
+ onClick: requestChange,
5886
+ disabled: contactFlow.loading || (isEmail ? !contactFlow.newEmail.trim() : !contactFlow.newPhone.trim()) || emailIsCurrent || phoneIsCurrent,
5887
+ style: { minWidth: "7.25rem", height: "2.35rem", opacity: contactFlow.loading ? 0.5 : 1 },
5888
+ children: contactFlow.loading ? /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.45rem" }, children: [
5889
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }),
5890
+ "Envoi..."
5891
+ ] }) : "Envoyer"
5892
+ }
5893
+ )
5894
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5895
+ /* @__PURE__ */ jsx(
5896
+ Button,
5897
+ {
5898
+ type: "button",
5899
+ variant: "outline",
5900
+ onClick: () => setConfirmAction("back"),
5901
+ disabled: contactFlow.loading,
5902
+ style: { minWidth: "6.5rem", height: "2.35rem" },
5903
+ children: "Retour"
5904
+ }
5905
+ ),
5906
+ /* @__PURE__ */ jsx(
5907
+ Button,
5908
+ {
5909
+ type: "button",
5910
+ onClick: contactFlow.phase === "verify-old" ? verifyOld : verifyNew,
5911
+ disabled: contactFlow.loading || contactFlow.phase === "verify-old" && contactFlow.oldOtp.length !== 6 || contactFlow.phase === "verify-new" && contactFlow.newOtp.length !== 6,
5912
+ style: { minWidth: "7.25rem", height: "2.35rem", opacity: contactFlow.loading ? 0.5 : 1 },
5913
+ children: contactFlow.loading ? /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.45rem" }, children: [
5914
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { animation: "spin 1s linear infinite" }, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) }),
5915
+ "Vérification..."
5916
+ ] }) : contactFlow.phase === "verify-old" ? "Vérifier" : "Confirmer"
5917
+ }
5918
+ )
5919
+ ] }) })
5920
+ ] });
5921
+ };
5922
+ const renderConfirmDialog = () => {
5923
+ if (!confirmAction) return null;
5924
+ const config2 = confirmAction === "back" ? {
5925
+ title: "Retourner à l’étape précédente ?",
5926
+ description: "La vérification en cours sera interrompue. Vous pourrez reprendre le changement depuis le début.",
5927
+ confirmLabel: "Retourner"
5928
+ } : confirmAction === "resend" ? {
5929
+ title: "Renvoyer un nouveau code OTP ?",
5930
+ description: `Un nouveau code sera envoyé vers ${currentReceiveLabel}.`,
5931
+ confirmLabel: "Renvoyer"
5932
+ } : {
5933
+ title: "Annuler la récupération ?",
5934
+ description: "Les informations IAM ne seront pas récupérées maintenant. Vous pourrez relancer l’ouverture plus tard.",
5935
+ confirmLabel: "Annuler"
5936
+ };
5937
+ const confirm = async () => {
5938
+ if (confirmAction === "back") {
5939
+ confirmBackToRequest();
5940
+ } else if (confirmAction === "resend") {
5941
+ await confirmResendOtp();
5942
+ } else {
5943
+ setConfirmAction(null);
5944
+ onDismiss();
5945
+ }
5946
+ };
5947
+ return /* @__PURE__ */ jsx(Dialog, { open: Boolean(confirmAction), onOpenChange: (open2) => {
5948
+ if (!open2) setConfirmAction(null);
5949
+ }, children: /* @__PURE__ */ jsxs(DialogContent, { hideCloseButton: false, style: { maxWidth: "22rem", padding: "0.9rem 1rem", borderRadius: "0.7rem" }, children: [
5950
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
5951
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "1rem", fontWeight: 600, color: "#111827" }, children: config2.title }),
5952
+ /* @__PURE__ */ jsx(DialogDescription, { children: config2.description })
5953
+ ] }),
5954
+ /* @__PURE__ */ jsxs(DialogFooter, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: [
5955
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setConfirmAction(null), style: { minWidth: "6rem", height: "2.25rem" }, children: "Annuler" }),
5956
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: confirm, style: { minWidth: "6.5rem", height: "2.25rem" }, children: confirmAction === "resend" && (contactFlow == null ? void 0 : contactFlow.loading) ? "Envoi..." : config2.confirmLabel })
5957
+ ] })
5958
+ ] }) });
5959
+ };
5960
+ return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange: handleDialogOpenChange, children: [
5961
+ /* @__PURE__ */ jsx(
5962
+ DialogContent,
5963
+ {
5964
+ hideCloseButton: lockModalClose,
5965
+ style: { maxWidth: "26rem", padding: "0.75rem 0.8rem", borderRadius: "0.65rem", maxHeight: "88vh" },
5966
+ children: profileHydrating && submitState === "idle" && !contactFlow ? renderHydration() : contactFlow ? renderContactFlow() : submitState === "success" ? renderSuccess() : renderBase()
5967
+ }
5968
+ ),
5969
+ renderConfirmDialog()
5970
+ ] });
4451
5971
  }
4452
- function DebugPanel({ saasApiUrl, iamApiUrl }) {
5972
+ function DebugPanel({ saasApiUrl, iamApiUrl, onOpenLogin, onOpenSignup, onOpenOnboarding, onResetProfilePrompt }) {
4453
5973
  const [logs, setLogs] = useState([]);
4454
5974
  const [expanded, setExpanded] = useState(true);
4455
5975
  const [selectedLog, setSelectedLog] = useState(null);
@@ -4543,7 +6063,7 @@ function DebugPanel({ saasApiUrl, iamApiUrl }) {
4543
6063
  maxHeight: "300px",
4544
6064
  overflowY: "auto"
4545
6065
  }, children: [
4546
- /* @__PURE__ */ jsxs("div", { style: { padding: "4px 12px", background: "#1e1e3f", fontSize: "10px", color: "#94a3b8", display: "flex", gap: "16px" }, children: [
6066
+ /* @__PURE__ */ jsxs("div", { style: { padding: "4px 12px", background: "#1e1e3f", fontSize: "10px", color: "#94a3b8", display: "flex", gap: "16px", alignItems: "center", flexWrap: "wrap" }, children: [
4547
6067
  /* @__PURE__ */ jsxs("span", { children: [
4548
6068
  "SaaS: ",
4549
6069
  /* @__PURE__ */ jsx("span", { style: { color: "#6366f1" }, children: saasApiUrl })
@@ -4551,6 +6071,56 @@ function DebugPanel({ saasApiUrl, iamApiUrl }) {
4551
6071
  /* @__PURE__ */ jsxs("span", { children: [
4552
6072
  "IAM: ",
4553
6073
  /* @__PURE__ */ jsx("span", { style: { color: "#6366f1" }, children: iamApiUrl })
6074
+ ] }),
6075
+ /* @__PURE__ */ jsxs("div", { style: { marginLeft: "auto", display: "flex", gap: "6px", flexWrap: "wrap" }, children: [
6076
+ /* @__PURE__ */ jsx(
6077
+ "button",
6078
+ {
6079
+ onClick: (e) => {
6080
+ e.stopPropagation();
6081
+ onOpenLogin == null ? void 0 : onOpenLogin();
6082
+ },
6083
+ disabled: !onOpenLogin,
6084
+ style: { background: "#334155", color: "#e2e8f0", border: "none", borderRadius: "4px", padding: "2px 8px", cursor: onOpenLogin ? "pointer" : "not-allowed", fontSize: "11px", opacity: onOpenLogin ? 1 : 0.5 },
6085
+ children: "Connexion"
6086
+ }
6087
+ ),
6088
+ /* @__PURE__ */ jsx(
6089
+ "button",
6090
+ {
6091
+ onClick: (e) => {
6092
+ e.stopPropagation();
6093
+ onOpenSignup == null ? void 0 : onOpenSignup();
6094
+ },
6095
+ disabled: !onOpenSignup,
6096
+ style: { background: "#334155", color: "#e2e8f0", border: "none", borderRadius: "4px", padding: "2px 8px", cursor: onOpenSignup ? "pointer" : "not-allowed", fontSize: "11px", opacity: onOpenSignup ? 1 : 0.5 },
6097
+ children: "Inscription"
6098
+ }
6099
+ ),
6100
+ /* @__PURE__ */ jsx(
6101
+ "button",
6102
+ {
6103
+ onClick: (e) => {
6104
+ e.stopPropagation();
6105
+ onOpenOnboarding == null ? void 0 : onOpenOnboarding("current");
6106
+ },
6107
+ disabled: !onOpenOnboarding,
6108
+ style: { background: "#334155", color: "#e2e8f0", border: "none", borderRadius: "4px", padding: "2px 8px", cursor: onOpenOnboarding ? "pointer" : "not-allowed", fontSize: "11px", opacity: onOpenOnboarding ? 1 : 0.5 },
6109
+ children: "Infos profile"
6110
+ }
6111
+ ),
6112
+ /* @__PURE__ */ jsx(
6113
+ "button",
6114
+ {
6115
+ onClick: (e) => {
6116
+ e.stopPropagation();
6117
+ onResetProfilePrompt == null ? void 0 : onResetProfilePrompt();
6118
+ },
6119
+ disabled: !onResetProfilePrompt,
6120
+ style: { background: "#334155", color: "#e2e8f0", border: "none", borderRadius: "4px", padding: "2px 8px", cursor: onResetProfilePrompt ? "pointer" : "not-allowed", fontSize: "11px", opacity: onResetProfilePrompt ? 1 : 0.5 },
6121
+ children: "Reset profil"
6122
+ }
6123
+ )
4554
6124
  ] })
4555
6125
  ] }),
4556
6126
  logs.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "20px", textAlign: "center", color: "#64748b" }, children: "Aucun appel API. Tentez une connexion pour voir les logs." }) : /* @__PURE__ */ jsx("div", { children: logs.map((log) => /* @__PURE__ */ jsxs("div", { children: [
@@ -4606,6 +6176,11 @@ function DebugPanel({ saasApiUrl, iamApiUrl }) {
4606
6176
  ] });
4607
6177
  }
4608
6178
  const ollaidLogo = "data:image/png;base64,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";
6179
+ const PROFILE_PROMPT_DELAY_MS = 5 * 60 * 1e3;
6180
+ const PROFILE_PROMPT_SNOOZE_MS = 8 * 60 * 60 * 1e3;
6181
+ const PROFILE_SYNC_INTERVAL_MS = 30 * 60 * 1e3;
6182
+ const PROFILE_FETCH_RETRY_DELAY_MS = 1200;
6183
+ const PROFILE_FETCH_MAX_ATTEMPTS = 3;
4609
6184
  const COLORS = {
4610
6185
  primary: "#002147",
4611
6186
  primaryForeground: "#ffffff",
@@ -4642,7 +6217,10 @@ const ShieldCheckIcon = ({ size = 24, color = COLORS.accent }) => /* @__PURE__ *
4642
6217
  /* @__PURE__ */ jsx("path", { d: "m9 12 2 2 4-4" })
4643
6218
  ] });
4644
6219
  function needsOnboarding(user) {
4645
- return !user.image_url || !user.phone;
6220
+ return !user.image_url || !user.phone || !user.email;
6221
+ }
6222
+ function isProfileComplete(user) {
6223
+ return Boolean(user.image_url && user.phone && user.email);
4646
6224
  }
4647
6225
  const GRADIENT_STYLE_ID = "ollaid-sso-gradient-anim";
4648
6226
  function injectGradientAnimation() {
@@ -4699,6 +6277,7 @@ function NativeSSOPage({
4699
6277
  const [loginInitialPhone, setLoginInitialPhone] = useState();
4700
6278
  const [showOnboarding, setShowOnboarding] = useState(false);
4701
6279
  const [pendingSession, setPendingSession] = useState(null);
6280
+ const [debugOnboardingState, setDebugOnboardingState] = useState(null);
4702
6281
  const [session, setSession] = useState(() => {
4703
6282
  try {
4704
6283
  const token = localStorage.getItem(STORAGE.AUTH_TOKEN) || localStorage.getItem(STORAGE.TOKEN);
@@ -4709,6 +6288,135 @@ function NativeSSOPage({
4709
6288
  return null;
4710
6289
  });
4711
6290
  const { isDebug: resolvedDebug } = useNativeAuth({ saasApiUrl, iamApiUrl, configPrefix, autoLoadCredentials: true });
6291
+ const onboardingPromptTimerRef = useRef(null);
6292
+ const onboardingRecheckTimerRef = useRef(null);
6293
+ const profileSyncTimerRef = useRef(null);
6294
+ const profileHydrationRunningRef = useRef(false);
6295
+ const profileHydrationRequestedRef = useRef(null);
6296
+ const sessionRef = useRef(session);
6297
+ const [profileHydrating, setProfileHydrating] = useState(false);
6298
+ useEffect(() => {
6299
+ sessionRef.current = session;
6300
+ }, [session]);
6301
+ const clearOnboardingTimers = useCallback(() => {
6302
+ if (onboardingPromptTimerRef.current) {
6303
+ clearTimeout(onboardingPromptTimerRef.current);
6304
+ onboardingPromptTimerRef.current = null;
6305
+ }
6306
+ if (onboardingRecheckTimerRef.current) {
6307
+ clearTimeout(onboardingRecheckTimerRef.current);
6308
+ onboardingRecheckTimerRef.current = null;
6309
+ }
6310
+ }, []);
6311
+ const persistSessionUser = useCallback((token, user) => {
6312
+ setSession({ token, user });
6313
+ localStorage.setItem(STORAGE.USER, JSON.stringify(user));
6314
+ localStorage.setItem(STORAGE.AUTH_TOKEN, token);
6315
+ localStorage.setItem(STORAGE.TOKEN, token);
6316
+ }, []);
6317
+ const extractUserInfos = useCallback((payload) => {
6318
+ if (!payload || typeof payload !== "object") return null;
6319
+ const data = payload;
6320
+ const candidate = data.user_infos && typeof data.user_infos === "object" ? data.user_infos : data.user && typeof data.user === "object" ? data.user : data;
6321
+ return {
6322
+ reference: typeof candidate.reference === "string" ? candidate.reference : void 0,
6323
+ name: typeof candidate.name === "string" ? candidate.name : void 0,
6324
+ email: typeof candidate.email === "string" ? candidate.email : void 0,
6325
+ ccphone: typeof candidate.ccphone === "string" ? candidate.ccphone : void 0,
6326
+ phone: typeof candidate.phone === "string" ? candidate.phone : void 0,
6327
+ address: typeof candidate.address === "string" ? candidate.address : void 0,
6328
+ town: typeof candidate.town === "string" ? candidate.town : void 0,
6329
+ country: typeof candidate.country === "string" ? candidate.country : void 0,
6330
+ image_url: typeof candidate.image_url === "string" ? candidate.image_url : void 0,
6331
+ auth_2fa: typeof candidate.auth_2fa === "boolean" ? candidate.auth_2fa : void 0,
6332
+ alias_reference: typeof candidate.alias_reference === "string" ? candidate.alias_reference : void 0,
6333
+ iam_reference: typeof candidate.iam_reference === "string" ? candidate.iam_reference : void 0
6334
+ };
6335
+ }, []);
6336
+ const refreshSessionProfile = useCallback(async (force = false) => {
6337
+ const activeSession = sessionRef.current;
6338
+ if (!(activeSession == null ? void 0 : activeSession.token)) return null;
6339
+ if (profileHydrationRunningRef.current && !force) return activeSession.user;
6340
+ if (profileHydrationRequestedRef.current === activeSession.token && !force) return activeSession.user;
6341
+ profileHydrationRunningRef.current = true;
6342
+ profileHydrationRequestedRef.current = activeSession.token;
6343
+ setProfileHydrating(true);
6344
+ try {
6345
+ let lastError = null;
6346
+ for (let attempt = 1; attempt <= PROFILE_FETCH_MAX_ATTEMPTS; attempt += 1) {
6347
+ try {
6348
+ const response = await profileService.getProfile();
6349
+ const mergedInfos = extractUserInfos(response) || {};
6350
+ const mergedUser = { ...activeSession.user, ...mergedInfos };
6351
+ persistSessionUser(activeSession.token, mergedUser);
6352
+ return mergedUser;
6353
+ } catch (error) {
6354
+ lastError = error;
6355
+ if (attempt < PROFILE_FETCH_MAX_ATTEMPTS) {
6356
+ await new Promise((resolve) => setTimeout(resolve, PROFILE_FETCH_RETRY_DELAY_MS * attempt));
6357
+ }
6358
+ }
6359
+ }
6360
+ const isDev = Boolean(false);
6361
+ if (isDev) {
6362
+ console.warn("Profile hydration failed after retries", lastError);
6363
+ }
6364
+ return activeSession.user;
6365
+ } finally {
6366
+ profileHydrationRunningRef.current = false;
6367
+ setProfileHydrating(false);
6368
+ }
6369
+ }, [persistSessionUser, extractUserInfos]);
6370
+ const makeDebugOnboardingUser = useCallback((baseUser, preset) => {
6371
+ const normalized = { ...baseUser };
6372
+ if (preset === "current") return normalized;
6373
+ if (preset === "photo") return { ...normalized, image_url: "" };
6374
+ if (preset === "phone") return { ...normalized, phone: "", ccphone: "" };
6375
+ if (preset === "email") return { ...normalized, email: "" };
6376
+ return { ...normalized, image_url: "", phone: "", ccphone: "", email: "" };
6377
+ }, []);
6378
+ const syncProfilePrompt = useCallback((currentSession) => {
6379
+ clearOnboardingTimers();
6380
+ if (!currentSession) {
6381
+ setPendingSession(null);
6382
+ setShowOnboarding(false);
6383
+ return;
6384
+ }
6385
+ const promptState = getProfilePromptState();
6386
+ const now = Date.now();
6387
+ const onboardingVisible = showOnboarding || Boolean(pendingSession || debugOnboardingState);
6388
+ if (promptState.lastStatus === true || isProfileComplete(currentSession.user)) {
6389
+ markProfilePromptComplete();
6390
+ if (!onboardingVisible) {
6391
+ setPendingSession(null);
6392
+ setShowOnboarding(false);
6393
+ }
6394
+ return;
6395
+ }
6396
+ setPendingSession({ ...currentSession, sourceUser: currentSession.user });
6397
+ const scheduleOpen = () => {
6398
+ onboardingPromptTimerRef.current = setTimeout(() => {
6399
+ const latestPromptState = getProfilePromptState();
6400
+ if (latestPromptState.lastStatus === true) return;
6401
+ if (!currentSession) return;
6402
+ if (!isProfileComplete(currentSession.user)) {
6403
+ setShowOnboarding(true);
6404
+ }
6405
+ }, PROFILE_PROMPT_DELAY_MS);
6406
+ };
6407
+ if (promptState.recheckAt && promptState.recheckAt > now) {
6408
+ onboardingRecheckTimerRef.current = setTimeout(() => {
6409
+ const latestPromptState = getProfilePromptState();
6410
+ if (latestPromptState.lastStatus === true) return;
6411
+ if (!currentSession) return;
6412
+ if (!isProfileComplete(currentSession.user)) {
6413
+ scheduleOpen();
6414
+ }
6415
+ }, promptState.recheckAt - now);
6416
+ return;
6417
+ }
6418
+ scheduleOpen();
6419
+ }, [clearOnboardingTimers, showOnboarding, pendingSession, debugOnboardingState]);
4712
6420
  useEffect(() => {
4713
6421
  injectGradientAnimation();
4714
6422
  const root = document.documentElement;
@@ -4768,40 +6476,166 @@ function NativeSSOPage({
4768
6476
  account_type: user.account_type || "user"
4769
6477
  };
4770
6478
  setModal("none");
4771
- if (needsOnboarding(userObj)) {
4772
- setPendingSession({ token, user: userObj });
4773
- setShowOnboarding(true);
4774
- } else {
4775
- setSession({ token, user: userObj });
6479
+ persistSessionUser(token, userObj);
6480
+ syncProfilePrompt({ token, user: userObj });
6481
+ void refreshSessionProfile(true);
6482
+ if (!needsOnboarding(userObj)) {
6483
+ markProfilePromptComplete();
4776
6484
  onLoginSuccess == null ? void 0 : onLoginSuccess(token, user);
4777
6485
  if (redirectAfterLogin) window.location.href = redirectAfterLogin;
4778
6486
  }
4779
- }, [onLoginSuccess, redirectAfterLogin]);
6487
+ }, [onLoginSuccess, redirectAfterLogin, persistSessionUser, syncProfilePrompt, refreshSessionProfile]);
4780
6488
  const handleOnboardingComplete = useCallback((data) => {
4781
- if (!pendingSession) return;
4782
- const updatedUser = { ...pendingSession.user, ...data };
4783
- localStorage.setItem(STORAGE.USER, JSON.stringify(updatedUser));
6489
+ const activeSession = debugOnboardingState || pendingSession;
6490
+ if (!activeSession) return;
6491
+ const updatedUser = { ...activeSession.sourceUser, ...data.user_infos || data };
6492
+ persistSessionUser(activeSession.token, updatedUser);
6493
+ onOnboardingComplete == null ? void 0 : onOnboardingComplete({
6494
+ name: updatedUser.name,
6495
+ image_url: updatedUser.image_url,
6496
+ ccphone: updatedUser.ccphone || void 0,
6497
+ phone: updatedUser.phone || void 0,
6498
+ email: updatedUser.email || void 0,
6499
+ address: updatedUser.address || void 0,
6500
+ town: updatedUser.town || void 0,
6501
+ country: updatedUser.country || void 0,
6502
+ user_infos: {
6503
+ reference: updatedUser.reference,
6504
+ name: updatedUser.name,
6505
+ email: updatedUser.email || null,
6506
+ ccphone: updatedUser.ccphone,
6507
+ phone: updatedUser.phone,
6508
+ address: updatedUser.address,
6509
+ town: updatedUser.town,
6510
+ country: updatedUser.country,
6511
+ image_url: updatedUser.image_url,
6512
+ auth_2fa: updatedUser.auth_2fa,
6513
+ alias_reference: updatedUser.alias_reference,
6514
+ iam_reference: updatedUser.iam_reference
6515
+ }
6516
+ });
6517
+ if (!debugOnboardingState) {
6518
+ markProfilePromptComplete();
6519
+ }
6520
+ void refreshSessionProfile(true);
6521
+ }, [pendingSession, debugOnboardingState, onOnboardingComplete, persistSessionUser, markProfilePromptComplete, refreshSessionProfile]);
6522
+ const handleOnboardingSkip = useCallback(() => {
6523
+ const activeSession = debugOnboardingState || pendingSession;
6524
+ if (!activeSession) return;
4784
6525
  setShowOnboarding(false);
4785
- setSession({ token: pendingSession.token, user: updatedUser });
4786
6526
  setPendingSession(null);
4787
- onOnboardingComplete == null ? void 0 : onOnboardingComplete(data);
4788
- onLoginSuccess == null ? void 0 : onLoginSuccess(pendingSession.token, updatedUser);
4789
- if (redirectAfterLogin) window.location.href = redirectAfterLogin;
4790
- }, [pendingSession, onLoginSuccess, onOnboardingComplete, redirectAfterLogin]);
4791
- const handleOnboardingSkip = useCallback(() => {
4792
- if (!pendingSession) return;
6527
+ setDebugOnboardingState(null);
6528
+ if (!debugOnboardingState) {
6529
+ persistSessionUser(activeSession.token, activeSession.user);
6530
+ snoozeProfilePrompt(PROFILE_PROMPT_SNOOZE_MS / (60 * 60 * 1e3));
6531
+ onLoginSuccess == null ? void 0 : onLoginSuccess(activeSession.token, activeSession.user);
6532
+ if (redirectAfterLogin) window.location.href = redirectAfterLogin;
6533
+ return;
6534
+ }
6535
+ }, [pendingSession, debugOnboardingState, onLoginSuccess, redirectAfterLogin, persistSessionUser]);
6536
+ const handleOnboardingDismiss = useCallback(() => {
4793
6537
  setShowOnboarding(false);
4794
- setSession(pendingSession);
4795
6538
  setPendingSession(null);
4796
- onLoginSuccess == null ? void 0 : onLoginSuccess(pendingSession.token, pendingSession.user);
4797
- if (redirectAfterLogin) window.location.href = redirectAfterLogin;
4798
- }, [pendingSession, onLoginSuccess, redirectAfterLogin]);
6539
+ setDebugOnboardingState(null);
6540
+ }, []);
4799
6541
  const handleLogout = useCallback(async () => {
4800
6542
  await logout();
4801
6543
  setSession(null);
6544
+ setPendingSession(null);
6545
+ setDebugOnboardingState(null);
6546
+ clearOnboardingTimers();
4802
6547
  onLogout == null ? void 0 : onLogout();
4803
6548
  if (redirectAfterLogout) window.location.href = redirectAfterLogout;
4804
- }, [onLogout, redirectAfterLogout]);
6549
+ }, [onLogout, redirectAfterLogout, clearOnboardingTimers]);
6550
+ const openDebugLogin = useCallback(() => {
6551
+ clearOnboardingTimers();
6552
+ setShowOnboarding(false);
6553
+ setPendingSession(null);
6554
+ setDebugOnboardingState(null);
6555
+ setModal("login");
6556
+ }, [clearOnboardingTimers]);
6557
+ const openDebugSignup = useCallback(() => {
6558
+ clearOnboardingTimers();
6559
+ setShowOnboarding(false);
6560
+ setPendingSession(null);
6561
+ setDebugOnboardingState(null);
6562
+ setModal("signup");
6563
+ }, [clearOnboardingTimers]);
6564
+ const openDebugOnboarding = useCallback((preset = "current") => {
6565
+ if (!session) return;
6566
+ clearOnboardingTimers();
6567
+ setModal("none");
6568
+ const simulatedUser = makeDebugOnboardingUser(session.user, preset);
6569
+ setDebugOnboardingState({
6570
+ token: session.token,
6571
+ user: simulatedUser,
6572
+ sourceUser: session.user,
6573
+ preset,
6574
+ variant: "edit"
6575
+ });
6576
+ setPendingSession(null);
6577
+ setShowOnboarding(true);
6578
+ }, [session, clearOnboardingTimers, makeDebugOnboardingUser]);
6579
+ const resetDebugProfilePrompt = useCallback(() => {
6580
+ setProfilePromptState(false, Date.now() + PROFILE_PROMPT_SNOOZE_MS);
6581
+ if (session) {
6582
+ clearOnboardingTimers();
6583
+ setDebugOnboardingState(null);
6584
+ setPendingSession({ ...session, sourceUser: session.user });
6585
+ setShowOnboarding(true);
6586
+ }
6587
+ }, [session, clearOnboardingTimers]);
6588
+ useEffect(() => {
6589
+ syncProfilePrompt(session);
6590
+ return () => {
6591
+ clearOnboardingTimers();
6592
+ };
6593
+ }, [session, syncProfilePrompt, clearOnboardingTimers]);
6594
+ useEffect(() => {
6595
+ if (!(session == null ? void 0 : session.token)) return;
6596
+ if (!showOnboarding && !debugOnboardingState) return;
6597
+ void refreshSessionProfile();
6598
+ }, [session == null ? void 0 : session.token, showOnboarding, debugOnboardingState, refreshSessionProfile]);
6599
+ useEffect(() => {
6600
+ if (!(session == null ? void 0 : session.token)) return;
6601
+ const syncUserInfos = async () => {
6602
+ try {
6603
+ const response = await nativeAuthService.checkToken(session.token);
6604
+ if (!response.valid || !response.user) {
6605
+ await refreshSessionProfile(true);
6606
+ return;
6607
+ }
6608
+ const storedRaw = typeof localStorage !== "undefined" ? localStorage.getItem(STORAGE.USER) : null;
6609
+ let storedUser = session.user;
6610
+ if (storedRaw) {
6611
+ try {
6612
+ storedUser = JSON.parse(storedRaw);
6613
+ } catch {
6614
+ }
6615
+ }
6616
+ const mergedUser = { ...storedUser, ...response.user };
6617
+ persistSessionUser(session.token, mergedUser);
6618
+ if (isProfileComplete(mergedUser)) {
6619
+ markProfilePromptComplete();
6620
+ setShowOnboarding(false);
6621
+ setPendingSession(null);
6622
+ }
6623
+ } catch {
6624
+ await refreshSessionProfile(true);
6625
+ }
6626
+ };
6627
+ if (profileSyncTimerRef.current) {
6628
+ clearInterval(profileSyncTimerRef.current);
6629
+ profileSyncTimerRef.current = null;
6630
+ }
6631
+ profileSyncTimerRef.current = setInterval(syncUserInfos, PROFILE_SYNC_INTERVAL_MS);
6632
+ return () => {
6633
+ if (profileSyncTimerRef.current) {
6634
+ clearInterval(profileSyncTimerRef.current);
6635
+ profileSyncTimerRef.current = null;
6636
+ }
6637
+ };
6638
+ }, [session == null ? void 0 : session.token, persistSessionUser, refreshSessionProfile, markProfilePromptComplete]);
4805
6639
  const containerStyle = {
4806
6640
  minHeight: "100vh",
4807
6641
  backgroundColor: COLORS.primary,
@@ -4868,7 +6702,33 @@ function NativeSSOPage({
4868
6702
  /* @__PURE__ */ jsx("p", { style: { fontSize: "0.875rem", color: COLORS.muted, textAlign: "center", marginTop: "0.25rem" }, children: "Vous êtes connecté à votre compte Ollaid SSO" }),
4869
6703
  /* @__PURE__ */ jsx("div", { style: { marginTop: "1.5rem" }, children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: handleLogout, style: { width: "100%" }, children: "Déconnexion" }) })
4870
6704
  ] }) }),
4871
- /* @__PURE__ */ jsx(Footer, { hideFooter })
6705
+ /* @__PURE__ */ jsx(Footer, { hideFooter }),
6706
+ (pendingSession || debugOnboardingState) && /* @__PURE__ */ jsx(
6707
+ OnboardingModal,
6708
+ {
6709
+ open: showOnboarding,
6710
+ onOpenChange: (open) => {
6711
+ if (!open) handleOnboardingSkip();
6712
+ },
6713
+ onDismiss: handleOnboardingDismiss,
6714
+ user: (debugOnboardingState || pendingSession).user,
6715
+ variant: debugOnboardingState ? "edit" : "missing",
6716
+ profileHydrating,
6717
+ onComplete: handleOnboardingComplete,
6718
+ onSkip: handleOnboardingSkip
6719
+ }
6720
+ ),
6721
+ resolvedDebug && /* @__PURE__ */ jsx(
6722
+ DebugPanel,
6723
+ {
6724
+ saasApiUrl,
6725
+ iamApiUrl,
6726
+ onOpenLogin: openDebugLogin,
6727
+ onOpenSignup: openDebugSignup,
6728
+ onOpenOnboarding: openDebugOnboarding,
6729
+ onResetProfilePrompt: resetDebugProfilePrompt
6730
+ }
6731
+ )
4872
6732
  ] });
4873
6733
  }
4874
6734
  return /* @__PURE__ */ jsxs("div", { style: containerStyle, children: [
@@ -4920,19 +6780,32 @@ function NativeSSOPage({
4920
6780
  defaultAccountType: accountType
4921
6781
  }
4922
6782
  ),
4923
- pendingSession && /* @__PURE__ */ jsx(
6783
+ (pendingSession || debugOnboardingState) && /* @__PURE__ */ jsx(
4924
6784
  OnboardingModal,
4925
6785
  {
4926
6786
  open: showOnboarding,
4927
6787
  onOpenChange: (open) => {
4928
6788
  if (!open) handleOnboardingSkip();
4929
6789
  },
4930
- user: pendingSession.user,
6790
+ onDismiss: handleOnboardingDismiss,
6791
+ user: (debugOnboardingState || pendingSession).user,
6792
+ variant: debugOnboardingState ? "edit" : "missing",
6793
+ profileHydrating,
4931
6794
  onComplete: handleOnboardingComplete,
4932
6795
  onSkip: handleOnboardingSkip
4933
6796
  }
4934
6797
  ),
4935
- resolvedDebug && /* @__PURE__ */ jsx(DebugPanel, { saasApiUrl, iamApiUrl })
6798
+ resolvedDebug && /* @__PURE__ */ jsx(
6799
+ DebugPanel,
6800
+ {
6801
+ saasApiUrl,
6802
+ iamApiUrl,
6803
+ onOpenLogin: openDebugLogin,
6804
+ onOpenSignup: openDebugSignup,
6805
+ onOpenOnboarding: openDebugOnboarding,
6806
+ onResetProfilePrompt: resetDebugProfilePrompt
6807
+ }
6808
+ )
4936
6809
  ] });
4937
6810
  }
4938
6811
  const NativeSSOContext = createContext(null);
@@ -5131,8 +7004,10 @@ export {
5131
7004
  NativeSSOProvider,
5132
7005
  OTPInput,
5133
7006
  OnboardingModal,
7007
+ PROFILE_STORAGE as PROFILE_PROMPT_KEYS,
5134
7008
  PasswordRecoveryModal,
5135
7009
  PhoneInput,
7010
+ STORAGE as STORAGE_KEYS,
5136
7011
  SignupModal,
5137
7012
  clearAuthToken,
5138
7013
  getAccountType,
@@ -5141,13 +7016,21 @@ export {
5141
7016
  getCountryByCode,
5142
7017
  getCountryByDialCode,
5143
7018
  getDefaultCountry,
7019
+ getDeviceId,
5144
7020
  getNativeAuthConfig,
7021
+ getProfilePromptState,
7022
+ getSessionUuid,
5145
7023
  iamAccountService,
5146
7024
  logout,
7025
+ markProfilePromptComplete,
5147
7026
  mobilePasswordService,
5148
7027
  nativeAuthService,
7028
+ profileChangeService,
7029
+ profileMediaService,
5149
7030
  searchCountries,
5150
7031
  setNativeAuthConfig,
7032
+ setProfilePromptState,
7033
+ snoozeProfilePrompt,
5151
7034
  useLogout,
5152
7035
  useMobilePassword,
5153
7036
  useMobileRegistration,