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