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