@ollaid/native-sso 2.1.5 → 2.5.0

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