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