@cedros/login-react 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AdminDepositList-CyT4VBH8.js +311 -0
- package/dist/AdminDepositList-CyT4VBH8.js.map +1 -0
- package/dist/AdminDepositList-b2AXtLg0.cjs +1 -0
- package/dist/AdminDepositList-b2AXtLg0.cjs.map +1 -0
- package/dist/{AdminWithdrawalHistory-BGjfrIe3.js → AdminWithdrawalHistory-Cud-yuWy.js} +295 -283
- package/dist/AdminWithdrawalHistory-Cud-yuWy.js.map +1 -0
- package/dist/AdminWithdrawalHistory-DL9zbu2b.cjs +1 -0
- package/dist/AdminWithdrawalHistory-DL9zbu2b.cjs.map +1 -0
- package/dist/{AuthenticationSettings-5Vi7Ib_A.cjs → AuthenticationSettings-D739nNul.cjs} +1 -1
- package/dist/{AuthenticationSettings-5Vi7Ib_A.cjs.map → AuthenticationSettings-D739nNul.cjs.map} +1 -1
- package/dist/AuthenticationSettings-DtLoxQ2z.cjs +1 -0
- package/dist/{AuthenticationSettings-BPAh1my6.cjs.map → AuthenticationSettings-DtLoxQ2z.cjs.map} +1 -1
- package/dist/{AuthenticationSettings-CJg8CJY9.js → AuthenticationSettings-Dx3JCI3m.js} +1 -1
- package/dist/{AuthenticationSettings-CJg8CJY9.js.map → AuthenticationSettings-Dx3JCI3m.js.map} +1 -1
- package/dist/{AuthenticationSettings-CR_i6TTS.js → AuthenticationSettings-vowmQPXz.js} +170 -140
- package/dist/{AuthenticationSettings-CR_i6TTS.js.map → AuthenticationSettings-vowmQPXz.js.map} +1 -1
- package/dist/{AutosaveStatus-CYkC2aI_.cjs → AutosaveStatus-CCrZszKA.cjs} +1 -1
- package/dist/{AutosaveStatus-CYkC2aI_.cjs.map → AutosaveStatus-CCrZszKA.cjs.map} +1 -1
- package/dist/{AutosaveStatus-N4uNS6-2.js → AutosaveStatus-Ciyt350A.js} +1 -1
- package/dist/{AutosaveStatus-N4uNS6-2.js.map → AutosaveStatus-Ciyt350A.js.map} +1 -1
- package/dist/{CreditSystemSettings-CIf_SfJq.js → CreditSystemSettings-BQ3h4CyM.js} +1 -1
- package/dist/{CreditSystemSettings-CIf_SfJq.js.map → CreditSystemSettings-BQ3h4CyM.js.map} +1 -1
- package/dist/{CreditSystemSettings-BnAOK_tT.cjs → CreditSystemSettings-C6dUsu72.cjs} +1 -1
- package/dist/{CreditSystemSettings-BnAOK_tT.cjs.map → CreditSystemSettings-C6dUsu72.cjs.map} +1 -1
- package/dist/{CreditSystemSettings-Ck5WIMp3.cjs → CreditSystemSettings-CBot8EPW.cjs} +1 -1
- package/dist/{CreditSystemSettings-Ck5WIMp3.cjs.map → CreditSystemSettings-CBot8EPW.cjs.map} +1 -1
- package/dist/{CreditSystemSettings-CePYGgev.js → CreditSystemSettings-bVuNLsqp.js} +1 -1
- package/dist/{CreditSystemSettings-CePYGgev.js.map → CreditSystemSettings-bVuNLsqp.js.map} +1 -1
- package/dist/DepositsSection-BkKUS4vk.cjs +1 -0
- package/dist/DepositsSection-BkKUS4vk.cjs.map +1 -0
- package/dist/{DepositsSection-DA89uR9A.js → DepositsSection-DD9MKUFt.js} +20 -14
- package/dist/DepositsSection-DD9MKUFt.js.map +1 -0
- package/dist/EmailRegisterForm-Pvm3I8GP.cjs +1 -0
- package/dist/{EmailRegisterForm-CxktR-4J.cjs.map → EmailRegisterForm-Pvm3I8GP.cjs.map} +1 -1
- package/dist/{EmailRegisterForm-BrDL3BZy.js → EmailRegisterForm-nI0BOIxR.js} +2 -2
- package/dist/{EmailRegisterForm-BrDL3BZy.js.map → EmailRegisterForm-nI0BOIxR.js.map} +1 -1
- package/dist/{EmailSettings-DZywTTRq.cjs → EmailSettings-B9qwPSiM.cjs} +1 -1
- package/dist/{EmailSettings-DZywTTRq.cjs.map → EmailSettings-B9qwPSiM.cjs.map} +1 -1
- package/dist/{EmailSettings-DbMDfVaM.js → EmailSettings-CCA8dNCi.js} +1 -1
- package/dist/{EmailSettings-DbMDfVaM.js.map → EmailSettings-CCA8dNCi.js.map} +1 -1
- package/dist/{EmailSettings-B8xwgd6_.cjs → EmailSettings-RbgAq9FB.cjs} +1 -1
- package/dist/{EmailSettings-B8xwgd6_.cjs.map → EmailSettings-RbgAq9FB.cjs.map} +1 -1
- package/dist/{EmailSettings-Bna7Z53E.js → EmailSettings-mxlKNcPl.js} +1 -1
- package/dist/{EmailSettings-Bna7Z53E.js.map → EmailSettings-mxlKNcPl.js.map} +1 -1
- package/dist/{EmbeddedWalletSettings-DivEPn39.cjs → EmbeddedWalletSettings-B4h-8rxB.cjs} +1 -1
- package/dist/{EmbeddedWalletSettings-DivEPn39.cjs.map → EmbeddedWalletSettings-B4h-8rxB.cjs.map} +1 -1
- package/dist/{EmbeddedWalletSettings-BEztqO19.js → EmbeddedWalletSettings-BInZvFZf.js} +1 -1
- package/dist/{EmbeddedWalletSettings-BEztqO19.js.map → EmbeddedWalletSettings-BInZvFZf.js.map} +1 -1
- package/dist/{EmbeddedWalletSettings-ANbhj3Lt.js → EmbeddedWalletSettings-CXlZFFDw.js} +1 -1
- package/dist/{EmbeddedWalletSettings-ANbhj3Lt.js.map → EmbeddedWalletSettings-CXlZFFDw.js.map} +1 -1
- package/dist/{EmbeddedWalletSettings-D6M7pwgk.cjs → EmbeddedWalletSettings-D5JXae1L.cjs} +1 -1
- package/dist/{EmbeddedWalletSettings-D6M7pwgk.cjs.map → EmbeddedWalletSettings-D5JXae1L.cjs.map} +1 -1
- package/dist/{GoogleLoginButton-B3uRMJ_n.js → GoogleLoginButton-CXwp4LsQ.js} +1 -1
- package/dist/{GoogleLoginButton-B3uRMJ_n.js.map → GoogleLoginButton-CXwp4LsQ.js.map} +1 -1
- package/dist/GoogleLoginButton-zS_69-KV.cjs +1 -0
- package/dist/{GoogleLoginButton-BydKswn4.cjs.map → GoogleLoginButton-zS_69-KV.cjs.map} +1 -1
- package/dist/PermissionsSection-BPbE-hNx.cjs +1 -0
- package/dist/{PermissionsSection-CKXXDfqi.cjs.map → PermissionsSection-BPbE-hNx.cjs.map} +1 -1
- package/dist/{PermissionsSection-BGaj_sI7.js → PermissionsSection-CighC1p6.js} +3 -3
- package/dist/{PermissionsSection-BGaj_sI7.js.map → PermissionsSection-CighC1p6.js.map} +1 -1
- package/dist/{ServerSettings-B2RKhJtZ.js → ServerSettings-BE8fsE5k.js} +1 -1
- package/dist/{ServerSettings-B2RKhJtZ.js.map → ServerSettings-BE8fsE5k.js.map} +1 -1
- package/dist/{ServerSettings-BZXlm1BX.cjs → ServerSettings-CYTlQ2xy.cjs} +1 -1
- package/dist/{ServerSettings-BZXlm1BX.cjs.map → ServerSettings-CYTlQ2xy.cjs.map} +1 -1
- package/dist/{ServerSettings-DZUKo6By.cjs → ServerSettings-DFs9jQ3f.cjs} +1 -1
- package/dist/{ServerSettings-DZUKo6By.cjs.map → ServerSettings-DFs9jQ3f.cjs.map} +1 -1
- package/dist/{ServerSettings-Bqm4-bt2.js → ServerSettings-pSmWDC1d.js} +1 -1
- package/dist/{ServerSettings-Bqm4-bt2.js.map → ServerSettings-pSmWDC1d.js.map} +1 -1
- package/dist/{SettingsPageLayout-DpgNEkuu.js → SettingsPageLayout--GZ_iHLc.js} +1 -1
- package/dist/{SettingsPageLayout-DpgNEkuu.js.map → SettingsPageLayout--GZ_iHLc.js.map} +1 -1
- package/dist/{SettingsPageLayout-COSYLMu7.cjs → SettingsPageLayout-j5lMjEID.cjs} +1 -1
- package/dist/{SettingsPageLayout-COSYLMu7.cjs.map → SettingsPageLayout-j5lMjEID.cjs.map} +1 -1
- package/dist/SolanaLoginButton-BjOxpE1C.cjs +1 -0
- package/dist/{SolanaLoginButton-fAW7kRUu.cjs.map → SolanaLoginButton-BjOxpE1C.cjs.map} +1 -1
- package/dist/{SolanaLoginButton-C_u9OppS.js → SolanaLoginButton-P22QjBaO.js} +1 -1
- package/dist/{SolanaLoginButton-C_u9OppS.js.map → SolanaLoginButton-P22QjBaO.js.map} +1 -1
- package/dist/{TeamSection-CvrCoa9D.js → TeamSection-BIECkp7g.js} +2 -2
- package/dist/{TeamSection-CvrCoa9D.js.map → TeamSection-BIECkp7g.js.map} +1 -1
- package/dist/{TeamSection-DlUD5kp5.cjs → TeamSection-BOH9pv_E.cjs} +1 -1
- package/dist/{TeamSection-DlUD5kp5.cjs.map → TeamSection-BOH9pv_E.cjs.map} +1 -1
- package/dist/UsersSection-e6q7FHzx.cjs +1 -0
- package/dist/UsersSection-e6q7FHzx.cjs.map +1 -0
- package/dist/UsersSection-t-zm0jZW.js +33 -0
- package/dist/UsersSection-t-zm0jZW.js.map +1 -0
- package/dist/{WebhookSettings-bUg2u_p0.js → WebhookSettings-Bb70MbFj.js} +1 -1
- package/dist/{WebhookSettings-bUg2u_p0.js.map → WebhookSettings-Bb70MbFj.js.map} +1 -1
- package/dist/{WebhookSettings-BWl_wsvg.cjs → WebhookSettings-C-gvNAW1.cjs} +1 -1
- package/dist/{WebhookSettings-BWl_wsvg.cjs.map → WebhookSettings-C-gvNAW1.cjs.map} +1 -1
- package/dist/{WebhookSettings-DXNH5bal.cjs → WebhookSettings-CGyDKjrm.cjs} +1 -1
- package/dist/{WebhookSettings-DXNH5bal.cjs.map → WebhookSettings-CGyDKjrm.cjs.map} +1 -1
- package/dist/{WebhookSettings-BT5q6AZ8.js → WebhookSettings-kIstSjZi.js} +1 -1
- package/dist/{WebhookSettings-BT5q6AZ8.js.map → WebhookSettings-kIstSjZi.js.map} +1 -1
- package/dist/WithdrawalsSection-sljIyeaz.cjs +1 -0
- package/dist/WithdrawalsSection-sljIyeaz.cjs.map +1 -0
- package/dist/WithdrawalsSection-yRDTVFsb.js +27 -0
- package/dist/WithdrawalsSection-yRDTVFsb.js.map +1 -0
- package/dist/admin-only.cjs +1 -1
- package/dist/admin-only.js +1 -1
- package/dist/email-only.cjs +1 -1
- package/dist/email-only.d.ts +13 -2
- package/dist/email-only.js +3 -3
- package/dist/google-only.cjs +1 -1
- package/dist/google-only.d.ts +13 -2
- package/dist/google-only.js +3 -3
- package/dist/index.cjs +12 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +31 -8
- package/dist/index.js +1066 -1092
- package/dist/index.js.map +1 -1
- package/dist/{plugin-BtYBW6JY.cjs → plugin-8_dpq7tC.cjs} +1 -1
- package/dist/{plugin-BtYBW6JY.cjs.map → plugin-8_dpq7tC.cjs.map} +1 -1
- package/dist/{plugin-5qRh-YhX.js → plugin-D1NdppqC.js} +1 -1
- package/dist/{plugin-5qRh-YhX.js.map → plugin-D1NdppqC.js.map} +1 -1
- package/dist/{shamir-AeLLfw0p.cjs → shamir-4DyQMJCk.cjs} +1 -1
- package/dist/{shamir-AeLLfw0p.cjs.map → shamir-4DyQMJCk.cjs.map} +1 -1
- package/dist/{shamir-B0wConeK.js → shamir-L-s-Tp1Z.js} +1 -1
- package/dist/{shamir-B0wConeK.js.map → shamir-L-s-Tp1Z.js.map} +1 -1
- package/dist/silentWalletEnroll-BgTb4H5I.cjs +1 -0
- package/dist/{silentWalletEnroll-B8pgdKZO.cjs.map → silentWalletEnroll-BgTb4H5I.cjs.map} +1 -1
- package/dist/{silentWalletEnroll-DR2kPw7W.js → silentWalletEnroll-DWt6Pr3B.js} +3 -3
- package/dist/{silentWalletEnroll-DR2kPw7W.js.map → silentWalletEnroll-DWt6Pr3B.js.map} +1 -1
- package/dist/solana-only.cjs +1 -1
- package/dist/solana-only.d.ts +13 -2
- package/dist/solana-only.js +3 -3
- package/dist/{useAdminDeposits-BDY5KJ0-.js → useAdminDeposits-BTSyeAfg.js} +1 -1
- package/dist/{useAdminDeposits-BDY5KJ0-.js.map → useAdminDeposits-BTSyeAfg.js.map} +1 -1
- package/dist/useAdminDeposits-BkkCwHWp.cjs +1 -0
- package/dist/{useAdminDeposits-Dvx3_UUE.cjs.map → useAdminDeposits-BkkCwHWp.cjs.map} +1 -1
- package/dist/{useAuth-Bge6KaWN.js → useAuth-C3dpk0po.js} +597 -561
- package/dist/useAuth-C3dpk0po.js.map +1 -0
- package/dist/useAuth-D3Pk_H3z.cjs +1 -0
- package/dist/useAuth-D3Pk_H3z.cjs.map +1 -0
- package/dist/useCedrosLogin-C9MrcZvh.cjs +1 -0
- package/dist/useCedrosLogin-C9MrcZvh.cjs.map +1 -0
- package/dist/{apiClient-B2JxVPlH.js → useCedrosLogin-_94MmGGq.js} +29 -29
- package/dist/useCedrosLogin-_94MmGGq.js.map +1 -0
- package/dist/{useOrgs-Be3KH4ib.js → useOrgs-C3pzMA9h.js} +1 -1
- package/dist/{useOrgs-Be3KH4ib.js.map → useOrgs-C3pzMA9h.js.map} +1 -1
- package/dist/useOrgs-DDVRCaVi.cjs +1 -0
- package/dist/{useOrgs-CVbacmaQ.cjs.map → useOrgs-DDVRCaVi.cjs.map} +1 -1
- package/dist/{useSystemSettings-DN5YqfNq.js → useSystemSettings-DBlAMjFi.js} +1 -1
- package/dist/{useSystemSettings-DN5YqfNq.js.map → useSystemSettings-DBlAMjFi.js.map} +1 -1
- package/dist/useSystemSettings-DRrreszl.cjs +1 -0
- package/dist/{useSystemSettings-D9Cr7ZTl.cjs.map → useSystemSettings-DRrreszl.cjs.map} +1 -1
- package/dist/useUsersStatsSummary-8qY7iP4G.cjs +1 -0
- package/dist/useUsersStatsSummary-8qY7iP4G.cjs.map +1 -0
- package/dist/{AdminUserDetail-DHFDzY8B.js → useUsersStatsSummary-NjEFvWuz.js} +431 -380
- package/dist/useUsersStatsSummary-NjEFvWuz.js.map +1 -0
- package/package.json +1 -1
- package/dist/AdminDepositList-Cx0xRwES.js +0 -305
- package/dist/AdminDepositList-Cx0xRwES.js.map +0 -1
- package/dist/AdminDepositList-UEcyRZkA.cjs +0 -1
- package/dist/AdminDepositList-UEcyRZkA.cjs.map +0 -1
- package/dist/AdminUserDetail-BzEIdNJh.cjs +0 -1
- package/dist/AdminUserDetail-BzEIdNJh.cjs.map +0 -1
- package/dist/AdminUserDetail-DHFDzY8B.js.map +0 -1
- package/dist/AdminWithdrawalHistory-0yxtMh6q.cjs +0 -1
- package/dist/AdminWithdrawalHistory-0yxtMh6q.cjs.map +0 -1
- package/dist/AdminWithdrawalHistory-BGjfrIe3.js.map +0 -1
- package/dist/AuthenticationSettings-BPAh1my6.cjs +0 -1
- package/dist/DepositsSection-DA89uR9A.js.map +0 -1
- package/dist/DepositsSection-i6XdhLNs.cjs +0 -1
- package/dist/DepositsSection-i6XdhLNs.cjs.map +0 -1
- package/dist/EmailRegisterForm-CxktR-4J.cjs +0 -1
- package/dist/GoogleLoginButton-BydKswn4.cjs +0 -1
- package/dist/PermissionsSection-CKXXDfqi.cjs +0 -1
- package/dist/SolanaLoginButton-fAW7kRUu.cjs +0 -1
- package/dist/UsersSection-C2U8Tb7V.cjs +0 -1
- package/dist/UsersSection-C2U8Tb7V.cjs.map +0 -1
- package/dist/UsersSection-Dbh9PTSA.js +0 -83
- package/dist/UsersSection-Dbh9PTSA.js.map +0 -1
- package/dist/WithdrawalsSection-BL_LOUq8.cjs +0 -1
- package/dist/WithdrawalsSection-BL_LOUq8.cjs.map +0 -1
- package/dist/WithdrawalsSection-CN-lLnqX.js +0 -20
- package/dist/WithdrawalsSection-CN-lLnqX.js.map +0 -1
- package/dist/apiClient-B2JxVPlH.js.map +0 -1
- package/dist/apiClient-CTTKhsYb.cjs +0 -1
- package/dist/apiClient-CTTKhsYb.cjs.map +0 -1
- package/dist/silentWalletEnroll-B8pgdKZO.cjs +0 -1
- package/dist/useAdminDeposits-Dvx3_UUE.cjs +0 -1
- package/dist/useAuth-Bge6KaWN.js.map +0 -1
- package/dist/useAuth-DhIDTLRd.cjs +0 -1
- package/dist/useAuth-DhIDTLRd.cjs.map +0 -1
- package/dist/useOrgs-CVbacmaQ.cjs +0 -1
- package/dist/useSystemSettings-D9Cr7ZTl.cjs +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const h=require("react"),m=require("./LoadingSpinner-d6sSxgQN.cjs"),w=32;function C(){if(typeof document>"u")return null;const t=document.querySelector('meta[name="csrf-token"]');if(t){const r=t.getAttribute("content");if(r&&r.length>=w)return r}const e=document.cookie.split(";");for(const r of e){const[s,...i]=r.trim().split("="),a=i.join("="),l=s.toLowerCase();if(l==="xsrf-token"||l==="csrf-token")try{const u=decodeURIComponent(a.trim());if(u.length>=w)return u}catch{continue}}return null}const b=1e4,S=2;function v(t,e){return{code:t.code||"SERVER_ERROR",message:t.message||e,details:t.details}}function L(){return{code:"NETWORK_ERROR",message:"Unable to connect to server"}}async function k(t,e,r){const s=new AbortController,i=setTimeout(()=>s.abort(),r);try{return await fetch(t,{...e,signal:s.signal})}finally{clearTimeout(i)}}function x(t){if(t instanceof Error){if(t.retryable)return!0;if(t.name==="AbortError")return!1;if(t.message.includes("fetch"))return!0}return!1}function U(t){return new Promise(e=>setTimeout(e,t))}class O{baseUrl;timeoutMs;retryAttempts;getAccessToken;constructor(e){this.baseUrl=e.baseUrl,this.timeoutMs=e.timeoutMs??b,this.retryAttempts=e.retryAttempts??S,this.getAccessToken=e.getAccessToken}async request(e){const{method:r,path:s,body:i,credentials:a="include",skipRetry:l=!1,validator:u}=e,R=`${this.baseUrl}${s}`,E=l||!(r==="GET"||r==="HEAD"||r==="PUT")?1:this.retryAttempts+1,d={};i!==void 0&&(d["Content-Type"]="application/json");const A=this.getAccessToken?.();A&&(d.Authorization=`Bearer ${A}`);const T=C();T&&(d["X-CSRF-Token"]=T);let g;for(let f=1;f<=E;f++)try{const n=await k(R,{method:r,headers:d,credentials:a,body:i!==void 0?JSON.stringify(i):void 0},this.timeoutMs),y=n.headers.get("content-type")||"";let c={};if(y.includes("application/json")){if(n.status!==204)try{c=await n.json()}catch(o){const p=o instanceof Error?o.message:"parse failed";throw new Error(`Invalid JSON response: ${p}`)}}else{const o=await n.text();if(o){const p=o.length>200?o.slice(0,200)+"...":o;c={message:y.includes("text/html")||o.trimStart().startsWith("<")?`Unexpected HTML response (${n.status}). The server may be unavailable.`:p}}}if(!n.ok){if(n.status>=400&&n.status<500)throw{isApiError:!0,data:c,status:n.status};const o=new Error(`Server error: ${n.status}`);throw o.retryable=!0,o}if(u)try{return u(c)}catch(o){throw new Error(`Response validation failed: ${o instanceof Error?o.message:"Invalid response shape"}`)}return c}catch(n){if(g=n,typeof n=="object"&&n!==null&&"isApiError"in n)throw n;if(f<E&&x(n)){await U(100*Math.pow(2,f-1));continue}throw n}throw g}async post(e,r,s){return this.request({method:"POST",path:e,body:r,...s})}async get(e,r){return this.request({method:"GET",path:e,...r})}async patch(e,r,s){return this.request({method:"PATCH",path:e,body:r,...s})}async delete(e,r){return this.request({method:"DELETE",path:e,...r})}}function I(t){return typeof t=="object"&&t!==null&&"isApiError"in t}function _(t){return typeof t=="object"&&t!==null&&"code"in t&&"message"in t}function q(t,e){if(_(t))return t;if(I(t))return v(t.data,e);if(t instanceof Error){if(t.name==="AbortError")return{code:"NETWORK_ERROR",message:"Request timed out"};if(t.message.startsWith("Server error:")||t.message.startsWith("Invalid JSON response"))return{code:"SERVER_ERROR",message:e}}return L()}function M(){const t=h.useContext(m.CedrosLoginContext);if(!t)throw new Error("useCedrosLogin must be used within a CedrosLoginProvider");return t}function N(){return h.useContext(m.CedrosLoginContext)}function P(){const t=h.useContext(m.AuthStateContext);if(!t)throw new Error("useAuthState must be used within a CedrosLoginProvider");return t}function j(){const t=h.useContext(m.AuthUIContext);if(!t)throw new Error("useAuthUI must be used within a CedrosLoginProvider");return t}exports.ApiClient=O;exports.getCsrfToken=C;exports.handleApiError=q;exports.useAuthState=P;exports.useAuthUI=j;exports.useCedrosLogin=M;exports.useCedrosLoginOptional=N;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCedrosLogin-C9MrcZvh.cjs","sources":["../src/utils/csrf.ts","../src/utils/apiClient.ts","../src/context/useCedrosLogin.ts"],"sourcesContent":["// UI-CSRF: Minimum CSRF token length to prevent weak/trivial tokens\n// UI-07: Raised from 20 to 32 bytes to meet minimum entropy requirements\nconst MIN_CSRF_TOKEN_LENGTH = 32;\n\nexport function getCsrfToken(): string | null {\n if (typeof document === 'undefined') return null;\n\n const metaTag = document.querySelector('meta[name=\"csrf-token\"]');\n if (metaTag) {\n const content = metaTag.getAttribute('content');\n // UI-CSRF: Reject weak tokens\n if (content && content.length >= MIN_CSRF_TOKEN_LENGTH) {\n return content;\n }\n }\n\n // UI-2 FIX: Use case-insensitive comparison for cookie names.\n // Server may set cookie with different casing (XSRF-TOKEN, xsrf-token, etc.)\n const cookies = document.cookie.split(';');\n for (const cookie of cookies) {\n const [name, ...rest] = cookie.trim().split('=');\n const value = rest.join('=');\n const nameLower = name.toLowerCase();\n if (nameLower === 'xsrf-token' || nameLower === 'csrf-token') {\n try {\n const decoded = decodeURIComponent(value.trim());\n // UI-CSRF: Reject weak tokens\n if (decoded.length >= MIN_CSRF_TOKEN_LENGTH) {\n return decoded;\n }\n } catch {\n // Malformed URL-encoded value - skip this cookie\n continue;\n }\n }\n }\n\n return null;\n}\n","import type { AuthError, AuthErrorCode } from '../types';\nimport { getCsrfToken } from './csrf';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_RETRY_ATTEMPTS = 2;\n\nexport interface ApiClientConfig {\n baseUrl: string;\n timeoutMs?: number;\n retryAttempts?: number;\n getAccessToken?: () => string | null;\n}\n\n/**\n * M-02: Response validator function type.\n * Returns the validated data or throws on invalid shape.\n */\nexport type ResponseValidator<T> = (data: unknown) => T;\n\nexport interface RequestOptions<T = unknown> {\n method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n body?: unknown;\n credentials?: RequestCredentials;\n skipRetry?: boolean;\n /** M-02: Optional validator to verify response shape at runtime */\n validator?: ResponseValidator<T>;\n}\n\n/**\n * Creates an authentication error from response data\n */\nexport function createAuthError(\n data: { code?: string; message?: string; details?: Record<string, unknown> },\n fallbackMessage: string\n): AuthError {\n return {\n code: (data.code as AuthErrorCode) || 'SERVER_ERROR',\n message: data.message || fallbackMessage,\n details: data.details,\n };\n}\n\n/**\n * Creates a network error\n */\nexport function createNetworkError(): AuthError {\n return {\n code: 'NETWORK_ERROR',\n message: 'Unable to connect to server',\n };\n}\n\n/**\n * Fetch with timeout support\n */\nasync function fetchWithTimeout(\n url: string,\n options: RequestInit,\n timeoutMs: number\n): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Determines if an error is retryable\n * UI-8 FIX: AbortError (timeout) should NOT be retried - server may have processed request\n */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n if ((error as { retryable?: boolean }).retryable) return true;\n // UI-8: AbortError from timeout should NOT be retried\n // Server may have processed the request (just responded slowly)\n // Retrying could cause duplicate operations\n if (error.name === 'AbortError') return false;\n // Network errors (connection failed) are safe to retry\n if (error.message.includes('fetch')) return true;\n }\n return false;\n}\n\n/**\n * Delays execution for the specified duration\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * API client for making authenticated requests with timeout and retry support\n */\nexport class ApiClient {\n private baseUrl: string;\n private timeoutMs: number;\n private retryAttempts: number;\n private getAccessToken?: () => string | null;\n\n constructor(config: ApiClientConfig) {\n this.baseUrl = config.baseUrl;\n this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.retryAttempts = config.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;\n this.getAccessToken = config.getAccessToken;\n }\n\n /**\n * Make an API request with timeout and optional retry\n */\n async request<T>(options: RequestOptions<T>): Promise<T> {\n const { method, path, body, credentials = 'include', skipRetry = false, validator } = options;\n const url = `${this.baseUrl}${path}`;\n // S-10: DELETE excluded — retrying mid-flight DELETE failures risks double-deletion\n const isIdempotent = method === 'GET' || method === 'HEAD' || method === 'PUT';\n const maxAttempts = skipRetry || !isIdempotent ? 1 : this.retryAttempts + 1;\n\n // Build headers with CSRF token if available\n const headers: Record<string, string> = {};\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n const accessToken = this.getAccessToken?.();\n if (accessToken) {\n headers.Authorization = `Bearer ${accessToken}`;\n }\n const csrfToken = getCsrfToken();\n if (csrfToken) {\n headers['X-CSRF-Token'] = csrfToken;\n }\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n const response = await fetchWithTimeout(\n url,\n {\n method,\n headers,\n credentials,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n },\n this.timeoutMs\n );\n\n const contentType = response.headers.get('content-type') || '';\n let data: { code?: string; message?: string; details?: Record<string, unknown> } = {};\n\n if (contentType.includes('application/json')) {\n if (response.status !== 204) {\n try {\n data = (await response.json()) as {\n code?: string;\n message?: string;\n details?: Record<string, unknown>;\n };\n } catch (e) {\n // UI-JSON: Include actual parse error for easier debugging\n const parseError = e instanceof Error ? e.message : 'parse failed';\n throw new Error(`Invalid JSON response: ${parseError}`);\n }\n }\n } else {\n // U-01: Handle non-JSON responses with informative error messages\n // Proxies/load balancers may return HTML error pages (502, 503)\n const text = await response.text();\n if (text) {\n // Truncate very long responses (e.g., HTML pages) for readability\n const truncated = text.length > 200 ? text.slice(0, 200) + '...' : text;\n const isHtml = contentType.includes('text/html') || text.trimStart().startsWith('<');\n data = {\n message: isHtml\n ? `Unexpected HTML response (${response.status}). The server may be unavailable.`\n : truncated,\n };\n }\n }\n\n if (!response.ok) {\n // Don't retry 4xx errors (client errors)\n if (response.status >= 400 && response.status < 500) {\n throw { isApiError: true, data, status: response.status };\n }\n // Retry 5xx errors\n const err = new Error(`Server error: ${response.status}`);\n (err as { retryable?: boolean }).retryable = true;\n throw err;\n }\n\n // M-02: Apply response validation if provided\n if (validator) {\n try {\n return validator(data);\n } catch (validationError) {\n throw new Error(\n `Response validation failed: ${validationError instanceof Error ? validationError.message : 'Invalid response shape'}`\n );\n }\n }\n\n return data as T;\n } catch (error) {\n lastError = error;\n\n // Don't retry API errors (4xx responses)\n if (typeof error === 'object' && error !== null && 'isApiError' in error) {\n throw error;\n }\n\n // Check if we should retry\n if (attempt < maxAttempts && isRetryableError(error)) {\n // Exponential backoff: 100ms, 200ms, 400ms...\n await delay(100 * Math.pow(2, attempt - 1));\n continue;\n }\n\n throw error;\n }\n }\n\n throw lastError;\n }\n\n /**\n * POST request helper\n */\n async post<T>(path: string, body: unknown, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'POST', path, body, ...options });\n }\n\n /**\n * GET request helper\n */\n async get<T>(path: string, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'GET', path, ...options });\n }\n\n /**\n * PATCH request helper\n */\n async patch<T>(path: string, body: unknown, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body, ...options });\n }\n\n /**\n * DELETE request helper\n */\n async delete<T>(path: string, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, ...options });\n }\n}\n\ninterface ApiErrorResponse {\n isApiError: true;\n data: { code?: string; message?: string; details?: Record<string, unknown> };\n status: number;\n}\n\nfunction isApiErrorResponse(err: unknown): err is ApiErrorResponse {\n return typeof err === 'object' && err !== null && 'isApiError' in err;\n}\n\nfunction isAuthError(err: unknown): err is AuthError {\n return typeof err === 'object' && err !== null && 'code' in err && 'message' in err;\n}\n\n/**\n * M-02: Helper to create a basic object shape validator.\n * Checks that required keys exist and are of expected types.\n * @example\n * const validateUser = createValidator<User>({\n * id: 'string',\n * email: 'string',\n * role: 'string',\n * });\n */\nexport function createValidator<T>(\n shape: Record<keyof T & string, 'string' | 'number' | 'boolean' | 'object'>\n): ResponseValidator<T> {\n return (data: unknown): T => {\n if (typeof data !== 'object' || data === null) {\n throw new Error('Expected object response');\n }\n const obj = data as Record<string, unknown>;\n for (const [key, expectedType] of Object.entries(shape)) {\n if (!(key in obj)) {\n throw new Error(`Missing required field: ${key}`);\n }\n const actualType = typeof obj[key];\n if (actualType !== expectedType) {\n throw new Error(`Invalid type for ${key}: expected ${expectedType}, got ${actualType}`);\n }\n }\n return data as T;\n };\n}\n\n/**\n * Converts API errors to AuthError format\n */\nexport function handleApiError(err: unknown, fallbackMessage: string): AuthError {\n // Already an AuthError\n if (isAuthError(err)) {\n return err;\n }\n\n // API error response (4xx/5xx)\n if (isApiErrorResponse(err)) {\n return createAuthError(err.data, fallbackMessage);\n }\n\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return {\n code: 'NETWORK_ERROR',\n message: 'Request timed out',\n };\n }\n if (\n err.message.startsWith('Server error:') ||\n err.message.startsWith('Invalid JSON response')\n ) {\n return {\n code: 'SERVER_ERROR',\n message: fallbackMessage,\n };\n }\n }\n\n // Network or timeout error\n return createNetworkError();\n}\n","import { useContext } from 'react';\nimport {\n AuthStateContext,\n AuthUIContext,\n CedrosLoginContext,\n type AuthStateContextValue,\n type AuthUIContextValue,\n type CedrosLoginContextValue,\n} from './CedrosLoginContext';\n\n/**\n * Hook to access the full Cedros Login context.\n * Must be used within a CedrosLoginProvider.\n *\n * For better performance, prefer `useAuthState()` or `useAuthUI()` when you\n * only need a subset of the context. This hook re-renders on any change.\n */\nexport function useCedrosLogin(): CedrosLoginContextValue {\n const context = useContext(CedrosLoginContext);\n if (!context) {\n throw new Error('useCedrosLogin must be used within a CedrosLoginProvider');\n }\n return context;\n}\n\n/**\n * Optional version of useCedrosLogin that returns null instead of throwing\n * when used outside a CedrosLoginProvider. Useful for components that need\n * to work in both provider and non-provider contexts (e.g., Storybook demos).\n */\nexport function useCedrosLoginOptional(): CedrosLoginContextValue | null {\n return useContext(CedrosLoginContext);\n}\n\n/**\n * Hook to access only auth state (user, authState, config, logout, refreshUser).\n *\n * Does NOT re-render on UI state changes (modal, error). Use this in components\n * that only need to know about authentication status.\n */\nexport function useAuthState(): AuthStateContextValue {\n const context = useContext(AuthStateContext);\n if (!context) {\n throw new Error('useAuthState must be used within a CedrosLoginProvider');\n }\n return context;\n}\n\n/**\n * Hook to access only UI state (isModalOpen, error, openModal, closeModal).\n *\n * Does NOT re-render on auth state changes (login, token refresh). Use this\n * in components that only control the login modal or display errors.\n */\nexport function useAuthUI(): AuthUIContextValue {\n const context = useContext(AuthUIContext);\n if (!context) {\n throw new Error('useAuthUI must be used within a CedrosLoginProvider');\n }\n return context;\n}\n"],"names":["MIN_CSRF_TOKEN_LENGTH","getCsrfToken","metaTag","content","cookies","cookie","name","rest","value","nameLower","decoded","DEFAULT_TIMEOUT_MS","DEFAULT_RETRY_ATTEMPTS","createAuthError","data","fallbackMessage","createNetworkError","fetchWithTimeout","url","options","timeoutMs","controller","timeoutId","isRetryableError","error","delay","ms","resolve","ApiClient","config","method","path","body","credentials","skipRetry","validator","maxAttempts","headers","accessToken","csrfToken","lastError","attempt","response","contentType","e","parseError","text","truncated","err","validationError","isApiErrorResponse","isAuthError","handleApiError","useCedrosLogin","context","useContext","CedrosLoginContext","useCedrosLoginOptional","useAuthState","AuthStateContext","useAuthUI","AuthUIContext"],"mappings":"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"}
|
|
@@ -23,27 +23,6 @@ function v() {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
function $() {
|
|
27
|
-
const t = h(w);
|
|
28
|
-
if (!t)
|
|
29
|
-
throw new Error("useCedrosLogin must be used within a CedrosLoginProvider");
|
|
30
|
-
return t;
|
|
31
|
-
}
|
|
32
|
-
function H() {
|
|
33
|
-
return h(w);
|
|
34
|
-
}
|
|
35
|
-
function W() {
|
|
36
|
-
const t = h(b);
|
|
37
|
-
if (!t)
|
|
38
|
-
throw new Error("useAuthState must be used within a CedrosLoginProvider");
|
|
39
|
-
return t;
|
|
40
|
-
}
|
|
41
|
-
function D() {
|
|
42
|
-
const t = h(C);
|
|
43
|
-
if (!t)
|
|
44
|
-
throw new Error("useAuthUI must be used within a CedrosLoginProvider");
|
|
45
|
-
return t;
|
|
46
|
-
}
|
|
47
26
|
const S = 1e4, k = 2;
|
|
48
27
|
function x(t, e) {
|
|
49
28
|
return {
|
|
@@ -80,7 +59,7 @@ function O(t) {
|
|
|
80
59
|
function I(t) {
|
|
81
60
|
return new Promise((e) => setTimeout(e, t));
|
|
82
61
|
}
|
|
83
|
-
class
|
|
62
|
+
class $ {
|
|
84
63
|
baseUrl;
|
|
85
64
|
timeoutMs;
|
|
86
65
|
retryAttempts;
|
|
@@ -186,7 +165,7 @@ function _(t) {
|
|
|
186
165
|
function M(t) {
|
|
187
166
|
return typeof t == "object" && t !== null && "code" in t && "message" in t;
|
|
188
167
|
}
|
|
189
|
-
function
|
|
168
|
+
function H(t, e) {
|
|
190
169
|
if (M(t))
|
|
191
170
|
return t;
|
|
192
171
|
if (_(t))
|
|
@@ -205,12 +184,33 @@ function G(t, e) {
|
|
|
205
184
|
}
|
|
206
185
|
return L();
|
|
207
186
|
}
|
|
187
|
+
function W() {
|
|
188
|
+
const t = h(w);
|
|
189
|
+
if (!t)
|
|
190
|
+
throw new Error("useCedrosLogin must be used within a CedrosLoginProvider");
|
|
191
|
+
return t;
|
|
192
|
+
}
|
|
193
|
+
function D() {
|
|
194
|
+
return h(w);
|
|
195
|
+
}
|
|
196
|
+
function F() {
|
|
197
|
+
const t = h(b);
|
|
198
|
+
if (!t)
|
|
199
|
+
throw new Error("useAuthState must be used within a CedrosLoginProvider");
|
|
200
|
+
return t;
|
|
201
|
+
}
|
|
202
|
+
function G() {
|
|
203
|
+
const t = h(C);
|
|
204
|
+
if (!t)
|
|
205
|
+
throw new Error("useAuthUI must be used within a CedrosLoginProvider");
|
|
206
|
+
return t;
|
|
207
|
+
}
|
|
208
208
|
export {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
$ as A,
|
|
210
|
+
D as a,
|
|
211
|
+
F as b,
|
|
212
|
+
G as c,
|
|
213
213
|
v as g,
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
H as h,
|
|
215
|
+
W as u
|
|
216
216
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCedrosLogin-_94MmGGq.js","sources":["../src/utils/csrf.ts","../src/utils/apiClient.ts","../src/context/useCedrosLogin.ts"],"sourcesContent":["// UI-CSRF: Minimum CSRF token length to prevent weak/trivial tokens\n// UI-07: Raised from 20 to 32 bytes to meet minimum entropy requirements\nconst MIN_CSRF_TOKEN_LENGTH = 32;\n\nexport function getCsrfToken(): string | null {\n if (typeof document === 'undefined') return null;\n\n const metaTag = document.querySelector('meta[name=\"csrf-token\"]');\n if (metaTag) {\n const content = metaTag.getAttribute('content');\n // UI-CSRF: Reject weak tokens\n if (content && content.length >= MIN_CSRF_TOKEN_LENGTH) {\n return content;\n }\n }\n\n // UI-2 FIX: Use case-insensitive comparison for cookie names.\n // Server may set cookie with different casing (XSRF-TOKEN, xsrf-token, etc.)\n const cookies = document.cookie.split(';');\n for (const cookie of cookies) {\n const [name, ...rest] = cookie.trim().split('=');\n const value = rest.join('=');\n const nameLower = name.toLowerCase();\n if (nameLower === 'xsrf-token' || nameLower === 'csrf-token') {\n try {\n const decoded = decodeURIComponent(value.trim());\n // UI-CSRF: Reject weak tokens\n if (decoded.length >= MIN_CSRF_TOKEN_LENGTH) {\n return decoded;\n }\n } catch {\n // Malformed URL-encoded value - skip this cookie\n continue;\n }\n }\n }\n\n return null;\n}\n","import type { AuthError, AuthErrorCode } from '../types';\nimport { getCsrfToken } from './csrf';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_RETRY_ATTEMPTS = 2;\n\nexport interface ApiClientConfig {\n baseUrl: string;\n timeoutMs?: number;\n retryAttempts?: number;\n getAccessToken?: () => string | null;\n}\n\n/**\n * M-02: Response validator function type.\n * Returns the validated data or throws on invalid shape.\n */\nexport type ResponseValidator<T> = (data: unknown) => T;\n\nexport interface RequestOptions<T = unknown> {\n method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n path: string;\n body?: unknown;\n credentials?: RequestCredentials;\n skipRetry?: boolean;\n /** M-02: Optional validator to verify response shape at runtime */\n validator?: ResponseValidator<T>;\n}\n\n/**\n * Creates an authentication error from response data\n */\nexport function createAuthError(\n data: { code?: string; message?: string; details?: Record<string, unknown> },\n fallbackMessage: string\n): AuthError {\n return {\n code: (data.code as AuthErrorCode) || 'SERVER_ERROR',\n message: data.message || fallbackMessage,\n details: data.details,\n };\n}\n\n/**\n * Creates a network error\n */\nexport function createNetworkError(): AuthError {\n return {\n code: 'NETWORK_ERROR',\n message: 'Unable to connect to server',\n };\n}\n\n/**\n * Fetch with timeout support\n */\nasync function fetchWithTimeout(\n url: string,\n options: RequestInit,\n timeoutMs: number\n): Promise<Response> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n return response;\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Determines if an error is retryable\n * UI-8 FIX: AbortError (timeout) should NOT be retried - server may have processed request\n */\nfunction isRetryableError(error: unknown): boolean {\n if (error instanceof Error) {\n if ((error as { retryable?: boolean }).retryable) return true;\n // UI-8: AbortError from timeout should NOT be retried\n // Server may have processed the request (just responded slowly)\n // Retrying could cause duplicate operations\n if (error.name === 'AbortError') return false;\n // Network errors (connection failed) are safe to retry\n if (error.message.includes('fetch')) return true;\n }\n return false;\n}\n\n/**\n * Delays execution for the specified duration\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * API client for making authenticated requests with timeout and retry support\n */\nexport class ApiClient {\n private baseUrl: string;\n private timeoutMs: number;\n private retryAttempts: number;\n private getAccessToken?: () => string | null;\n\n constructor(config: ApiClientConfig) {\n this.baseUrl = config.baseUrl;\n this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.retryAttempts = config.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;\n this.getAccessToken = config.getAccessToken;\n }\n\n /**\n * Make an API request with timeout and optional retry\n */\n async request<T>(options: RequestOptions<T>): Promise<T> {\n const { method, path, body, credentials = 'include', skipRetry = false, validator } = options;\n const url = `${this.baseUrl}${path}`;\n // S-10: DELETE excluded — retrying mid-flight DELETE failures risks double-deletion\n const isIdempotent = method === 'GET' || method === 'HEAD' || method === 'PUT';\n const maxAttempts = skipRetry || !isIdempotent ? 1 : this.retryAttempts + 1;\n\n // Build headers with CSRF token if available\n const headers: Record<string, string> = {};\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n const accessToken = this.getAccessToken?.();\n if (accessToken) {\n headers.Authorization = `Bearer ${accessToken}`;\n }\n const csrfToken = getCsrfToken();\n if (csrfToken) {\n headers['X-CSRF-Token'] = csrfToken;\n }\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n const response = await fetchWithTimeout(\n url,\n {\n method,\n headers,\n credentials,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n },\n this.timeoutMs\n );\n\n const contentType = response.headers.get('content-type') || '';\n let data: { code?: string; message?: string; details?: Record<string, unknown> } = {};\n\n if (contentType.includes('application/json')) {\n if (response.status !== 204) {\n try {\n data = (await response.json()) as {\n code?: string;\n message?: string;\n details?: Record<string, unknown>;\n };\n } catch (e) {\n // UI-JSON: Include actual parse error for easier debugging\n const parseError = e instanceof Error ? e.message : 'parse failed';\n throw new Error(`Invalid JSON response: ${parseError}`);\n }\n }\n } else {\n // U-01: Handle non-JSON responses with informative error messages\n // Proxies/load balancers may return HTML error pages (502, 503)\n const text = await response.text();\n if (text) {\n // Truncate very long responses (e.g., HTML pages) for readability\n const truncated = text.length > 200 ? text.slice(0, 200) + '...' : text;\n const isHtml = contentType.includes('text/html') || text.trimStart().startsWith('<');\n data = {\n message: isHtml\n ? `Unexpected HTML response (${response.status}). The server may be unavailable.`\n : truncated,\n };\n }\n }\n\n if (!response.ok) {\n // Don't retry 4xx errors (client errors)\n if (response.status >= 400 && response.status < 500) {\n throw { isApiError: true, data, status: response.status };\n }\n // Retry 5xx errors\n const err = new Error(`Server error: ${response.status}`);\n (err as { retryable?: boolean }).retryable = true;\n throw err;\n }\n\n // M-02: Apply response validation if provided\n if (validator) {\n try {\n return validator(data);\n } catch (validationError) {\n throw new Error(\n `Response validation failed: ${validationError instanceof Error ? validationError.message : 'Invalid response shape'}`\n );\n }\n }\n\n return data as T;\n } catch (error) {\n lastError = error;\n\n // Don't retry API errors (4xx responses)\n if (typeof error === 'object' && error !== null && 'isApiError' in error) {\n throw error;\n }\n\n // Check if we should retry\n if (attempt < maxAttempts && isRetryableError(error)) {\n // Exponential backoff: 100ms, 200ms, 400ms...\n await delay(100 * Math.pow(2, attempt - 1));\n continue;\n }\n\n throw error;\n }\n }\n\n throw lastError;\n }\n\n /**\n * POST request helper\n */\n async post<T>(path: string, body: unknown, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'POST', path, body, ...options });\n }\n\n /**\n * GET request helper\n */\n async get<T>(path: string, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'GET', path, ...options });\n }\n\n /**\n * PATCH request helper\n */\n async patch<T>(path: string, body: unknown, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'PATCH', path, body, ...options });\n }\n\n /**\n * DELETE request helper\n */\n async delete<T>(path: string, options?: Partial<RequestOptions<T>>): Promise<T> {\n return this.request<T>({ method: 'DELETE', path, ...options });\n }\n}\n\ninterface ApiErrorResponse {\n isApiError: true;\n data: { code?: string; message?: string; details?: Record<string, unknown> };\n status: number;\n}\n\nfunction isApiErrorResponse(err: unknown): err is ApiErrorResponse {\n return typeof err === 'object' && err !== null && 'isApiError' in err;\n}\n\nfunction isAuthError(err: unknown): err is AuthError {\n return typeof err === 'object' && err !== null && 'code' in err && 'message' in err;\n}\n\n/**\n * M-02: Helper to create a basic object shape validator.\n * Checks that required keys exist and are of expected types.\n * @example\n * const validateUser = createValidator<User>({\n * id: 'string',\n * email: 'string',\n * role: 'string',\n * });\n */\nexport function createValidator<T>(\n shape: Record<keyof T & string, 'string' | 'number' | 'boolean' | 'object'>\n): ResponseValidator<T> {\n return (data: unknown): T => {\n if (typeof data !== 'object' || data === null) {\n throw new Error('Expected object response');\n }\n const obj = data as Record<string, unknown>;\n for (const [key, expectedType] of Object.entries(shape)) {\n if (!(key in obj)) {\n throw new Error(`Missing required field: ${key}`);\n }\n const actualType = typeof obj[key];\n if (actualType !== expectedType) {\n throw new Error(`Invalid type for ${key}: expected ${expectedType}, got ${actualType}`);\n }\n }\n return data as T;\n };\n}\n\n/**\n * Converts API errors to AuthError format\n */\nexport function handleApiError(err: unknown, fallbackMessage: string): AuthError {\n // Already an AuthError\n if (isAuthError(err)) {\n return err;\n }\n\n // API error response (4xx/5xx)\n if (isApiErrorResponse(err)) {\n return createAuthError(err.data, fallbackMessage);\n }\n\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return {\n code: 'NETWORK_ERROR',\n message: 'Request timed out',\n };\n }\n if (\n err.message.startsWith('Server error:') ||\n err.message.startsWith('Invalid JSON response')\n ) {\n return {\n code: 'SERVER_ERROR',\n message: fallbackMessage,\n };\n }\n }\n\n // Network or timeout error\n return createNetworkError();\n}\n","import { useContext } from 'react';\nimport {\n AuthStateContext,\n AuthUIContext,\n CedrosLoginContext,\n type AuthStateContextValue,\n type AuthUIContextValue,\n type CedrosLoginContextValue,\n} from './CedrosLoginContext';\n\n/**\n * Hook to access the full Cedros Login context.\n * Must be used within a CedrosLoginProvider.\n *\n * For better performance, prefer `useAuthState()` or `useAuthUI()` when you\n * only need a subset of the context. This hook re-renders on any change.\n */\nexport function useCedrosLogin(): CedrosLoginContextValue {\n const context = useContext(CedrosLoginContext);\n if (!context) {\n throw new Error('useCedrosLogin must be used within a CedrosLoginProvider');\n }\n return context;\n}\n\n/**\n * Optional version of useCedrosLogin that returns null instead of throwing\n * when used outside a CedrosLoginProvider. Useful for components that need\n * to work in both provider and non-provider contexts (e.g., Storybook demos).\n */\nexport function useCedrosLoginOptional(): CedrosLoginContextValue | null {\n return useContext(CedrosLoginContext);\n}\n\n/**\n * Hook to access only auth state (user, authState, config, logout, refreshUser).\n *\n * Does NOT re-render on UI state changes (modal, error). Use this in components\n * that only need to know about authentication status.\n */\nexport function useAuthState(): AuthStateContextValue {\n const context = useContext(AuthStateContext);\n if (!context) {\n throw new Error('useAuthState must be used within a CedrosLoginProvider');\n }\n return context;\n}\n\n/**\n * Hook to access only UI state (isModalOpen, error, openModal, closeModal).\n *\n * Does NOT re-render on auth state changes (login, token refresh). Use this\n * in components that only control the login modal or display errors.\n */\nexport function useAuthUI(): AuthUIContextValue {\n const context = useContext(AuthUIContext);\n if (!context) {\n throw new Error('useAuthUI must be used within a CedrosLoginProvider');\n }\n return context;\n}\n"],"names":["MIN_CSRF_TOKEN_LENGTH","getCsrfToken","metaTag","content","cookies","cookie","name","rest","value","nameLower","decoded","DEFAULT_TIMEOUT_MS","DEFAULT_RETRY_ATTEMPTS","createAuthError","data","fallbackMessage","createNetworkError","fetchWithTimeout","url","options","timeoutMs","controller","timeoutId","isRetryableError","error","delay","ms","resolve","ApiClient","config","method","path","body","credentials","skipRetry","validator","maxAttempts","headers","accessToken","csrfToken","lastError","attempt","response","contentType","e","parseError","text","truncated","err","validationError","isApiErrorResponse","isAuthError","handleApiError","useCedrosLogin","context","useContext","CedrosLoginContext","useCedrosLoginOptional","useAuthState","AuthStateContext","useAuthUI","AuthUIContext"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState as f, useMemo as G, useRef as C, useEffect as S, useCallback as u } from "react";
|
|
2
|
-
import { A as K, h as g, u as x } from "./
|
|
2
|
+
import { A as K, h as g, u as x } from "./useCedrosLogin-_94MmGGq.js";
|
|
3
3
|
class M {
|
|
4
4
|
client;
|
|
5
5
|
constructor(e, t, h, l) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOrgs-Be3KH4ib.js","sources":["../src/utils/orgApi.ts","../src/hooks/useOrgs.ts"],"sourcesContent":["import type {\n Organization,\n OrgWithMembership,\n CreateOrgRequest,\n UpdateOrgRequest,\n ListOrgsResponse,\n AuthorizeRequest,\n AuthorizeResponse,\n PermissionsResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for organization operations\n */\nexport class OrgApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * List all organizations the current user belongs to\n */\n async listOrgs(): Promise<OrgWithMembership[]> {\n try {\n const response = await this.client.get<ListOrgsResponse>('/orgs');\n return response.orgs.map((org) => ({\n ...org,\n membership: {\n orgId: org.id,\n role: org.role,\n },\n }));\n } catch (error) {\n throw handleApiError(error, 'Failed to list organizations');\n }\n }\n\n /**\n * Get a single organization by ID\n */\n async getOrg(orgId: string): Promise<Organization> {\n try {\n return await this.client.get<Organization>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to get organization');\n }\n }\n\n /**\n * Create a new organization\n */\n async createOrg(data: CreateOrgRequest): Promise<Organization> {\n try {\n return await this.client.post<Organization>('/orgs', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to create organization');\n }\n }\n\n /**\n * Update an organization\n */\n async updateOrg(orgId: string, data: UpdateOrgRequest): Promise<Organization> {\n try {\n return await this.client.patch<Organization>(`/orgs/${orgId}`, data);\n } catch (error) {\n throw handleApiError(error, 'Failed to update organization');\n }\n }\n\n /**\n * Delete an organization\n */\n async deleteOrg(orgId: string): Promise<void> {\n try {\n await this.client.delete<void>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to delete organization');\n }\n }\n\n /**\n * Check authorization for an action\n */\n async authorize(data: AuthorizeRequest): Promise<AuthorizeResponse> {\n try {\n return await this.client.post<AuthorizeResponse>('/authorize', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to check authorization');\n }\n }\n\n /**\n * Get current user's permissions in an organization\n */\n async getPermissions(orgId: string): Promise<PermissionsResponse> {\n try {\n return await this.client.post<PermissionsResponse>('/permissions', { orgId });\n } catch (error) {\n throw handleApiError(error, 'Failed to get permissions');\n }\n }\n}\n","import { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport type {\n OrgWithMembership,\n Organization,\n CreateOrgRequest,\n UpdateOrgRequest,\n Permission,\n OrgRole,\n AuthError,\n} from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { OrgApiClient } from '../utils/orgApi';\n\nexport interface UseOrgsReturn {\n /** All organizations the user belongs to */\n orgs: OrgWithMembership[];\n /** Currently active organization */\n activeOrg: OrgWithMembership | null;\n /** User's permissions in the active org */\n permissions: Permission[];\n /** User's role in the active org */\n role: OrgRole | null;\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: AuthError | null;\n /** Fetch/refresh organizations list */\n fetchOrgs: () => Promise<void>;\n /** Switch to a different organization */\n switchOrg: (orgId: string) => Promise<void>;\n /** Create a new organization */\n createOrg: (data: CreateOrgRequest) => Promise<Organization>;\n /** Update an organization */\n updateOrg: (orgId: string, data: UpdateOrgRequest) => Promise<Organization>;\n /** Delete an organization */\n deleteOrg: (orgId: string) => Promise<void>;\n /** Check if user has a specific permission */\n hasPermission: (permission: Permission) => boolean;\n}\n\nconst ACTIVE_ORG_KEY = 'cedros_active_org';\n\n// P-06: Safe localStorage helpers to handle private browsing and quota errors\nfunction safeGetItem(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction safeSetItem(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Ignore - private browsing or quota exceeded\n }\n}\n\n/**\n * Hook for managing organizations, memberships, and permissions.\n *\n * @example\n * ```tsx\n * function OrgSelector() {\n * const { orgs, activeOrg, switchOrg, hasPermission } = useOrgs();\n *\n * return (\n * <select\n * value={activeOrg?.id}\n * onChange={(e) => switchOrg(e.target.value)}\n * >\n * {orgs.map(org => (\n * <option key={org.id} value={org.id}>{org.name}</option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport function useOrgs(): UseOrgsReturn {\n const { config, user, authState, _internal } = useCedrosLogin();\n const hasStorage = typeof window !== 'undefined' && !!window.localStorage;\n\n const [orgs, setOrgs] = useState<OrgWithMembership[]>([]);\n const [activeOrg, setActiveOrg] = useState<OrgWithMembership | null>(null);\n const [permissions, setPermissions] = useState<Permission[]>([]);\n const [role, setRole] = useState<OrgRole | null>(null);\n const [isLoading, setIsLoading] = useState(authState === 'authenticated');\n const [error, setError] = useState<AuthError | null>(null);\n\n // M-03: Memoize API client and use ref to prevent callback dependency cascades\n const apiClient = useMemo(\n () =>\n new OrgApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // M-03: Store apiClient in ref to stabilize callback dependencies\n const apiClientRef = useRef(apiClient);\n useEffect(() => {\n apiClientRef.current = apiClient;\n }, [apiClient]);\n\n // M-03: Use ref in callback to break dependency chain\n const loadPermissions = useCallback(async (orgId: string) => {\n try {\n const response = await apiClientRef.current.getPermissions(orgId);\n setPermissions(response.permissions);\n setRole(response.role);\n } catch {\n // Permissions loading failure is non-fatal, just clear them\n setPermissions([]);\n setRole(null);\n }\n }, []);\n\n // Ref to latest fetchOrgs for stable auto-fetch effect\n const fetchOrgsRef = useRef<() => Promise<void>>(async () => {});\n\n // M-03: Use ref to break apiClient dependency chain\n const fetchOrgs = useCallback(async () => {\n if (authState !== 'authenticated' || !user) {\n setOrgs([]);\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const fetchedOrgs = await apiClientRef.current.listOrgs();\n setOrgs(fetchedOrgs);\n\n // P-06: Restore active org from localStorage with safe access\n const savedOrgId = hasStorage ? safeGetItem(ACTIVE_ORG_KEY) : null;\n let selectedOrg = fetchedOrgs.find((org) => org.id === savedOrgId);\n\n if (!selectedOrg && fetchedOrgs.length > 0) {\n // Default to personal org or first org\n selectedOrg = fetchedOrgs.find((org) => org.isPersonal) || fetchedOrgs[0];\n }\n\n if (selectedOrg) {\n setActiveOrg(selectedOrg);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, selectedOrg.id);\n }\n await loadPermissions(selectedOrg.id);\n } else {\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n }\n } catch (err) {\n setError(err as AuthError);\n } finally {\n setIsLoading(false);\n }\n }, [authState, user, loadPermissions, hasStorage]);\n\n // Keep fetchOrgsRef current so auto-fetch always calls the latest version\n useEffect(() => {\n fetchOrgsRef.current = fetchOrgs;\n }, [fetchOrgs]);\n\n // Auto-fetch orgs when auth becomes ready.\n // Uses only `authState` (a string) as dep — immune to object-identity churn\n // from the AdminShell bridge recreating `user` objects.\n const hasAutoFetched = useRef(false);\n useEffect(() => {\n if (authState === 'authenticated' && !hasAutoFetched.current) {\n hasAutoFetched.current = true;\n fetchOrgsRef.current();\n } else if (authState !== 'authenticated') {\n hasAutoFetched.current = false;\n }\n }, [authState]);\n\n const switchOrg = useCallback(\n async (orgId: string) => {\n const org = orgs.find((o) => o.id === orgId);\n if (!org) {\n setError({ code: 'UNKNOWN_ERROR', message: 'Organization not found' });\n return;\n }\n\n setActiveOrg(org);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, orgId);\n }\n await loadPermissions(orgId);\n },\n [orgs, loadPermissions, hasStorage]\n );\n\n // M-03: Use ref to break apiClient dependency chain\n const createOrg = useCallback(\n async (data: CreateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const newOrg = await apiClientRef.current.createOrg(data);\n // Refresh orgs list to include the new org with membership\n await fetchOrgs();\n return newOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const updateOrg = useCallback(\n async (orgId: string, data: UpdateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const updatedOrg = await apiClientRef.current.updateOrg(orgId, data);\n // Refresh orgs list to reflect changes\n await fetchOrgs();\n return updatedOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const deleteOrg = useCallback(\n async (orgId: string): Promise<void> => {\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.deleteOrg(orgId);\n // Refresh orgs list\n await fetchOrgs();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const hasPermission = useCallback(\n (permission: Permission): boolean => {\n return permissions.includes(permission);\n },\n [permissions]\n );\n\n return {\n orgs,\n activeOrg,\n permissions,\n role,\n isLoading,\n error,\n fetchOrgs,\n switchOrg,\n createOrg,\n updateOrg,\n deleteOrg,\n hasPermission,\n };\n}\n"],"names":["OrgApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","org","error","handleApiError","orgId","data","ACTIVE_ORG_KEY","safeGetItem","key","safeSetItem","value","useOrgs","config","user","authState","_internal","useCedrosLogin","hasStorage","orgs","setOrgs","useState","activeOrg","setActiveOrg","permissions","setPermissions","role","setRole","isLoading","setIsLoading","setError","apiClient","useMemo","apiClientRef","useRef","useEffect","loadPermissions","useCallback","response","fetchOrgsRef","fetchOrgs","fetchedOrgs","savedOrgId","selectedOrg","err","hasAutoFetched","switchOrg","o","createOrg","newOrg","updateOrg","updatedOrg","deleteOrg","hasPermission","permission"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useOrgs-C3pzMA9h.js","sources":["../src/utils/orgApi.ts","../src/hooks/useOrgs.ts"],"sourcesContent":["import type {\n Organization,\n OrgWithMembership,\n CreateOrgRequest,\n UpdateOrgRequest,\n ListOrgsResponse,\n AuthorizeRequest,\n AuthorizeResponse,\n PermissionsResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for organization operations\n */\nexport class OrgApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * List all organizations the current user belongs to\n */\n async listOrgs(): Promise<OrgWithMembership[]> {\n try {\n const response = await this.client.get<ListOrgsResponse>('/orgs');\n return response.orgs.map((org) => ({\n ...org,\n membership: {\n orgId: org.id,\n role: org.role,\n },\n }));\n } catch (error) {\n throw handleApiError(error, 'Failed to list organizations');\n }\n }\n\n /**\n * Get a single organization by ID\n */\n async getOrg(orgId: string): Promise<Organization> {\n try {\n return await this.client.get<Organization>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to get organization');\n }\n }\n\n /**\n * Create a new organization\n */\n async createOrg(data: CreateOrgRequest): Promise<Organization> {\n try {\n return await this.client.post<Organization>('/orgs', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to create organization');\n }\n }\n\n /**\n * Update an organization\n */\n async updateOrg(orgId: string, data: UpdateOrgRequest): Promise<Organization> {\n try {\n return await this.client.patch<Organization>(`/orgs/${orgId}`, data);\n } catch (error) {\n throw handleApiError(error, 'Failed to update organization');\n }\n }\n\n /**\n * Delete an organization\n */\n async deleteOrg(orgId: string): Promise<void> {\n try {\n await this.client.delete<void>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to delete organization');\n }\n }\n\n /**\n * Check authorization for an action\n */\n async authorize(data: AuthorizeRequest): Promise<AuthorizeResponse> {\n try {\n return await this.client.post<AuthorizeResponse>('/authorize', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to check authorization');\n }\n }\n\n /**\n * Get current user's permissions in an organization\n */\n async getPermissions(orgId: string): Promise<PermissionsResponse> {\n try {\n return await this.client.post<PermissionsResponse>('/permissions', { orgId });\n } catch (error) {\n throw handleApiError(error, 'Failed to get permissions');\n }\n }\n}\n","import { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport type {\n OrgWithMembership,\n Organization,\n CreateOrgRequest,\n UpdateOrgRequest,\n Permission,\n OrgRole,\n AuthError,\n} from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { OrgApiClient } from '../utils/orgApi';\n\nexport interface UseOrgsReturn {\n /** All organizations the user belongs to */\n orgs: OrgWithMembership[];\n /** Currently active organization */\n activeOrg: OrgWithMembership | null;\n /** User's permissions in the active org */\n permissions: Permission[];\n /** User's role in the active org */\n role: OrgRole | null;\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: AuthError | null;\n /** Fetch/refresh organizations list */\n fetchOrgs: () => Promise<void>;\n /** Switch to a different organization */\n switchOrg: (orgId: string) => Promise<void>;\n /** Create a new organization */\n createOrg: (data: CreateOrgRequest) => Promise<Organization>;\n /** Update an organization */\n updateOrg: (orgId: string, data: UpdateOrgRequest) => Promise<Organization>;\n /** Delete an organization */\n deleteOrg: (orgId: string) => Promise<void>;\n /** Check if user has a specific permission */\n hasPermission: (permission: Permission) => boolean;\n}\n\nconst ACTIVE_ORG_KEY = 'cedros_active_org';\n\n// P-06: Safe localStorage helpers to handle private browsing and quota errors\nfunction safeGetItem(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction safeSetItem(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Ignore - private browsing or quota exceeded\n }\n}\n\n/**\n * Hook for managing organizations, memberships, and permissions.\n *\n * @example\n * ```tsx\n * function OrgSelector() {\n * const { orgs, activeOrg, switchOrg, hasPermission } = useOrgs();\n *\n * return (\n * <select\n * value={activeOrg?.id}\n * onChange={(e) => switchOrg(e.target.value)}\n * >\n * {orgs.map(org => (\n * <option key={org.id} value={org.id}>{org.name}</option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport function useOrgs(): UseOrgsReturn {\n const { config, user, authState, _internal } = useCedrosLogin();\n const hasStorage = typeof window !== 'undefined' && !!window.localStorage;\n\n const [orgs, setOrgs] = useState<OrgWithMembership[]>([]);\n const [activeOrg, setActiveOrg] = useState<OrgWithMembership | null>(null);\n const [permissions, setPermissions] = useState<Permission[]>([]);\n const [role, setRole] = useState<OrgRole | null>(null);\n const [isLoading, setIsLoading] = useState(authState === 'authenticated');\n const [error, setError] = useState<AuthError | null>(null);\n\n // M-03: Memoize API client and use ref to prevent callback dependency cascades\n const apiClient = useMemo(\n () =>\n new OrgApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // M-03: Store apiClient in ref to stabilize callback dependencies\n const apiClientRef = useRef(apiClient);\n useEffect(() => {\n apiClientRef.current = apiClient;\n }, [apiClient]);\n\n // M-03: Use ref in callback to break dependency chain\n const loadPermissions = useCallback(async (orgId: string) => {\n try {\n const response = await apiClientRef.current.getPermissions(orgId);\n setPermissions(response.permissions);\n setRole(response.role);\n } catch {\n // Permissions loading failure is non-fatal, just clear them\n setPermissions([]);\n setRole(null);\n }\n }, []);\n\n // Ref to latest fetchOrgs for stable auto-fetch effect\n const fetchOrgsRef = useRef<() => Promise<void>>(async () => {});\n\n // M-03: Use ref to break apiClient dependency chain\n const fetchOrgs = useCallback(async () => {\n if (authState !== 'authenticated' || !user) {\n setOrgs([]);\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const fetchedOrgs = await apiClientRef.current.listOrgs();\n setOrgs(fetchedOrgs);\n\n // P-06: Restore active org from localStorage with safe access\n const savedOrgId = hasStorage ? safeGetItem(ACTIVE_ORG_KEY) : null;\n let selectedOrg = fetchedOrgs.find((org) => org.id === savedOrgId);\n\n if (!selectedOrg && fetchedOrgs.length > 0) {\n // Default to personal org or first org\n selectedOrg = fetchedOrgs.find((org) => org.isPersonal) || fetchedOrgs[0];\n }\n\n if (selectedOrg) {\n setActiveOrg(selectedOrg);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, selectedOrg.id);\n }\n await loadPermissions(selectedOrg.id);\n } else {\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n }\n } catch (err) {\n setError(err as AuthError);\n } finally {\n setIsLoading(false);\n }\n }, [authState, user, loadPermissions, hasStorage]);\n\n // Keep fetchOrgsRef current so auto-fetch always calls the latest version\n useEffect(() => {\n fetchOrgsRef.current = fetchOrgs;\n }, [fetchOrgs]);\n\n // Auto-fetch orgs when auth becomes ready.\n // Uses only `authState` (a string) as dep — immune to object-identity churn\n // from the AdminShell bridge recreating `user` objects.\n const hasAutoFetched = useRef(false);\n useEffect(() => {\n if (authState === 'authenticated' && !hasAutoFetched.current) {\n hasAutoFetched.current = true;\n fetchOrgsRef.current();\n } else if (authState !== 'authenticated') {\n hasAutoFetched.current = false;\n }\n }, [authState]);\n\n const switchOrg = useCallback(\n async (orgId: string) => {\n const org = orgs.find((o) => o.id === orgId);\n if (!org) {\n setError({ code: 'UNKNOWN_ERROR', message: 'Organization not found' });\n return;\n }\n\n setActiveOrg(org);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, orgId);\n }\n await loadPermissions(orgId);\n },\n [orgs, loadPermissions, hasStorage]\n );\n\n // M-03: Use ref to break apiClient dependency chain\n const createOrg = useCallback(\n async (data: CreateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const newOrg = await apiClientRef.current.createOrg(data);\n // Refresh orgs list to include the new org with membership\n await fetchOrgs();\n return newOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const updateOrg = useCallback(\n async (orgId: string, data: UpdateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const updatedOrg = await apiClientRef.current.updateOrg(orgId, data);\n // Refresh orgs list to reflect changes\n await fetchOrgs();\n return updatedOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const deleteOrg = useCallback(\n async (orgId: string): Promise<void> => {\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.deleteOrg(orgId);\n // Refresh orgs list\n await fetchOrgs();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const hasPermission = useCallback(\n (permission: Permission): boolean => {\n return permissions.includes(permission);\n },\n [permissions]\n );\n\n return {\n orgs,\n activeOrg,\n permissions,\n role,\n isLoading,\n error,\n fetchOrgs,\n switchOrg,\n createOrg,\n updateOrg,\n deleteOrg,\n hasPermission,\n };\n}\n"],"names":["OrgApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","org","error","handleApiError","orgId","data","ACTIVE_ORG_KEY","safeGetItem","key","safeSetItem","value","useOrgs","config","user","authState","_internal","useCedrosLogin","hasStorage","orgs","setOrgs","useState","activeOrg","setActiveOrg","permissions","setPermissions","role","setRole","isLoading","setIsLoading","setError","apiClient","useMemo","apiClientRef","useRef","useEffect","loadPermissions","useCallback","response","fetchOrgsRef","fetchOrgs","fetchedOrgs","savedOrgId","selectedOrg","err","hasAutoFetched","switchOrg","o","createOrg","newOrg","updateOrg","updatedOrg","deleteOrg","hasPermission","permission"],"mappings":";;AAeO,MAAMA,EAAa;AAAA,EAChB;AAAA,EAER,YACEC,GACAC,GACAC,GACAC,GACA;AACA,SAAK,SAAS,IAAIC,EAAU,EAAE,SAAAJ,GAAS,WAAAC,GAAW,eAAAC,GAAe,gBAAAC,GAAgB;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAyC;AAC7C,QAAI;AAEF,cADiB,MAAM,KAAK,OAAO,IAAsB,OAAO,GAChD,KAAK,IAAI,CAACE,OAAS;AAAA,QACjC,GAAGA;AAAA,QACH,YAAY;AAAA,UACV,OAAOA,EAAI;AAAA,UACX,MAAMA,EAAI;AAAA,QAAA;AAAA,MACZ,EACA;AAAA,IACJ,SAASC,GAAO;AACd,YAAMC,EAAeD,GAAO,8BAA8B;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAOE,GAAsC;AACjD,QAAI;AACF,aAAO,MAAM,KAAK,OAAO,IAAkB,SAASA,CAAK,EAAE;AAAA,IAC7D,SAASF,GAAO;AACd,YAAMC,EAAeD,GAAO,4BAA4B;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAUG,GAA+C;AAC7D,QAAI;AACF,aAAO,MAAM,KAAK,OAAO,KAAmB,SAASA,CAAI;AAAA,IAC3D,SAASH,GAAO;AACd,YAAMC,EAAeD,GAAO,+BAA+B;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAUE,GAAeC,GAA+C;AAC5E,QAAI;AACF,aAAO,MAAM,KAAK,OAAO,MAAoB,SAASD,CAAK,IAAIC,CAAI;AAAA,IACrE,SAASH,GAAO;AACd,YAAMC,EAAeD,GAAO,+BAA+B;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAUE,GAA8B;AAC5C,QAAI;AACF,YAAM,KAAK,OAAO,OAAa,SAASA,CAAK,EAAE;AAAA,IACjD,SAASF,GAAO;AACd,YAAMC,EAAeD,GAAO,+BAA+B;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAUG,GAAoD;AAClE,QAAI;AACF,aAAO,MAAM,KAAK,OAAO,KAAwB,cAAcA,CAAI;AAAA,IACrE,SAASH,GAAO;AACd,YAAMC,EAAeD,GAAO,+BAA+B;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAeE,GAA6C;AAChE,QAAI;AACF,aAAO,MAAM,KAAK,OAAO,KAA0B,gBAAgB,EAAE,OAAAA,GAAO;AAAA,IAC9E,SAASF,GAAO;AACd,YAAMC,EAAeD,GAAO,2BAA2B;AAAA,IACzD;AAAA,EACF;AACF;ACtEA,MAAMI,IAAiB;AAGvB,SAASC,EAAYC,GAA4B;AAC/C,MAAI;AACF,WAAO,aAAa,QAAQA,CAAG;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASC,EAAYD,GAAaE,GAAqB;AACrD,MAAI;AACF,iBAAa,QAAQF,GAAKE,CAAK;AAAA,EACjC,QAAQ;AAAA,EAER;AACF;AAuBO,SAASC,IAAyB;AACvC,QAAM,EAAE,QAAAC,GAAQ,MAAAC,GAAM,WAAAC,GAAW,WAAAC,EAAA,IAAcC,EAAA,GACzCC,IAAa,OAAO,SAAW,OAAe,CAAC,CAAC,OAAO,cAEvD,CAACC,GAAMC,CAAO,IAAIC,EAA8B,CAAA,CAAE,GAClD,CAACC,GAAWC,CAAY,IAAIF,EAAmC,IAAI,GACnE,CAACG,GAAaC,CAAc,IAAIJ,EAAuB,CAAA,CAAE,GACzD,CAACK,GAAMC,CAAO,IAAIN,EAAyB,IAAI,GAC/C,CAACO,GAAWC,CAAY,IAAIR,EAASN,MAAc,eAAe,GAClE,CAACZ,GAAO2B,CAAQ,IAAIT,EAA2B,IAAI,GAGnDU,IAAYC;AAAA,IAChB,MACE,IAAIpC;AAAA,MACFiB,EAAO;AAAA,MACPA,EAAO;AAAA,MACPA,EAAO;AAAA,MACPG,GAAW;AAAA,IAAA;AAAA,IAEf,CAACH,EAAO,WAAWA,EAAO,gBAAgBA,EAAO,eAAeG,CAAS;AAAA,EAAA,GAIrEiB,IAAeC,EAAOH,CAAS;AACrC,EAAAI,EAAU,MAAM;AACd,IAAAF,EAAa,UAAUF;AAAA,EACzB,GAAG,CAACA,CAAS,CAAC;AAGd,QAAMK,IAAkBC,EAAY,OAAOhC,MAAkB;AAC3D,QAAI;AACF,YAAMiC,IAAW,MAAML,EAAa,QAAQ,eAAe5B,CAAK;AAChE,MAAAoB,EAAea,EAAS,WAAW,GACnCX,EAAQW,EAAS,IAAI;AAAA,IACvB,QAAQ;AAEN,MAAAb,EAAe,CAAA,CAAE,GACjBE,EAAQ,IAAI;AAAA,IACd;AAAA,EACF,GAAG,CAAA,CAAE,GAGCY,IAAeL,EAA4B,YAAY;AAAA,EAAC,CAAC,GAGzDM,IAAYH,EAAY,YAAY;AACxC,QAAItB,MAAc,mBAAmB,CAACD,GAAM;AAC1C,MAAAM,EAAQ,CAAA,CAAE,GACVG,EAAa,IAAI,GACjBE,EAAe,CAAA,CAAE,GACjBE,EAAQ,IAAI;AACZ;AAAA,IACF;AAEA,IAAAE,EAAa,EAAI,GACjBC,EAAS,IAAI;AAEb,QAAI;AACF,YAAMW,IAAc,MAAMR,EAAa,QAAQ,SAAA;AAC/C,MAAAb,EAAQqB,CAAW;AAGnB,YAAMC,IAAaxB,IAAaV,EAAYD,CAAc,IAAI;AAC9D,UAAIoC,IAAcF,EAAY,KAAK,CAACvC,MAAQA,EAAI,OAAOwC,CAAU;AAEjE,MAAI,CAACC,KAAeF,EAAY,SAAS,MAEvCE,IAAcF,EAAY,KAAK,CAACvC,MAAQA,EAAI,UAAU,KAAKuC,EAAY,CAAC,IAGtEE,KACFpB,EAAaoB,CAAW,GACpBzB,KACFR,EAAYH,GAAgBoC,EAAY,EAAE,GAE5C,MAAMP,EAAgBO,EAAY,EAAE,MAEpCpB,EAAa,IAAI,GACjBE,EAAe,CAAA,CAAE,GACjBE,EAAQ,IAAI;AAAA,IAEhB,SAASiB,GAAK;AACZ,MAAAd,EAASc,CAAgB;AAAA,IAC3B,UAAA;AACE,MAAAf,EAAa,EAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAACd,GAAWD,GAAMsB,GAAiBlB,CAAU,CAAC;AAGjD,EAAAiB,EAAU,MAAM;AACd,IAAAI,EAAa,UAAUC;AAAA,EACzB,GAAG,CAACA,CAAS,CAAC;AAKd,QAAMK,IAAiBX,EAAO,EAAK;AACnC,EAAAC,EAAU,MAAM;AACd,IAAIpB,MAAc,mBAAmB,CAAC8B,EAAe,WACnDA,EAAe,UAAU,IACzBN,EAAa,QAAA,KACJxB,MAAc,oBACvB8B,EAAe,UAAU;AAAA,EAE7B,GAAG,CAAC9B,CAAS,CAAC;AAEd,QAAM+B,IAAYT;AAAA,IAChB,OAAOhC,MAAkB;AACvB,YAAMH,IAAMiB,EAAK,KAAK,CAAC4B,MAAMA,EAAE,OAAO1C,CAAK;AAC3C,UAAI,CAACH,GAAK;AACR,QAAA4B,EAAS,EAAE,MAAM,iBAAiB,SAAS,0BAA0B;AACrE;AAAA,MACF;AAEA,MAAAP,EAAarB,CAAG,GACZgB,KACFR,EAAYH,GAAgBF,CAAK,GAEnC,MAAM+B,EAAgB/B,CAAK;AAAA,IAC7B;AAAA,IACA,CAACc,GAAMiB,GAAiBlB,CAAU;AAAA,EAAA,GAI9B8B,IAAYX;AAAA,IAChB,OAAO/B,MAAkD;AACvD,MAAAuB,EAAa,EAAI,GACjBC,EAAS,IAAI;AAEb,UAAI;AACF,cAAMmB,IAAS,MAAMhB,EAAa,QAAQ,UAAU3B,CAAI;AAExD,qBAAMkC,EAAA,GACCS;AAAA,MACT,SAASL,GAAK;AACZ,cAAAd,EAASc,CAAgB,GACnBA;AAAA,MACR,UAAA;AACE,QAAAf,EAAa,EAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAACW,CAAS;AAAA,EAAA,GAGNU,IAAYb;AAAA,IAChB,OAAOhC,GAAeC,MAAkD;AACtE,MAAAuB,EAAa,EAAI,GACjBC,EAAS,IAAI;AAEb,UAAI;AACF,cAAMqB,IAAa,MAAMlB,EAAa,QAAQ,UAAU5B,GAAOC,CAAI;AAEnE,qBAAMkC,EAAA,GACCW;AAAA,MACT,SAASP,GAAK;AACZ,cAAAd,EAASc,CAAgB,GACnBA;AAAA,MACR,UAAA;AACE,QAAAf,EAAa,EAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAACW,CAAS;AAAA,EAAA,GAGNY,IAAYf;AAAA,IAChB,OAAOhC,MAAiC;AACtC,MAAAwB,EAAa,EAAI,GACjBC,EAAS,IAAI;AAEb,UAAI;AACF,cAAMG,EAAa,QAAQ,UAAU5B,CAAK,GAE1C,MAAMmC,EAAA;AAAA,MACR,SAASI,GAAK;AACZ,cAAAd,EAASc,CAAgB,GACnBA;AAAA,MACR,UAAA;AACE,QAAAf,EAAa,EAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAACW,CAAS;AAAA,EAAA,GAGNa,IAAgBhB;AAAA,IACpB,CAACiB,MACQ9B,EAAY,SAAS8B,CAAU;AAAA,IAExC,CAAC9B,CAAW;AAAA,EAAA;AAGd,SAAO;AAAA,IACL,MAAAL;AAAA,IACA,WAAAG;AAAA,IACA,aAAAE;AAAA,IACA,MAAAE;AAAA,IACA,WAAAE;AAAA,IACA,OAAAzB;AAAA,IACA,WAAAqC;AAAA,IACA,WAAAM;AAAA,IACA,WAAAE;AAAA,IACA,WAAAE;AAAA,IACA,WAAAE;AAAA,IACA,eAAAC;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const s=require("react"),l=require("./useCedrosLogin-C9MrcZvh.cjs");class b{client;constructor(e,t,h,g){this.client=new l.ApiClient({baseUrl:e,timeoutMs:t,retryAttempts:h,getAccessToken:g})}async listOrgs(){try{return(await this.client.get("/orgs")).orgs.map(t=>({...t,membership:{orgId:t.id,role:t.role}}))}catch(e){throw l.handleApiError(e,"Failed to list organizations")}}async getOrg(e){try{return await this.client.get(`/orgs/${e}`)}catch(t){throw l.handleApiError(t,"Failed to get organization")}}async createOrg(e){try{return await this.client.post("/orgs",e)}catch(t){throw l.handleApiError(t,"Failed to create organization")}}async updateOrg(e,t){try{return await this.client.patch(`/orgs/${e}`,t)}catch(h){throw l.handleApiError(h,"Failed to update organization")}}async deleteOrg(e){try{await this.client.delete(`/orgs/${e}`)}catch(t){throw l.handleApiError(t,"Failed to delete organization")}}async authorize(e){try{return await this.client.post("/authorize",e)}catch(t){throw l.handleApiError(t,"Failed to check authorization")}}async getPermissions(e){try{return await this.client.post("/permissions",{orgId:e})}catch(t){throw l.handleApiError(t,"Failed to get permissions")}}}const S="cedros_active_org";function U(i){try{return localStorage.getItem(i)}catch{return null}}function z(i,e){try{localStorage.setItem(i,e)}catch{}}function $(){const{config:i,user:e,authState:t,_internal:h}=l.useCedrosLogin(),g=typeof window<"u"&&!!window.localStorage,[O,R]=s.useState([]),[F,f]=s.useState(null),[m,w]=s.useState([]),[v,y]=s.useState(null),[I,u]=s.useState(t==="authenticated"),[P,c]=s.useState(null),A=s.useMemo(()=>new b(i.serverUrl,i.requestTimeout,i.retryAttempts,h?.getAccessToken),[i.serverUrl,i.requestTimeout,i.retryAttempts,h]),d=s.useRef(A);s.useEffect(()=>{d.current=A},[A]);const p=s.useCallback(async r=>{try{const a=await d.current.getPermissions(r);w(a.permissions),y(a.role)}catch{w([]),y(null)}},[]),k=s.useRef(async()=>{}),o=s.useCallback(async()=>{if(t!=="authenticated"||!e){R([]),f(null),w([]),y(null);return}u(!0),c(null);try{const r=await d.current.listOrgs();R(r);const a=g?U(S):null;let n=r.find(E=>E.id===a);!n&&r.length>0&&(n=r.find(E=>E.isPersonal)||r[0]),n?(f(n),g&&z(S,n.id),await p(n.id)):(f(null),w([]),y(null))}catch(r){c(r)}finally{u(!1)}},[t,e,p,g]);s.useEffect(()=>{k.current=o},[o]);const C=s.useRef(!1);s.useEffect(()=>{t==="authenticated"&&!C.current?(C.current=!0,k.current()):t!=="authenticated"&&(C.current=!1)},[t]);const _=s.useCallback(async r=>{const a=O.find(n=>n.id===r);if(!a){c({code:"UNKNOWN_ERROR",message:"Organization not found"});return}f(a),g&&z(S,r),await p(r)},[O,p,g]),q=s.useCallback(async r=>{u(!0),c(null);try{const a=await d.current.createOrg(r);return await o(),a}catch(a){throw c(a),a}finally{u(!1)}},[o]),L=s.useCallback(async(r,a)=>{u(!0),c(null);try{const n=await d.current.updateOrg(r,a);return await o(),n}catch(n){throw c(n),n}finally{u(!1)}},[o]),T=s.useCallback(async r=>{u(!0),c(null);try{await d.current.deleteOrg(r),await o()}catch(a){throw c(a),a}finally{u(!1)}},[o]),N=s.useCallback(r=>m.includes(r),[m]);return{orgs:O,activeOrg:F,permissions:m,role:v,isLoading:I,error:P,fetchOrgs:o,switchOrg:_,createOrg:q,updateOrg:L,deleteOrg:T,hasPermission:N}}exports.OrgApiClient=b;exports.useOrgs=$;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOrgs-CVbacmaQ.cjs","sources":["../src/utils/orgApi.ts","../src/hooks/useOrgs.ts"],"sourcesContent":["import type {\n Organization,\n OrgWithMembership,\n CreateOrgRequest,\n UpdateOrgRequest,\n ListOrgsResponse,\n AuthorizeRequest,\n AuthorizeResponse,\n PermissionsResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for organization operations\n */\nexport class OrgApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * List all organizations the current user belongs to\n */\n async listOrgs(): Promise<OrgWithMembership[]> {\n try {\n const response = await this.client.get<ListOrgsResponse>('/orgs');\n return response.orgs.map((org) => ({\n ...org,\n membership: {\n orgId: org.id,\n role: org.role,\n },\n }));\n } catch (error) {\n throw handleApiError(error, 'Failed to list organizations');\n }\n }\n\n /**\n * Get a single organization by ID\n */\n async getOrg(orgId: string): Promise<Organization> {\n try {\n return await this.client.get<Organization>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to get organization');\n }\n }\n\n /**\n * Create a new organization\n */\n async createOrg(data: CreateOrgRequest): Promise<Organization> {\n try {\n return await this.client.post<Organization>('/orgs', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to create organization');\n }\n }\n\n /**\n * Update an organization\n */\n async updateOrg(orgId: string, data: UpdateOrgRequest): Promise<Organization> {\n try {\n return await this.client.patch<Organization>(`/orgs/${orgId}`, data);\n } catch (error) {\n throw handleApiError(error, 'Failed to update organization');\n }\n }\n\n /**\n * Delete an organization\n */\n async deleteOrg(orgId: string): Promise<void> {\n try {\n await this.client.delete<void>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to delete organization');\n }\n }\n\n /**\n * Check authorization for an action\n */\n async authorize(data: AuthorizeRequest): Promise<AuthorizeResponse> {\n try {\n return await this.client.post<AuthorizeResponse>('/authorize', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to check authorization');\n }\n }\n\n /**\n * Get current user's permissions in an organization\n */\n async getPermissions(orgId: string): Promise<PermissionsResponse> {\n try {\n return await this.client.post<PermissionsResponse>('/permissions', { orgId });\n } catch (error) {\n throw handleApiError(error, 'Failed to get permissions');\n }\n }\n}\n","import { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport type {\n OrgWithMembership,\n Organization,\n CreateOrgRequest,\n UpdateOrgRequest,\n Permission,\n OrgRole,\n AuthError,\n} from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { OrgApiClient } from '../utils/orgApi';\n\nexport interface UseOrgsReturn {\n /** All organizations the user belongs to */\n orgs: OrgWithMembership[];\n /** Currently active organization */\n activeOrg: OrgWithMembership | null;\n /** User's permissions in the active org */\n permissions: Permission[];\n /** User's role in the active org */\n role: OrgRole | null;\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: AuthError | null;\n /** Fetch/refresh organizations list */\n fetchOrgs: () => Promise<void>;\n /** Switch to a different organization */\n switchOrg: (orgId: string) => Promise<void>;\n /** Create a new organization */\n createOrg: (data: CreateOrgRequest) => Promise<Organization>;\n /** Update an organization */\n updateOrg: (orgId: string, data: UpdateOrgRequest) => Promise<Organization>;\n /** Delete an organization */\n deleteOrg: (orgId: string) => Promise<void>;\n /** Check if user has a specific permission */\n hasPermission: (permission: Permission) => boolean;\n}\n\nconst ACTIVE_ORG_KEY = 'cedros_active_org';\n\n// P-06: Safe localStorage helpers to handle private browsing and quota errors\nfunction safeGetItem(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction safeSetItem(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Ignore - private browsing or quota exceeded\n }\n}\n\n/**\n * Hook for managing organizations, memberships, and permissions.\n *\n * @example\n * ```tsx\n * function OrgSelector() {\n * const { orgs, activeOrg, switchOrg, hasPermission } = useOrgs();\n *\n * return (\n * <select\n * value={activeOrg?.id}\n * onChange={(e) => switchOrg(e.target.value)}\n * >\n * {orgs.map(org => (\n * <option key={org.id} value={org.id}>{org.name}</option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport function useOrgs(): UseOrgsReturn {\n const { config, user, authState, _internal } = useCedrosLogin();\n const hasStorage = typeof window !== 'undefined' && !!window.localStorage;\n\n const [orgs, setOrgs] = useState<OrgWithMembership[]>([]);\n const [activeOrg, setActiveOrg] = useState<OrgWithMembership | null>(null);\n const [permissions, setPermissions] = useState<Permission[]>([]);\n const [role, setRole] = useState<OrgRole | null>(null);\n const [isLoading, setIsLoading] = useState(authState === 'authenticated');\n const [error, setError] = useState<AuthError | null>(null);\n\n // M-03: Memoize API client and use ref to prevent callback dependency cascades\n const apiClient = useMemo(\n () =>\n new OrgApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // M-03: Store apiClient in ref to stabilize callback dependencies\n const apiClientRef = useRef(apiClient);\n useEffect(() => {\n apiClientRef.current = apiClient;\n }, [apiClient]);\n\n // M-03: Use ref in callback to break dependency chain\n const loadPermissions = useCallback(async (orgId: string) => {\n try {\n const response = await apiClientRef.current.getPermissions(orgId);\n setPermissions(response.permissions);\n setRole(response.role);\n } catch {\n // Permissions loading failure is non-fatal, just clear them\n setPermissions([]);\n setRole(null);\n }\n }, []);\n\n // Ref to latest fetchOrgs for stable auto-fetch effect\n const fetchOrgsRef = useRef<() => Promise<void>>(async () => {});\n\n // M-03: Use ref to break apiClient dependency chain\n const fetchOrgs = useCallback(async () => {\n if (authState !== 'authenticated' || !user) {\n setOrgs([]);\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const fetchedOrgs = await apiClientRef.current.listOrgs();\n setOrgs(fetchedOrgs);\n\n // P-06: Restore active org from localStorage with safe access\n const savedOrgId = hasStorage ? safeGetItem(ACTIVE_ORG_KEY) : null;\n let selectedOrg = fetchedOrgs.find((org) => org.id === savedOrgId);\n\n if (!selectedOrg && fetchedOrgs.length > 0) {\n // Default to personal org or first org\n selectedOrg = fetchedOrgs.find((org) => org.isPersonal) || fetchedOrgs[0];\n }\n\n if (selectedOrg) {\n setActiveOrg(selectedOrg);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, selectedOrg.id);\n }\n await loadPermissions(selectedOrg.id);\n } else {\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n }\n } catch (err) {\n setError(err as AuthError);\n } finally {\n setIsLoading(false);\n }\n }, [authState, user, loadPermissions, hasStorage]);\n\n // Keep fetchOrgsRef current so auto-fetch always calls the latest version\n useEffect(() => {\n fetchOrgsRef.current = fetchOrgs;\n }, [fetchOrgs]);\n\n // Auto-fetch orgs when auth becomes ready.\n // Uses only `authState` (a string) as dep — immune to object-identity churn\n // from the AdminShell bridge recreating `user` objects.\n const hasAutoFetched = useRef(false);\n useEffect(() => {\n if (authState === 'authenticated' && !hasAutoFetched.current) {\n hasAutoFetched.current = true;\n fetchOrgsRef.current();\n } else if (authState !== 'authenticated') {\n hasAutoFetched.current = false;\n }\n }, [authState]);\n\n const switchOrg = useCallback(\n async (orgId: string) => {\n const org = orgs.find((o) => o.id === orgId);\n if (!org) {\n setError({ code: 'UNKNOWN_ERROR', message: 'Organization not found' });\n return;\n }\n\n setActiveOrg(org);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, orgId);\n }\n await loadPermissions(orgId);\n },\n [orgs, loadPermissions, hasStorage]\n );\n\n // M-03: Use ref to break apiClient dependency chain\n const createOrg = useCallback(\n async (data: CreateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const newOrg = await apiClientRef.current.createOrg(data);\n // Refresh orgs list to include the new org with membership\n await fetchOrgs();\n return newOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const updateOrg = useCallback(\n async (orgId: string, data: UpdateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const updatedOrg = await apiClientRef.current.updateOrg(orgId, data);\n // Refresh orgs list to reflect changes\n await fetchOrgs();\n return updatedOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const deleteOrg = useCallback(\n async (orgId: string): Promise<void> => {\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.deleteOrg(orgId);\n // Refresh orgs list\n await fetchOrgs();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const hasPermission = useCallback(\n (permission: Permission): boolean => {\n return permissions.includes(permission);\n },\n [permissions]\n );\n\n return {\n orgs,\n activeOrg,\n permissions,\n role,\n isLoading,\n error,\n fetchOrgs,\n switchOrg,\n createOrg,\n updateOrg,\n deleteOrg,\n hasPermission,\n };\n}\n"],"names":["OrgApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","org","error","handleApiError","orgId","data","ACTIVE_ORG_KEY","safeGetItem","key","safeSetItem","value","useOrgs","config","user","authState","_internal","useCedrosLogin","hasStorage","orgs","setOrgs","useState","activeOrg","setActiveOrg","permissions","setPermissions","role","setRole","isLoading","setIsLoading","setError","apiClient","useMemo","apiClientRef","useRef","useEffect","loadPermissions","useCallback","response","fetchOrgsRef","fetchOrgs","fetchedOrgs","savedOrgId","selectedOrg","err","hasAutoFetched","switchOrg","o","createOrg","newOrg","updateOrg","updatedOrg","deleteOrg","hasPermission","permission"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useOrgs-DDVRCaVi.cjs","sources":["../src/utils/orgApi.ts","../src/hooks/useOrgs.ts"],"sourcesContent":["import type {\n Organization,\n OrgWithMembership,\n CreateOrgRequest,\n UpdateOrgRequest,\n ListOrgsResponse,\n AuthorizeRequest,\n AuthorizeResponse,\n PermissionsResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for organization operations\n */\nexport class OrgApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * List all organizations the current user belongs to\n */\n async listOrgs(): Promise<OrgWithMembership[]> {\n try {\n const response = await this.client.get<ListOrgsResponse>('/orgs');\n return response.orgs.map((org) => ({\n ...org,\n membership: {\n orgId: org.id,\n role: org.role,\n },\n }));\n } catch (error) {\n throw handleApiError(error, 'Failed to list organizations');\n }\n }\n\n /**\n * Get a single organization by ID\n */\n async getOrg(orgId: string): Promise<Organization> {\n try {\n return await this.client.get<Organization>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to get organization');\n }\n }\n\n /**\n * Create a new organization\n */\n async createOrg(data: CreateOrgRequest): Promise<Organization> {\n try {\n return await this.client.post<Organization>('/orgs', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to create organization');\n }\n }\n\n /**\n * Update an organization\n */\n async updateOrg(orgId: string, data: UpdateOrgRequest): Promise<Organization> {\n try {\n return await this.client.patch<Organization>(`/orgs/${orgId}`, data);\n } catch (error) {\n throw handleApiError(error, 'Failed to update organization');\n }\n }\n\n /**\n * Delete an organization\n */\n async deleteOrg(orgId: string): Promise<void> {\n try {\n await this.client.delete<void>(`/orgs/${orgId}`);\n } catch (error) {\n throw handleApiError(error, 'Failed to delete organization');\n }\n }\n\n /**\n * Check authorization for an action\n */\n async authorize(data: AuthorizeRequest): Promise<AuthorizeResponse> {\n try {\n return await this.client.post<AuthorizeResponse>('/authorize', data);\n } catch (error) {\n throw handleApiError(error, 'Failed to check authorization');\n }\n }\n\n /**\n * Get current user's permissions in an organization\n */\n async getPermissions(orgId: string): Promise<PermissionsResponse> {\n try {\n return await this.client.post<PermissionsResponse>('/permissions', { orgId });\n } catch (error) {\n throw handleApiError(error, 'Failed to get permissions');\n }\n }\n}\n","import { useState, useCallback, useMemo, useRef, useEffect } from 'react';\nimport type {\n OrgWithMembership,\n Organization,\n CreateOrgRequest,\n UpdateOrgRequest,\n Permission,\n OrgRole,\n AuthError,\n} from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { OrgApiClient } from '../utils/orgApi';\n\nexport interface UseOrgsReturn {\n /** All organizations the user belongs to */\n orgs: OrgWithMembership[];\n /** Currently active organization */\n activeOrg: OrgWithMembership | null;\n /** User's permissions in the active org */\n permissions: Permission[];\n /** User's role in the active org */\n role: OrgRole | null;\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: AuthError | null;\n /** Fetch/refresh organizations list */\n fetchOrgs: () => Promise<void>;\n /** Switch to a different organization */\n switchOrg: (orgId: string) => Promise<void>;\n /** Create a new organization */\n createOrg: (data: CreateOrgRequest) => Promise<Organization>;\n /** Update an organization */\n updateOrg: (orgId: string, data: UpdateOrgRequest) => Promise<Organization>;\n /** Delete an organization */\n deleteOrg: (orgId: string) => Promise<void>;\n /** Check if user has a specific permission */\n hasPermission: (permission: Permission) => boolean;\n}\n\nconst ACTIVE_ORG_KEY = 'cedros_active_org';\n\n// P-06: Safe localStorage helpers to handle private browsing and quota errors\nfunction safeGetItem(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction safeSetItem(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // Ignore - private browsing or quota exceeded\n }\n}\n\n/**\n * Hook for managing organizations, memberships, and permissions.\n *\n * @example\n * ```tsx\n * function OrgSelector() {\n * const { orgs, activeOrg, switchOrg, hasPermission } = useOrgs();\n *\n * return (\n * <select\n * value={activeOrg?.id}\n * onChange={(e) => switchOrg(e.target.value)}\n * >\n * {orgs.map(org => (\n * <option key={org.id} value={org.id}>{org.name}</option>\n * ))}\n * </select>\n * );\n * }\n * ```\n */\nexport function useOrgs(): UseOrgsReturn {\n const { config, user, authState, _internal } = useCedrosLogin();\n const hasStorage = typeof window !== 'undefined' && !!window.localStorage;\n\n const [orgs, setOrgs] = useState<OrgWithMembership[]>([]);\n const [activeOrg, setActiveOrg] = useState<OrgWithMembership | null>(null);\n const [permissions, setPermissions] = useState<Permission[]>([]);\n const [role, setRole] = useState<OrgRole | null>(null);\n const [isLoading, setIsLoading] = useState(authState === 'authenticated');\n const [error, setError] = useState<AuthError | null>(null);\n\n // M-03: Memoize API client and use ref to prevent callback dependency cascades\n const apiClient = useMemo(\n () =>\n new OrgApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // M-03: Store apiClient in ref to stabilize callback dependencies\n const apiClientRef = useRef(apiClient);\n useEffect(() => {\n apiClientRef.current = apiClient;\n }, [apiClient]);\n\n // M-03: Use ref in callback to break dependency chain\n const loadPermissions = useCallback(async (orgId: string) => {\n try {\n const response = await apiClientRef.current.getPermissions(orgId);\n setPermissions(response.permissions);\n setRole(response.role);\n } catch {\n // Permissions loading failure is non-fatal, just clear them\n setPermissions([]);\n setRole(null);\n }\n }, []);\n\n // Ref to latest fetchOrgs for stable auto-fetch effect\n const fetchOrgsRef = useRef<() => Promise<void>>(async () => {});\n\n // M-03: Use ref to break apiClient dependency chain\n const fetchOrgs = useCallback(async () => {\n if (authState !== 'authenticated' || !user) {\n setOrgs([]);\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const fetchedOrgs = await apiClientRef.current.listOrgs();\n setOrgs(fetchedOrgs);\n\n // P-06: Restore active org from localStorage with safe access\n const savedOrgId = hasStorage ? safeGetItem(ACTIVE_ORG_KEY) : null;\n let selectedOrg = fetchedOrgs.find((org) => org.id === savedOrgId);\n\n if (!selectedOrg && fetchedOrgs.length > 0) {\n // Default to personal org or first org\n selectedOrg = fetchedOrgs.find((org) => org.isPersonal) || fetchedOrgs[0];\n }\n\n if (selectedOrg) {\n setActiveOrg(selectedOrg);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, selectedOrg.id);\n }\n await loadPermissions(selectedOrg.id);\n } else {\n setActiveOrg(null);\n setPermissions([]);\n setRole(null);\n }\n } catch (err) {\n setError(err as AuthError);\n } finally {\n setIsLoading(false);\n }\n }, [authState, user, loadPermissions, hasStorage]);\n\n // Keep fetchOrgsRef current so auto-fetch always calls the latest version\n useEffect(() => {\n fetchOrgsRef.current = fetchOrgs;\n }, [fetchOrgs]);\n\n // Auto-fetch orgs when auth becomes ready.\n // Uses only `authState` (a string) as dep — immune to object-identity churn\n // from the AdminShell bridge recreating `user` objects.\n const hasAutoFetched = useRef(false);\n useEffect(() => {\n if (authState === 'authenticated' && !hasAutoFetched.current) {\n hasAutoFetched.current = true;\n fetchOrgsRef.current();\n } else if (authState !== 'authenticated') {\n hasAutoFetched.current = false;\n }\n }, [authState]);\n\n const switchOrg = useCallback(\n async (orgId: string) => {\n const org = orgs.find((o) => o.id === orgId);\n if (!org) {\n setError({ code: 'UNKNOWN_ERROR', message: 'Organization not found' });\n return;\n }\n\n setActiveOrg(org);\n if (hasStorage) {\n safeSetItem(ACTIVE_ORG_KEY, orgId);\n }\n await loadPermissions(orgId);\n },\n [orgs, loadPermissions, hasStorage]\n );\n\n // M-03: Use ref to break apiClient dependency chain\n const createOrg = useCallback(\n async (data: CreateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const newOrg = await apiClientRef.current.createOrg(data);\n // Refresh orgs list to include the new org with membership\n await fetchOrgs();\n return newOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const updateOrg = useCallback(\n async (orgId: string, data: UpdateOrgRequest): Promise<Organization> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const updatedOrg = await apiClientRef.current.updateOrg(orgId, data);\n // Refresh orgs list to reflect changes\n await fetchOrgs();\n return updatedOrg;\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const deleteOrg = useCallback(\n async (orgId: string): Promise<void> => {\n setIsLoading(true);\n setError(null);\n\n try {\n await apiClientRef.current.deleteOrg(orgId);\n // Refresh orgs list\n await fetchOrgs();\n } catch (err) {\n setError(err as AuthError);\n throw err;\n } finally {\n setIsLoading(false);\n }\n },\n [fetchOrgs]\n );\n\n const hasPermission = useCallback(\n (permission: Permission): boolean => {\n return permissions.includes(permission);\n },\n [permissions]\n );\n\n return {\n orgs,\n activeOrg,\n permissions,\n role,\n isLoading,\n error,\n fetchOrgs,\n switchOrg,\n createOrg,\n updateOrg,\n deleteOrg,\n hasPermission,\n };\n}\n"],"names":["OrgApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","org","error","handleApiError","orgId","data","ACTIVE_ORG_KEY","safeGetItem","key","safeSetItem","value","useOrgs","config","user","authState","_internal","useCedrosLogin","hasStorage","orgs","setOrgs","useState","activeOrg","setActiveOrg","permissions","setPermissions","role","setRole","isLoading","setIsLoading","setError","apiClient","useMemo","apiClientRef","useRef","useEffect","loadPermissions","useCallback","response","fetchOrgsRef","fetchOrgs","fetchedOrgs","savedOrgId","selectedOrg","err","hasAutoFetched","switchOrg","o","createOrg","newOrg","updateOrg","updatedOrg","deleteOrg","hasPermission","permission"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState as l, useRef as m, useMemo as F, useCallback as d } from "react";
|
|
2
|
-
import { A as I, h as w, u as U } from "./
|
|
2
|
+
import { A as I, h as w, u as U } from "./useCedrosLogin-_94MmGGq.js";
|
|
3
3
|
class k {
|
|
4
4
|
client;
|
|
5
5
|
constructor(t, s, i, o) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSystemSettings-
|
|
1
|
+
{"version":3,"file":"useSystemSettings-DBlAMjFi.js","sources":["../src/utils/systemSettingsApi.ts","../src/hooks/useSystemSettings.ts"],"sourcesContent":["import type {\n ListSystemSettingsResponse,\n UpdateSettingRequest,\n UpdateSystemSettingsResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for system settings operations (admin only)\n */\nexport class SystemSettingsApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * Get all system settings grouped by category\n * Requires system admin privileges\n */\n async getSettings(): Promise<ListSystemSettingsResponse> {\n try {\n return await this.client.get<ListSystemSettingsResponse>('/admin/settings');\n } catch (error) {\n throw handleApiError(error, 'Failed to fetch system settings');\n }\n }\n\n /**\n * Update one or more system settings\n * Requires system admin privileges\n */\n async updateSettings(settings: UpdateSettingRequest[]): Promise<UpdateSystemSettingsResponse> {\n try {\n return await this.client.patch<UpdateSystemSettingsResponse>('/admin/settings', {\n settings,\n });\n } catch (error) {\n throw handleApiError(error, 'Failed to update system settings');\n }\n }\n}\n","import { useState, useCallback, useMemo, useRef } from 'react';\nimport type { SystemSetting, UpdateSettingRequest, UseSystemSettingsReturn } from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { SystemSettingsApiClient } from '../utils/systemSettingsApi';\n\n/**\n * Hook for managing system settings (admin only).\n *\n * Provides CRUD operations for system-wide configuration settings\n * stored in the database. Only accessible to system administrators.\n *\n * @example\n * ```tsx\n * function SystemSettingsPanel() {\n * const {\n * settings,\n * isLoading,\n * error,\n * fetchSettings,\n * updateSettings,\n * getValue,\n * } = useSystemSettings();\n *\n * useEffect(() => {\n * fetchSettings();\n * }, [fetchSettings]);\n *\n * const handleSave = async () => {\n * await updateSettings([\n * { key: 'privacy_period_secs', value: '1209600' },\n * ]);\n * };\n *\n * return (\n * <div>\n * {Object.entries(settings).map(([category, items]) => (\n * <section key={category}>\n * <h3>{category}</h3>\n * {items.map(item => (\n * <div key={item.key}>\n * {item.key}: {item.value}\n * </div>\n * ))}\n * </section>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useSystemSettings(): UseSystemSettingsReturn {\n const { config, authState, _internal } = useCedrosLogin();\n\n const [settings, setSettings] = useState<Record<string, SystemSetting[]>>({});\n const [isLoading, setIsLoading] = useState(false);\n const [isUpdating, setIsUpdating] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const requestIdRef = useRef(0);\n\n const apiClient = useMemo(\n () =>\n new SystemSettingsApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // Use ref to avoid apiClient in callback dependencies\n const apiClientRef = useRef(apiClient);\n apiClientRef.current = apiClient;\n\n const fetchSettings = useCallback(async () => {\n if (authState !== 'authenticated') {\n setSettings({});\n return;\n }\n\n setIsLoading(true);\n setError(null);\n const requestId = ++requestIdRef.current;\n\n try {\n const response = await apiClientRef.current.getSettings();\n if (requestId !== requestIdRef.current) return;\n setSettings(response.settings);\n } catch (err) {\n if (requestId !== requestIdRef.current) return;\n setError(err instanceof Error ? err : new Error('Failed to fetch settings'));\n } finally {\n if (requestId === requestIdRef.current) {\n setIsLoading(false);\n }\n }\n }, [authState]);\n\n const updateSettings = useCallback(\n async (updates: UpdateSettingRequest[]): Promise<void> => {\n if (authState !== 'authenticated') {\n throw new Error('Not authenticated');\n }\n\n setIsUpdating(true);\n setError(null);\n\n try {\n await apiClientRef.current.updateSettings(updates);\n // Refresh settings after update\n await fetchSettings();\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update settings');\n setError(error);\n throw error;\n } finally {\n setIsUpdating(false);\n }\n },\n [authState, fetchSettings]\n );\n\n const getValue = useCallback(\n (key: string): string | undefined => {\n for (const categorySettings of Object.values(settings)) {\n const found = categorySettings.find((s) => s.key === key);\n if (found) return found.value;\n }\n return undefined;\n },\n [settings]\n );\n\n return {\n settings,\n isLoading,\n isUpdating,\n error,\n fetchSettings,\n updateSettings,\n getValue,\n };\n}\n"],"names":["SystemSettingsApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","error","handleApiError","settings","useSystemSettings","config","authState","_internal","useCedrosLogin","setSettings","useState","isLoading","setIsLoading","isUpdating","setIsUpdating","setError","requestIdRef","useRef","apiClient","useMemo","apiClientRef","fetchSettings","useCallback","requestId","response","err","updateSettings","updates","getValue","key","categorySettings","found","s"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("react"),g=require("./useCedrosLogin-C9MrcZvh.cjs");class q{client;constructor(t,i,a,o){this.client=new g.ApiClient({baseUrl:t,timeoutMs:i,retryAttempts:a,getAccessToken:o})}async getSettings(){try{return await this.client.get("/admin/settings")}catch(t){throw g.handleApiError(t,"Failed to fetch system settings")}}async updateSettings(t){try{return await this.client.patch("/admin/settings",{settings:t})}catch(i){throw g.handleApiError(i,"Failed to update system settings")}}}function k(){const{config:s,authState:t,_internal:i}=g.useCedrosLogin(),[a,o]=e.useState({}),[p,h]=e.useState(!1),[w,y]=e.useState(!1),[m,u]=e.useState(null),l=e.useRef(0),S=e.useMemo(()=>new q(s.serverUrl,s.requestTimeout,s.retryAttempts,i?.getAccessToken),[s.serverUrl,s.requestTimeout,s.retryAttempts,i]),d=e.useRef(S);d.current=S;const f=e.useCallback(async()=>{if(t!=="authenticated"){o({});return}h(!0),u(null);const n=++l.current;try{const r=await d.current.getSettings();if(n!==l.current)return;o(r.settings)}catch(r){if(n!==l.current)return;u(r instanceof Error?r:new Error("Failed to fetch settings"))}finally{n===l.current&&h(!1)}},[t]),C=e.useCallback(async n=>{if(t!=="authenticated")throw new Error("Not authenticated");y(!0),u(null);try{await d.current.updateSettings(n),await f()}catch(r){const c=r instanceof Error?r:new Error("Failed to update settings");throw u(c),c}finally{y(!1)}},[t,f]),E=e.useCallback(n=>{for(const r of Object.values(a)){const c=r.find(A=>A.key===n);if(c)return c.value}},[a]);return{settings:a,isLoading:p,isUpdating:w,error:m,fetchSettings:f,updateSettings:C,getValue:E}}exports.useSystemSettings=k;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSystemSettings-
|
|
1
|
+
{"version":3,"file":"useSystemSettings-DRrreszl.cjs","sources":["../src/utils/systemSettingsApi.ts","../src/hooks/useSystemSettings.ts"],"sourcesContent":["import type {\n ListSystemSettingsResponse,\n UpdateSettingRequest,\n UpdateSystemSettingsResponse,\n} from '../types';\nimport { ApiClient, handleApiError } from './apiClient';\n\n/**\n * API client for system settings operations (admin only)\n */\nexport class SystemSettingsApiClient {\n private client: ApiClient;\n\n constructor(\n baseUrl: string,\n timeoutMs?: number,\n retryAttempts?: number,\n getAccessToken?: () => string | null\n ) {\n this.client = new ApiClient({ baseUrl, timeoutMs, retryAttempts, getAccessToken });\n }\n\n /**\n * Get all system settings grouped by category\n * Requires system admin privileges\n */\n async getSettings(): Promise<ListSystemSettingsResponse> {\n try {\n return await this.client.get<ListSystemSettingsResponse>('/admin/settings');\n } catch (error) {\n throw handleApiError(error, 'Failed to fetch system settings');\n }\n }\n\n /**\n * Update one or more system settings\n * Requires system admin privileges\n */\n async updateSettings(settings: UpdateSettingRequest[]): Promise<UpdateSystemSettingsResponse> {\n try {\n return await this.client.patch<UpdateSystemSettingsResponse>('/admin/settings', {\n settings,\n });\n } catch (error) {\n throw handleApiError(error, 'Failed to update system settings');\n }\n }\n}\n","import { useState, useCallback, useMemo, useRef } from 'react';\nimport type { SystemSetting, UpdateSettingRequest, UseSystemSettingsReturn } from '../types';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { SystemSettingsApiClient } from '../utils/systemSettingsApi';\n\n/**\n * Hook for managing system settings (admin only).\n *\n * Provides CRUD operations for system-wide configuration settings\n * stored in the database. Only accessible to system administrators.\n *\n * @example\n * ```tsx\n * function SystemSettingsPanel() {\n * const {\n * settings,\n * isLoading,\n * error,\n * fetchSettings,\n * updateSettings,\n * getValue,\n * } = useSystemSettings();\n *\n * useEffect(() => {\n * fetchSettings();\n * }, [fetchSettings]);\n *\n * const handleSave = async () => {\n * await updateSettings([\n * { key: 'privacy_period_secs', value: '1209600' },\n * ]);\n * };\n *\n * return (\n * <div>\n * {Object.entries(settings).map(([category, items]) => (\n * <section key={category}>\n * <h3>{category}</h3>\n * {items.map(item => (\n * <div key={item.key}>\n * {item.key}: {item.value}\n * </div>\n * ))}\n * </section>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useSystemSettings(): UseSystemSettingsReturn {\n const { config, authState, _internal } = useCedrosLogin();\n\n const [settings, setSettings] = useState<Record<string, SystemSetting[]>>({});\n const [isLoading, setIsLoading] = useState(false);\n const [isUpdating, setIsUpdating] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const requestIdRef = useRef(0);\n\n const apiClient = useMemo(\n () =>\n new SystemSettingsApiClient(\n config.serverUrl,\n config.requestTimeout,\n config.retryAttempts,\n _internal?.getAccessToken\n ),\n [config.serverUrl, config.requestTimeout, config.retryAttempts, _internal]\n );\n\n // Use ref to avoid apiClient in callback dependencies\n const apiClientRef = useRef(apiClient);\n apiClientRef.current = apiClient;\n\n const fetchSettings = useCallback(async () => {\n if (authState !== 'authenticated') {\n setSettings({});\n return;\n }\n\n setIsLoading(true);\n setError(null);\n const requestId = ++requestIdRef.current;\n\n try {\n const response = await apiClientRef.current.getSettings();\n if (requestId !== requestIdRef.current) return;\n setSettings(response.settings);\n } catch (err) {\n if (requestId !== requestIdRef.current) return;\n setError(err instanceof Error ? err : new Error('Failed to fetch settings'));\n } finally {\n if (requestId === requestIdRef.current) {\n setIsLoading(false);\n }\n }\n }, [authState]);\n\n const updateSettings = useCallback(\n async (updates: UpdateSettingRequest[]): Promise<void> => {\n if (authState !== 'authenticated') {\n throw new Error('Not authenticated');\n }\n\n setIsUpdating(true);\n setError(null);\n\n try {\n await apiClientRef.current.updateSettings(updates);\n // Refresh settings after update\n await fetchSettings();\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update settings');\n setError(error);\n throw error;\n } finally {\n setIsUpdating(false);\n }\n },\n [authState, fetchSettings]\n );\n\n const getValue = useCallback(\n (key: string): string | undefined => {\n for (const categorySettings of Object.values(settings)) {\n const found = categorySettings.find((s) => s.key === key);\n if (found) return found.value;\n }\n return undefined;\n },\n [settings]\n );\n\n return {\n settings,\n isLoading,\n isUpdating,\n error,\n fetchSettings,\n updateSettings,\n getValue,\n };\n}\n"],"names":["SystemSettingsApiClient","baseUrl","timeoutMs","retryAttempts","getAccessToken","ApiClient","error","handleApiError","settings","useSystemSettings","config","authState","_internal","useCedrosLogin","setSettings","useState","isLoading","setIsLoading","isUpdating","setIsUpdating","setError","requestIdRef","useRef","apiClient","useMemo","apiClientRef","fetchSettings","useCallback","requestId","response","err","updateSettings","updates","getValue","key","categorySettings","found","s"],"mappings":"iFAUO,MAAMA,CAAwB,CAC3B,OAER,YACEC,EACAC,EACAC,EACAC,EACA,CACA,KAAK,OAAS,IAAIC,YAAU,CAAE,QAAAJ,EAAS,UAAAC,EAAW,cAAAC,EAAe,eAAAC,EAAgB,CACnF,CAMA,MAAM,aAAmD,CACvD,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,IAAgC,iBAAiB,CAC5E,OAASE,EAAO,CACd,MAAMC,EAAAA,eAAeD,EAAO,iCAAiC,CAC/D,CACF,CAMA,MAAM,eAAeE,EAAyE,CAC5F,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,MAAoC,kBAAmB,CAC9E,SAAAA,CAAA,CACD,CACH,OAASF,EAAO,CACd,MAAMC,EAAAA,eAAeD,EAAO,kCAAkC,CAChE,CACF,CACF,CCGO,SAASG,GAA6C,CAC3D,KAAM,CAAE,OAAAC,EAAQ,UAAAC,EAAW,UAAAC,CAAA,EAAcC,EAAAA,eAAA,EAEnC,CAACL,EAAUM,CAAW,EAAIC,EAAAA,SAA0C,CAAA,CAAE,EACtE,CAACC,EAAWC,CAAY,EAAIF,EAAAA,SAAS,EAAK,EAC1C,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,EAAK,EAC5C,CAACT,EAAOc,CAAQ,EAAIL,EAAAA,SAAuB,IAAI,EAC/CM,EAAeC,EAAAA,OAAO,CAAC,EAEvBC,EAAYC,EAAAA,QAChB,IACE,IAAIxB,EACFU,EAAO,UACPA,EAAO,eACPA,EAAO,cACPE,GAAW,cAAA,EAEf,CAACF,EAAO,UAAWA,EAAO,eAAgBA,EAAO,cAAeE,CAAS,CAAA,EAIrEa,EAAeH,EAAAA,OAAOC,CAAS,EACrCE,EAAa,QAAUF,EAEvB,MAAMG,EAAgBC,EAAAA,YAAY,SAAY,CAC5C,GAAIhB,IAAc,gBAAiB,CACjCG,EAAY,CAAA,CAAE,EACd,MACF,CAEAG,EAAa,EAAI,EACjBG,EAAS,IAAI,EACb,MAAMQ,EAAY,EAAEP,EAAa,QAEjC,GAAI,CACF,MAAMQ,EAAW,MAAMJ,EAAa,QAAQ,YAAA,EAC5C,GAAIG,IAAcP,EAAa,QAAS,OACxCP,EAAYe,EAAS,QAAQ,CAC/B,OAASC,EAAK,CACZ,GAAIF,IAAcP,EAAa,QAAS,OACxCD,EAASU,aAAe,MAAQA,EAAM,IAAI,MAAM,0BAA0B,CAAC,CAC7E,QAAA,CACMF,IAAcP,EAAa,SAC7BJ,EAAa,EAAK,CAEtB,CACF,EAAG,CAACN,CAAS,CAAC,EAERoB,EAAiBJ,EAAAA,YACrB,MAAOK,GAAmD,CACxD,GAAIrB,IAAc,gBAChB,MAAM,IAAI,MAAM,mBAAmB,EAGrCQ,EAAc,EAAI,EAClBC,EAAS,IAAI,EAEb,GAAI,CACF,MAAMK,EAAa,QAAQ,eAAeO,CAAO,EAEjD,MAAMN,EAAA,CACR,OAASI,EAAK,CACZ,MAAMxB,EAAQwB,aAAe,MAAQA,EAAM,IAAI,MAAM,2BAA2B,EAChF,MAAAV,EAASd,CAAK,EACRA,CACR,QAAA,CACEa,EAAc,EAAK,CACrB,CACF,EACA,CAACR,EAAWe,CAAa,CAAA,EAGrBO,EAAWN,EAAAA,YACdO,GAAoC,CACnC,UAAWC,KAAoB,OAAO,OAAO3B,CAAQ,EAAG,CACtD,MAAM4B,EAAQD,EAAiB,KAAME,GAAMA,EAAE,MAAQH,CAAG,EACxD,GAAIE,SAAcA,EAAM,KAC1B,CAEF,EACA,CAAC5B,CAAQ,CAAA,EAGX,MAAO,CACL,SAAAA,EACA,UAAAQ,EACA,WAAAE,EACA,MAAAZ,EACA,cAAAoB,EACA,eAAAK,EACA,SAAAE,CAAA,CAEJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const e=require("react/jsx-runtime"),n=require("react"),v=require("./useCedrosLogin-C9MrcZvh.cjs"),be=require("./useSystemSettings-DRrreszl.cjs");class pe{client;constructor(t,r,a,b){this.client=new v.ApiClient({baseUrl:t,timeoutMs:r,retryAttempts:a,getAccessToken:b})}async listUsers(t){try{const r=new URLSearchParams;t?.limit&&r.set("limit",String(t.limit)),t?.offset&&r.set("offset",String(t.offset));const a=r.toString(),b=`/admin/users${a?`?${a}`:""}`;return await this.client.get(b)}catch(r){throw v.handleApiError(r,"Failed to list users")}}async getUser(t){try{return await this.client.get(`/admin/users/${t}`)}catch(r){throw v.handleApiError(r,"Failed to get user")}}async setSystemAdmin(t,r){try{await this.client.patch(`/admin/users/${t}/system-admin`,{isAdmin:r})}catch(a){throw v.handleApiError(a,"Failed to update system admin status")}}async updateUser(t,r){try{return await this.client.patch(`/admin/users/${t}`,r)}catch(a){throw v.handleApiError(a,"Failed to update user")}}async deleteUser(t){try{await this.client.delete(`/admin/users/${t}`)}catch(r){throw v.handleApiError(r,"Failed to delete user")}}async forcePasswordReset(t){try{await this.client.post(`/admin/users/${t}/force-password-reset`,{})}catch(r){throw v.handleApiError(r,"Failed to send password reset email")}}async adjustCredits(t,r){try{await this.client.post(`/admin/users/${t}/credits`,r)}catch(a){throw v.handleApiError(a,"Failed to adjust credits")}}async getUserDeposits(t,r){try{const a=new URLSearchParams;r?.limit&&a.set("limit",String(r.limit)),r?.offset&&a.set("offset",String(r.offset));const b=a.toString(),p=`/admin/users/${t}/deposits${b?`?${b}`:""}`;return await this.client.get(p)}catch(a){throw v.handleApiError(a,"Failed to get user deposits")}}async getUserCredits(t,r){try{const a=new URLSearchParams;r?.limit&&a.set("limit",String(r.limit)),r?.offset&&a.set("offset",String(r.offset));const b=a.toString(),p=`/admin/users/${t}/credits${b?`?${b}`:""}`;return await this.client.get(p)}catch(a){throw v.handleApiError(a,"Failed to get user credits")}}async getUserWithdrawalHistory(t,r){try{const a=new URLSearchParams;r?.limit&&a.set("limit",String(r.limit)),r?.offset&&a.set("offset",String(r.offset));const b=a.toString(),p=`/admin/users/${t}/withdrawal-history${b?`?${b}`:""}`;return await this.client.get(p)}catch(a){throw v.handleApiError(a,"Failed to get user withdrawal history")}}async getUserChats(t,r){try{const a=new URLSearchParams;r?.limit&&a.set("limit",String(r.limit)),r?.offset&&a.set("offset",String(r.offset));const b=a.toString(),p=`/admin/users/${t}/chats${b?`?${b}`:""}`;return await this.client.get(p)}catch(a){throw v.handleApiError(a,"Failed to get user chat history")}}async getStats(){try{return await this.client.get("/admin/users/stats")}catch(t){throw v.handleApiError(t,"Failed to get user stats")}}}function V(){const{config:s,_internal:t}=v.useCedrosLogin(),[r,a]=n.useState([]),[b,p]=n.useState(0),[j,l]=n.useState(!1),[u,h]=n.useState(null),[A,D]=n.useState({}),f=n.useMemo(()=>new pe(s.serverUrl,s.requestTimeout,s.retryAttempts,t?.getAccessToken),[s.serverUrl,s.requestTimeout,s.retryAttempts,t]),T=n.useCallback(async d=>{l(!0),h(null),D(d||{});try{const i=await f.listUsers(d);return a(i.users),p(i.total),i}catch(i){const o=i instanceof Error?i:new Error("Failed to list users");throw h(o),o}finally{l(!1)}},[f]),y=n.useCallback(async d=>{l(!0),h(null);try{return await f.getUser(d)}catch(i){const o=i instanceof Error?i:new Error("Failed to get user");throw h(o),o}finally{l(!1)}},[f]),c=n.useCallback(async(d,i)=>{l(!0),h(null);try{await f.setSystemAdmin(d,i),a(o=>o.map(g=>g.id===d?{...g,isSystemAdmin:i}:g))}catch(o){const g=o instanceof Error?o:new Error("Failed to update admin status");throw h(g),g}finally{l(!1)}},[f]),N=n.useCallback(async(d,i)=>{l(!0),h(null);try{const o=await f.updateUser(d,i);return a(g=>g.map(R=>R.id===d?o:R)),o}catch(o){const g=o instanceof Error?o:new Error("Failed to update user");throw h(g),g}finally{l(!1)}},[f]),w=n.useCallback(async d=>{l(!0),h(null);try{await f.deleteUser(d),a(i=>i.filter(o=>o.id!==d)),p(i=>i-1)}catch(i){const o=i instanceof Error?i:new Error("Failed to delete user");throw h(o),o}finally{l(!1)}},[f]),S=n.useCallback(async d=>{l(!0),h(null);try{await f.forcePasswordReset(d)}catch(i){const o=i instanceof Error?i:new Error("Failed to send password reset");throw h(o),o}finally{l(!1)}},[f]),k=n.useCallback(async(d,i,o)=>{l(!0),h(null);try{await f.adjustCredits(d,{amount:i,reason:o})}catch(g){const R=g instanceof Error?g:new Error("Failed to adjust credits");throw h(R),R}finally{l(!1)}},[f]),F=n.useCallback(async(d,i)=>{l(!0),h(null);try{return await f.getUserDeposits(d,i)}catch(o){const g=o instanceof Error?o:new Error("Failed to get user deposits");throw h(g),g}finally{l(!1)}},[f]),$=n.useCallback(async(d,i)=>{l(!0),h(null);try{return await f.getUserCredits(d,i)}catch(o){const g=o instanceof Error?o:new Error("Failed to get user credits");throw h(g),g}finally{l(!1)}},[f]),L=n.useCallback(async(d,i)=>{l(!0),h(null);try{return await f.getUserWithdrawalHistory(d,i)}catch(o){const g=o instanceof Error?o:new Error("Failed to get user withdrawal history");throw h(g),g}finally{l(!1)}},[f]),C=n.useCallback(async(d,i)=>{l(!0),h(null);try{return await f.getUserChats(d,i)}catch(o){const g=o instanceof Error?o:new Error("Failed to get user chat history");throw h(g),g}finally{l(!1)}},[f]),U=n.useCallback(async()=>{l(!0),h(null);try{return await f.getStats()}catch(d){const i=d instanceof Error?d:new Error("Failed to get user stats");throw h(i),i}finally{l(!1)}},[f]),P=n.useCallback(async()=>{await T(A)},[T,A]),m=n.useCallback(()=>{h(null)},[]);return{users:r,total:b,isLoading:j,error:u,listUsers:T,getUser:y,setSystemAdmin:c,updateUser:N,deleteUser:w,forcePasswordReset:S,adjustCredits:k,getUserDeposits:F,getUserCredits:$,getUserWithdrawalHistory:L,getUserChats:C,getStats:U,refresh:P,clearError:m}}function z(s){return new Date(s).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function ge(s){return s.length<=12?s:`${s.slice(0,6)}...${s.slice(-4)}`}function xe(s){return s==null?"-":(s/1e9).toFixed(4)}function je({pageSize:s=20,refreshInterval:t=0,currentUserId:r,className:a="",onLoad:b,onUserClick:p}){const{users:j,total:l,isLoading:u,error:h,listUsers:A,clearError:D}=V(),[f,T]=n.useState(0),[y,c]=n.useState(null),[N,w]=n.useState("createdAt"),[S,k]=n.useState("desc"),F=m=>{N===m?k(S==="asc"?"desc":"asc"):(w(m),k("desc"))},$=n.useMemo(()=>[...j].sort((m,d)=>{let i,o;switch(N){case"name":i=(m.name||m.email||"").toLowerCase(),o=(d.name||d.email||"").toLowerCase();break;case"createdAt":i=new Date(m.createdAt).getTime(),o=new Date(d.createdAt).getTime();break;case"lastLoginAt":i=m.lastLoginAt?new Date(m.lastLoginAt).getTime():0,o=d.lastLoginAt?new Date(d.lastLoginAt).getTime():0;break;case"balanceLamports":i=m.balanceLamports??0,o=d.balanceLamports??0;break;default:return 0}return i<o?S==="asc"?-1:1:i>o?S==="asc"?1:-1:0}),[j,N,S]),L=n.useCallback(async()=>{try{const m=await A({limit:s,offset:f});b?.(m),c(null)}catch(m){c(m instanceof Error?m.message:"Failed to load users")}},[s,f,A,b]);n.useEffect(()=>{T(0)},[s]),n.useEffect(()=>{L()},[L]),n.useEffect(()=>{if(t<=0)return;const m=setInterval(L,t);return()=>clearInterval(m)},[t,L]);const C=Math.ceil(l/s),U=Math.floor(f/s)+1,P=m=>{const d=(m-1)*s;T(Math.max(0,Math.min(d,Math.max(0,l-1))))};return y||h?e.jsxs("div",{className:`cedros-admin-user-list cedros-admin-user-list-error ${a}`,children:[e.jsx("p",{className:"cedros-admin-error",children:y||h?.message}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline",onClick:()=>{D(),c(null),L()},children:"Retry"})]}):u&&j.length===0?e.jsxs("div",{className:`cedros-admin-user-list cedros-admin-user-list-loading ${a}`,children:[e.jsx("span",{className:"cedros-admin-loading-indicator"}),e.jsx("span",{className:"cedros-admin-loading-text",children:"Loading users..."})]}):e.jsxs("div",{className:`cedros-admin-user-list ${a}`,children:[e.jsxs("div",{className:"cedros-admin-user-list-header",children:[e.jsx("h4",{className:"cedros-admin-user-list-title",children:"All Users"}),e.jsxs("div",{className:"cedros-admin-user-list-actions",children:[e.jsxs("span",{className:"cedros-admin-queue-count",children:[l," user",l!==1?"s":""]}),e.jsx("button",{type:"button",className:"cedros-admin__stats-bar-refresh",onClick:L,disabled:u,title:"Refresh list","aria-label":"Refresh list",children:u?"...":"↻"})]})]}),j.length===0?e.jsx("div",{className:"cedros-admin-empty",children:e.jsx("p",{className:"cedros-admin-empty-message",children:"No users found."})}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"cedros-admin-user-table",children:[e.jsxs("div",{className:"cedros-admin-user-thead",children:[e.jsx("div",{className:"cedros-admin-user-th",children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${N==="name"?"cedros-admin-sort-active":""}`,onClick:()=>F("name"),"aria-label":"Sort by user",children:["User"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:N==="name"?S==="asc"?"↑":"↓":"↕"})]})}),e.jsx("div",{className:"cedros-admin-user-th",children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${N==="createdAt"?"cedros-admin-sort-active":""}`,onClick:()=>F("createdAt"),"aria-label":"Sort by registered date",children:["Registered"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:N==="createdAt"?S==="asc"?"↑":"↓":"↕"})]})}),e.jsx("div",{className:"cedros-admin-user-th",children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${N==="lastLoginAt"?"cedros-admin-sort-active":""}`,onClick:()=>F("lastLoginAt"),"aria-label":"Sort by last login",children:["Last Login"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:N==="lastLoginAt"?S==="asc"?"↑":"↓":"↕"})]})}),e.jsx("div",{className:"cedros-admin-user-th",children:e.jsxs("button",{type:"button",className:`cedros-admin-sort-button ${N==="balanceLamports"?"cedros-admin-sort-active":""}`,onClick:()=>F("balanceLamports"),"aria-label":"Sort by balance",children:["Balance"," ",e.jsx("span",{className:"cedros-admin-sort-icon",children:N==="balanceLamports"?S==="asc"?"↑":"↓":"↕"})]})})]}),$.map(m=>{const d=m.id===r;return e.jsxs("div",{className:`cedros-admin-user-row ${d?"cedros-admin-user-row-current":""}`,onClick:()=>p?.(m),onKeyDown:i=>{(i.key==="Enter"||i.key===" ")&&(i.preventDefault(),p?.(m))},role:p?"button":void 0,tabIndex:p?0:void 0,children:[e.jsxs("div",{className:"cedros-admin-user-td cedros-admin-user-info",children:[e.jsx("div",{className:"cedros-admin-user-avatar",children:m.picture?e.jsx("img",{src:m.picture,alt:m.name||m.email||"User",className:"cedros-admin-user-avatar-img",referrerPolicy:"no-referrer"}):e.jsx("span",{className:"cedros-admin-user-avatar-placeholder",children:(m.name?.[0]||m.email?.[0]||"?").toUpperCase()})}),e.jsxs("div",{className:"cedros-admin-user-details",children:[e.jsxs("span",{className:"cedros-admin-user-name",children:[m.name||"Unknown",d&&e.jsx("span",{className:"cedros-admin-user-you",children:"(you)"})]}),e.jsx("span",{className:"cedros-admin-user-email",title:m.email,children:m.email||ge(m.id)})]})]}),e.jsx("div",{className:"cedros-admin-user-td",children:z(m.createdAt)}),e.jsx("div",{className:"cedros-admin-user-td",children:m.lastLoginAt?z(m.lastLoginAt):"-"}),e.jsx("div",{className:"cedros-admin-user-td",children:xe(m.balanceLamports)})]},m.id)})]}),C>1&&e.jsxs("div",{className:"cedros-admin-pagination",children:[e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>P(U-1),disabled:U<=1,children:"Previous"}),e.jsxs("span",{className:"cedros-admin-page-info",children:["Page ",U," of ",C," (",l," total)"]}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>P(U+1),disabled:U>=C,children:"Next"})]})]})]})}function ee(s){return new Date(s).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function Q(s){return new Date(s).toLocaleString(void 0,{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}function ye(s){return s==null?"—":`${(s/1e9).toFixed(4)} SOL`}function B(s){return s==null?"—":(s/1e9).toFixed(4)}function Ne(s){return{DEPOSIT:"Deposit",SPEND:"Spend",ADJUSTMENT:"Adjustment",REFUND:"Refund"}[s.toUpperCase()]||s}function we(s,t){return t?{deposit:"Credit deposit",purchase:"Purchase",api_call:"API usage",subscription:"Subscription",refund:"Refund",bonus:"Bonus credit",promo:"Promotional credit",correction:"Balance correction"}[t.toLowerCase()]||t:{DEPOSIT:"Credit added",SPEND:"Credit used",ADJUSTMENT:"Manual adjustment",REFUND:"Credit refunded"}[s.toUpperCase()]||"—"}function ve({userId:s,onBack:t,currentUserId:r,onEditUser:a,onAdjustCredits:b,cedrosPayEnabled:p=!1,className:j=""}){const{isLoading:l,getUser:u,getUserDeposits:h,getUserCredits:A,getUserChats:D,deleteUser:f,forcePasswordReset:T,clearError:y}=V(),[c,N]=n.useState(null),[w,S]=n.useState(null),[k,F]=n.useState(null),[$,L]=n.useState(null),[C,U]=n.useState("deposits"),[P,m]=n.useState(null),[d,i]=n.useState(null),[o,g]=n.useState(null),[R,H]=n.useState(null),[O,_]=n.useState(!1),[W,se]=n.useState(0),[G,te]=n.useState(0),[J,ae]=n.useState(0),E=10,I=n.useCallback(async()=>{try{const x=await u(s);N(x),m(null)}catch(x){m(x instanceof Error?x.message:"Failed to load user")}},[s,u]),K=n.useCallback(async()=>{try{const M=await h(s,{limit:E,offset:W});F(M),g(null)}catch(x){g(x instanceof Error?x.message:"Failed to load deposits")}},[s,h,W]),q=n.useCallback(async()=>{try{const M=await A(s,{limit:E,offset:G});S(M),i(null)}catch(x){i(x instanceof Error?x.message:"Failed to load credits")}},[s,A,G]),Y=n.useCallback(async()=>{if(p)try{const M=await D(s,{limit:E,offset:J});L(M),H(null)}catch(x){H(x instanceof Error?x.message:"Failed to load chat history")}},[s,D,J,p]);n.useEffect(()=>{I(),K(),q(),p&&Y()},[I,K,q,Y,p]);const re=async()=>{if(!c)return;if(c.id===r){alert("You cannot delete your own account");return}if(c.isSystemAdmin){alert("Cannot delete a system admin. Remove admin status first.");return}if(window.confirm(`Are you sure you want to delete ${c.name||c.email||"this user"}? This action cannot be undone.`)){_(!0);try{await f(c.id),t()}catch{}finally{_(!1)}}},ne=async()=>{if(!c?.email){alert("User has no email address");return}if(window.confirm(`Send a password reset email to ${c.email}?`)){_(!0);try{await T(c.id),alert("Password reset email sent")}catch{}finally{_(!1)}}},ie=k?Math.ceil(k.total/E):0,oe=Math.floor(W/E)+1,ce=w?Math.ceil(w.totalTransactions/E):0,de=Math.floor(G/E)+1,le=x=>{se((x-1)*E)},me=x=>{te((x-1)*E)},ue=$?Math.ceil($.total/E):0,he=Math.floor(J/E)+1,fe=x=>{ae((x-1)*E)};if(P)return e.jsxs("div",{className:`cedros-admin-user-detail cedros-admin-user-detail-error ${j}`,children:[e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:t,children:"Back"}),e.jsx("p",{className:"cedros-admin-error",children:P}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline",onClick:()=>{y(),m(null),I()},children:"Retry"})]});if(l&&!c)return e.jsxs("div",{className:`cedros-admin-user-detail cedros-admin-user-detail-loading ${j}`,children:[e.jsx("span",{className:"cedros-admin-loading-indicator"}),e.jsx("span",{className:"cedros-admin-loading-text",children:"Loading user..."})]});if(!c)return e.jsxs("div",{className:`cedros-admin-user-detail ${j}`,children:[e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:t,children:"Back"}),e.jsx("p",{className:"cedros-admin-empty-message",children:"User not found."})]});const Z=c.id===r;return e.jsxs("div",{className:`cedros-admin-user-detail ${j}`,children:[e.jsxs("div",{className:"cedros-admin-user-detail-header",children:[e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm cedros-admin-back-btn",onClick:t,children:"Back to Users"}),e.jsxs("div",{className:"cedros-admin-user-detail-actions",children:[a&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>a(c),disabled:O,children:"Edit"}),c.email&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:ne,disabled:O,children:"Reset Password"}),b&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>b(c),disabled:O,children:"Adjust Credits"}),!Z&&!c.isSystemAdmin&&e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm cedros-admin-btn-danger",onClick:re,disabled:O,children:"Delete"})]})]}),e.jsxs("div",{className:"cedros-admin-user-detail-info",children:[e.jsx("div",{className:"cedros-admin-user-detail-avatar",children:c.picture?e.jsx("img",{src:c.picture,alt:c.name||c.email||"User",className:"cedros-admin-user-detail-avatar-img",referrerPolicy:"no-referrer"}):e.jsx("span",{className:"cedros-admin-user-detail-avatar-placeholder",children:(c.name?.[0]||c.email?.[0]||"?").toUpperCase()})}),e.jsxs("div",{className:"cedros-admin-user-detail-meta",children:[e.jsxs("h2",{className:"cedros-admin-user-detail-name",children:[c.name||"Unknown",Z&&e.jsx("span",{className:"cedros-admin-user-you",children:"(you)"})]}),e.jsxs("p",{className:"cedros-admin-user-detail-email",children:[c.email||"No email",c.emailVerified&&e.jsx("span",{className:"cedros-admin-verified-badge",title:"Email verified",children:"Verified"})]}),c.isSystemAdmin&&e.jsx("div",{className:"cedros-admin-user-detail-badges",children:e.jsx("span",{className:"cedros-admin-admin-badge cedros-admin-admin-badge-yes",children:"System Admin"})}),e.jsxs("div",{className:"cedros-admin-user-detail-methods",children:[e.jsx("span",{className:"cedros-admin-user-detail-methods-label",children:"Auth Methods:"}),c.authMethods.length>0?c.authMethods.map(x=>e.jsx("span",{className:`cedros-admin-auth-badge cedros-admin-auth-badge-${x}`,children:x},x)):e.jsx("span",{className:"cedros-admin-auth-badge cedros-admin-auth-badge-none",children:"none"})]}),e.jsxs("p",{className:"cedros-admin-user-detail-dates",children:["Registered: ",ee(c.createdAt)," | Updated: ",ee(c.updatedAt)]})]})]}),d?e.jsxs("div",{className:"cedros-admin-stats-error",children:[e.jsx("p",{className:"cedros-admin-error-inline",children:d}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>{i(null),q()},children:"Retry"})]}):w?e.jsxs("div",{className:"cedros-admin-user-detail-stats",children:[e.jsxs("div",{className:"cedros-admin-stat-card",children:[e.jsx("span",{className:"cedros-admin-stat-label",children:"Credit Balance"}),e.jsx("span",{className:"cedros-admin-stat-value",children:B(w.stats.currentBalanceLamports)})]}),e.jsxs("div",{className:"cedros-admin-stat-card",children:[e.jsx("span",{className:"cedros-admin-stat-label",children:"Total Credited"}),e.jsx("span",{className:"cedros-admin-stat-value",children:B(w.stats.totalDepositedLamports)})]}),e.jsxs("div",{className:"cedros-admin-stat-card",children:[e.jsx("span",{className:"cedros-admin-stat-label",children:"Total Spent"}),e.jsx("span",{className:"cedros-admin-stat-value",children:B(w.stats.totalSpentLamports)})]}),e.jsxs("div",{className:"cedros-admin-stat-card",children:[e.jsx("span",{className:"cedros-admin-stat-label",children:"Deposits"}),e.jsx("span",{className:"cedros-admin-stat-value",children:w.stats.depositCount})]}),e.jsxs("div",{className:"cedros-admin-stat-card",children:[e.jsx("span",{className:"cedros-admin-stat-label",children:"Transactions"}),e.jsx("span",{className:"cedros-admin-stat-value",children:w.stats.spendCount})]})]}):e.jsxs("div",{className:"cedros-admin-stats-loading",children:[e.jsx("span",{className:"cedros-admin-loading-indicator"}),e.jsx("span",{children:"Loading credit stats..."})]}),e.jsxs("div",{className:"cedros-admin-user-detail-tabs",children:[e.jsxs("button",{type:"button",className:`cedros-admin-tab ${C==="deposits"?"cedros-admin-tab-active":""}`,onClick:()=>U("deposits"),children:["Deposits (",k?.total??0,")"]}),e.jsxs("button",{type:"button",className:`cedros-admin-tab ${C==="transactions"?"cedros-admin-tab-active":""}`,onClick:()=>U("transactions"),children:["Credits (",w?.totalTransactions??0,")"]}),p&&e.jsxs("button",{type:"button",className:`cedros-admin-tab ${C==="chats"?"cedros-admin-tab-active":""}`,onClick:()=>U("chats"),children:["Chats (",$?.total??0,")"]})]}),e.jsxs("div",{className:"cedros-admin-user-detail-content",children:[C==="deposits"&&e.jsx(Se,{deposits:k?.deposits??[],total:k?.total??0,currentPage:oe,totalPages:ie,onPageChange:le,isLoading:l,error:o,onRetry:()=>{g(null),K()}}),C==="transactions"&&e.jsx(Ce,{transactions:w?.transactions??[],total:w?.totalTransactions??0,currentPage:de,totalPages:ce,onPageChange:me,error:d,onRetry:()=>{i(null),q()},isLoading:l}),C==="chats"&&p&&e.jsx(Ee,{sessions:$?.sessions??[],total:$?.total??0,currentPage:he,totalPages:ue,onPageChange:fe,error:R,onRetry:()=>{H(null),Y()},isLoading:l})]})]})}function Se({deposits:s,total:t,currentPage:r,totalPages:a,onPageChange:b,isLoading:p,error:j,onRetry:l}){return j?e.jsxs("div",{className:"cedros-admin-tab-error",children:[e.jsx("p",{className:"cedros-admin-error-inline",children:j}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:l,children:"Retry"})]}):p&&s.length===0?e.jsxs("div",{className:"cedros-admin-tab-loading",children:[e.jsx("span",{className:"cedros-admin-loading-indicator"}),e.jsx("span",{children:"Loading deposits..."})]}):t===0?e.jsx("div",{className:"cedros-admin-empty-message",children:"No deposits found."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"cedros-admin-list-table",children:[e.jsxs("div",{className:"cedros-admin-list-thead",children:[e.jsx("div",{className:"cedros-admin-list-th",children:"Date"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Amount"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Status"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Transaction"})]}),s.map(u=>e.jsxs("div",{className:"cedros-admin-list-row",children:[e.jsx("div",{className:"cedros-admin-list-td",children:Q(u.createdAt)}),e.jsx("div",{className:"cedros-admin-list-td",children:ye(u.amountLamports)}),e.jsx("div",{className:"cedros-admin-list-td",children:e.jsx("span",{className:`cedros-admin-status-badge cedros-admin-status-${u.status}`,children:u.status})}),e.jsx("div",{className:"cedros-admin-list-td cedros-admin-list-td-actions",children:u.txSignature?e.jsxs(e.Fragment,{children:[e.jsxs("span",{className:"cedros-admin-list-td-mono",title:u.txSignature,children:[u.txSignature.slice(0,8),"..."]}),e.jsx("a",{href:`https://orbmarkets.io/tx/${u.txSignature}`,target:"_blank",rel:"noopener noreferrer",className:"cedros-admin-icon-link",title:"View on Orbmarkets","aria-label":"View transaction on Orbmarkets",children:"↗"})]}):e.jsx("span",{className:"cedros-admin-list-td-muted",children:"—"})})]},u.id))]}),a>1&&e.jsx(X,{currentPage:r,totalPages:a,total:t,onPageChange:b})]})}function Ce({transactions:s,total:t,currentPage:r,totalPages:a,onPageChange:b,isLoading:p,error:j,onRetry:l}){return j?e.jsxs("div",{className:"cedros-admin-tab-error",children:[e.jsx("p",{className:"cedros-admin-error-inline",children:j}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:l,children:"Retry"})]}):p&&s.length===0?e.jsxs("div",{className:"cedros-admin-tab-loading",children:[e.jsx("span",{className:"cedros-admin-loading-indicator"}),e.jsx("span",{children:"Loading transactions..."})]}):t===0?e.jsx("div",{className:"cedros-admin-empty-message",children:"No credit transactions found."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"cedros-admin-list-table",children:[e.jsxs("div",{className:"cedros-admin-list-thead",children:[e.jsx("div",{className:"cedros-admin-list-th",children:"Date"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Type"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Description"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Amount"})]}),s.map(u=>e.jsxs("div",{className:"cedros-admin-list-row",children:[e.jsx("div",{className:"cedros-admin-list-td",children:Q(u.createdAt)}),e.jsx("div",{className:"cedros-admin-list-td",children:e.jsx("span",{className:`cedros-admin-tx-type cedros-admin-tx-type-${u.txType.toLowerCase()}`,children:Ne(u.txType)})}),e.jsx("div",{className:"cedros-admin-list-td",children:we(u.txType,u.referenceType)}),e.jsxs("div",{className:`cedros-admin-list-td ${u.amountLamports>=0?"cedros-admin-amount-positive":"cedros-admin-amount-negative"}`,children:[u.amountLamports>=0?"+":"",B(u.amountLamports)]})]},u.id))]}),a>1&&e.jsx(X,{currentPage:r,totalPages:a,total:t,onPageChange:b})]})}function Ee({sessions:s,total:t,currentPage:r,totalPages:a,onPageChange:b,isLoading:p,error:j,onRetry:l}){return j?e.jsxs("div",{className:"cedros-admin-tab-error",children:[e.jsx("p",{className:"cedros-admin-error-inline",children:j}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:l,children:"Retry"})]}):p&&s.length===0?e.jsxs("div",{className:"cedros-admin-tab-loading",children:[e.jsx("span",{className:"cedros-admin-loading-indicator"}),e.jsx("span",{children:"Loading chat history..."})]}):t===0?e.jsx("div",{className:"cedros-admin-empty-message",children:"No chat sessions found."}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"cedros-admin-list-table",children:[e.jsxs("div",{className:"cedros-admin-list-thead",children:[e.jsx("div",{className:"cedros-admin-list-th",children:"Date"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Session"}),e.jsx("div",{className:"cedros-admin-list-th",children:"Messages"})]}),s.map(u=>e.jsxs("div",{className:"cedros-admin-list-row",children:[e.jsx("div",{className:"cedros-admin-list-td",children:Q(u.createdAt)}),e.jsx("div",{className:"cedros-admin-list-td",children:u.title||`Chat ${u.id.slice(0,8)}...`}),e.jsx("div",{className:"cedros-admin-list-td",children:u.messageCount})]},u.id))]}),a>1&&e.jsx(X,{currentPage:r,totalPages:a,total:t,onPageChange:b})]})}function X({currentPage:s,totalPages:t,total:r,onPageChange:a}){return e.jsxs("div",{className:"cedros-admin-pagination",children:[e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>a(s-1),disabled:s<=1,children:"Previous"}),e.jsxs("span",{className:"cedros-admin-page-info",children:["Page ",s," of ",t," (",r," total)"]}),e.jsx("button",{type:"button",className:"cedros-button cedros-button-outline cedros-button-sm",onClick:()=>a(s+1),disabled:s>=t,children:"Next"})]})}const Ue=["email","google","apple","solana","webauthn","sso"],Ae={email:"Email Users",google:"Google Users",apple:"Apple Users",solana:"Solana Users",webauthn:"Passkey Users",sso:"SSO Provider Users"},ke={email:"auth_email_enabled",google:"auth_google_enabled",apple:"auth_apple_enabled",solana:"auth_solana_enabled",webauthn:"auth_webauthn_enabled",sso:"feature_sso"};function Le(){const{getStats:s}=V(),{fetchSettings:t,getValue:r}=be.useSystemSettings(),[a,b]=n.useState(null),[p,j]=n.useState(!1),[l,u]=n.useState(null),[h,A]=n.useState(!1);n.useEffect(()=>{h||(t(),A(!0))},[t,h]);const D=n.useCallback(y=>{const c=r(y);return c===void 0?!1:c==="true"||c==="1"},[r]),f=n.useCallback(async()=>{j(!0),u(null);try{const y=await s();b(y)}catch(y){u(y instanceof Error?y.message:"Failed to load user stats")}finally{j(!1)}},[s]);return n.useEffect(()=>{f()},[f]),{statsItems:n.useMemo(()=>{const y=[{label:"Total Users",value:a?.total??"—"}];return Ue.forEach(c=>{D(ke[c])&&y.push({label:Ae[c],value:a?.authMethodCounts[c]??0})}),y},[a,D]),isLoading:p,error:l,refresh:f}}exports.AdminUserDetail=ve;exports.AdminUserList=je;exports.useAdminUsers=V;exports.useUsersStatsSummary=Le;
|