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