@cedros/login-react 0.0.1

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.
Files changed (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1183 -0
  3. package/dist/EmailRegisterForm-D_uCEdX9.cjs +1 -0
  4. package/dist/EmailRegisterForm-D_uCEdX9.cjs.map +1 -0
  5. package/dist/EmailRegisterForm-m3rX3A6X.js +2923 -0
  6. package/dist/EmailRegisterForm-m3rX3A6X.js.map +1 -0
  7. package/dist/ErrorMessage-Bm1j5mBT.js +2042 -0
  8. package/dist/ErrorMessage-Bm1j5mBT.js.map +1 -0
  9. package/dist/ErrorMessage-CntMyn93.cjs +1 -0
  10. package/dist/ErrorMessage-CntMyn93.cjs.map +1 -0
  11. package/dist/GoogleLoginButton-CJNJ-THo.cjs +1 -0
  12. package/dist/GoogleLoginButton-CJNJ-THo.cjs.map +1 -0
  13. package/dist/GoogleLoginButton-CvDoOc-0.js +227 -0
  14. package/dist/GoogleLoginButton-CvDoOc-0.js.map +1 -0
  15. package/dist/SolanaLoginButton-BlSgPW50.cjs +1 -0
  16. package/dist/SolanaLoginButton-BlSgPW50.cjs.map +1 -0
  17. package/dist/SolanaLoginButton-h32xN2PQ.js +261 -0
  18. package/dist/SolanaLoginButton-h32xN2PQ.js.map +1 -0
  19. package/dist/assets/argon2Worker-Bi5TuQvD.js +1 -0
  20. package/dist/assets/argon2Worker-Bi5TuQvD.js.map +1 -0
  21. package/dist/components/LoginButton.d.ts +23 -0
  22. package/dist/components/LoginForm.d.ts +9 -0
  23. package/dist/components/LoginModal.d.ts +9 -0
  24. package/dist/components/admin/AdminUserDetail.d.ts +21 -0
  25. package/dist/components/admin/AdminUserList.d.ts +25 -0
  26. package/dist/components/admin/CedrosAdminDashboard.d.ts +48 -0
  27. package/dist/components/admin/SystemSettings.d.ts +19 -0
  28. package/dist/components/apple/AppleLoginButton.d.ts +29 -0
  29. package/dist/components/deposit/CreditBalance.d.ts +19 -0
  30. package/dist/components/deposit/DepositFlow.d.ts +118 -0
  31. package/dist/components/deposit/FeeConfigDisplay.d.ts +15 -0
  32. package/dist/components/deposit/History.d.ts +21 -0
  33. package/dist/components/deposit/TieredAmountSlider.d.ts +19 -0
  34. package/dist/components/deposit/TokenSelector.d.ts +23 -0
  35. package/dist/components/deposit/admin/AdminDepositList.d.ts +21 -0
  36. package/dist/components/deposit/admin/AdminDepositStats.d.ts +15 -0
  37. package/dist/components/deposit/admin/AdminPrivacyPeriodDeposits.d.ts +19 -0
  38. package/dist/components/deposit/admin/AdminWithdrawalHistory.d.ts +19 -0
  39. package/dist/components/deposit/admin/AdminWithdrawalQueue.d.ts +23 -0
  40. package/dist/components/deposit/admin/PrivacySystemStatus.d.ts +15 -0
  41. package/dist/components/deposit/admin/index.d.ts +17 -0
  42. package/dist/components/deposit/index.d.ts +12 -0
  43. package/dist/components/deposit/tierUtils.d.ts +8 -0
  44. package/dist/components/deposit/tokens.d.ts +19 -0
  45. package/dist/components/email/EmailLoginForm.d.ts +11 -0
  46. package/dist/components/email/EmailRegisterForm.d.ts +14 -0
  47. package/dist/components/email/ForgotPasswordForm.d.ts +17 -0
  48. package/dist/components/email/PasswordInput.d.ts +14 -0
  49. package/dist/components/email/ResetPasswordForm.d.ts +22 -0
  50. package/dist/components/google/GoogleLoginButton.d.ts +12 -0
  51. package/dist/components/invites/InviteForm.d.ts +38 -0
  52. package/dist/components/invites/InviteList.d.ts +40 -0
  53. package/dist/components/members/MemberList.d.ts +47 -0
  54. package/dist/components/org/CreateOrgForm.d.ts +7 -0
  55. package/dist/components/org/OrgAvatar.d.ts +7 -0
  56. package/dist/components/org/OrgListView.d.ts +9 -0
  57. package/dist/components/org/OrgSelector.d.ts +51 -0
  58. package/dist/components/org/OrgSwitcher.d.ts +47 -0
  59. package/dist/components/org/icons.d.ts +8 -0
  60. package/dist/components/sessions/SessionList.d.ts +33 -0
  61. package/dist/components/shared/ErrorBoundary.d.ts +38 -0
  62. package/dist/components/shared/ErrorMessage.d.ts +14 -0
  63. package/dist/components/shared/LoadingSpinner.d.ts +16 -0
  64. package/dist/components/solana/SolanaLoginButton.d.ts +49 -0
  65. package/dist/components/templates/FullPageLayout.d.ts +40 -0
  66. package/dist/components/templates/SplitPageLayout.d.ts +44 -0
  67. package/dist/components/templates/index.d.ts +4 -0
  68. package/dist/components/totp/OtpInput.d.ts +32 -0
  69. package/dist/components/totp/QrCode.d.ts +21 -0
  70. package/dist/components/totp/TotpSettings.d.ts +38 -0
  71. package/dist/components/totp/TotpSetup.d.ts +23 -0
  72. package/dist/components/totp/TotpVerify.d.ts +25 -0
  73. package/dist/components/totp/index.d.ts +10 -0
  74. package/dist/components/wallet/CapabilityWarning.d.ts +11 -0
  75. package/dist/components/wallet/PasskeyPrompt.d.ts +34 -0
  76. package/dist/components/wallet/RecoveryPhraseDisplay.d.ts +18 -0
  77. package/dist/components/wallet/RecoveryPhraseInput.d.ts +21 -0
  78. package/dist/components/wallet/WalletAddressRow.d.ts +10 -0
  79. package/dist/components/wallet/WalletEnrollment.d.ts +15 -0
  80. package/dist/components/wallet/WalletManager.d.ts +9 -0
  81. package/dist/components/wallet/WalletRecovery.d.ts +19 -0
  82. package/dist/components/wallet/WalletStatus.d.ts +28 -0
  83. package/dist/components/wallet/WalletUnlock.d.ts +23 -0
  84. package/dist/components/wallet/index.d.ts +23 -0
  85. package/dist/components/webauthn/PasskeyLoginButton.d.ts +8 -0
  86. package/dist/context/CedrosLoginContext.d.ts +24 -0
  87. package/dist/context/CedrosLoginProvider.d.ts +17 -0
  88. package/dist/context/EmbeddedWalletExposure.d.ts +19 -0
  89. package/dist/context/useCedrosLogin.d.ts +12 -0
  90. package/dist/crypto/aesGcm.d.ts +89 -0
  91. package/dist/crypto/argon2.d.ts +65 -0
  92. package/dist/crypto/argon2Worker.d.ts +1 -0
  93. package/dist/crypto/argon2WorkerClient.d.ts +28 -0
  94. package/dist/crypto/bip39.d.ts +106 -0
  95. package/dist/crypto/capabilities.d.ts +35 -0
  96. package/dist/crypto/entropy.d.ts +56 -0
  97. package/dist/crypto/hkdf.d.ts +38 -0
  98. package/dist/crypto/index.d.ts +30 -0
  99. package/dist/crypto/secureWipe.d.ts +90 -0
  100. package/dist/crypto/shamir.d.ts +52 -0
  101. package/dist/crypto/solanaKeypair.d.ts +63 -0
  102. package/dist/crypto/types.d.ts +134 -0
  103. package/dist/crypto/webauthnPrf.d.ts +118 -0
  104. package/dist/email-only.cjs +1 -0
  105. package/dist/email-only.cjs.map +1 -0
  106. package/dist/email-only.d.ts +16 -0
  107. package/dist/email-only.js +15 -0
  108. package/dist/email-only.js.map +1 -0
  109. package/dist/google-only.cjs +1 -0
  110. package/dist/google-only.cjs.map +1 -0
  111. package/dist/google-only.d.ts +13 -0
  112. package/dist/google-only.js +11 -0
  113. package/dist/google-only.js.map +1 -0
  114. package/dist/hooks/useAdminDeposits.d.ts +10 -0
  115. package/dist/hooks/useAdminUsers.d.ts +28 -0
  116. package/dist/hooks/useAppleAuth.d.ts +52 -0
  117. package/dist/hooks/useAuth.d.ts +34 -0
  118. package/dist/hooks/useAuthSession.d.ts +19 -0
  119. package/dist/hooks/useAuthorize.d.ts +62 -0
  120. package/dist/hooks/useCredits.d.ts +11 -0
  121. package/dist/hooks/useDeposit.d.ts +16 -0
  122. package/dist/hooks/useEmailAuth.d.ts +60 -0
  123. package/dist/hooks/useGoogleAuth.d.ts +67 -0
  124. package/dist/hooks/useInstantLink.d.ts +42 -0
  125. package/dist/hooks/useInvites.d.ts +57 -0
  126. package/dist/hooks/useMembers.d.ts +52 -0
  127. package/dist/hooks/useOrgs.d.ts +49 -0
  128. package/dist/hooks/usePasswordReset.d.ts +32 -0
  129. package/dist/hooks/usePendingRecovery.d.ts +34 -0
  130. package/dist/hooks/useRateLimiter.d.ts +58 -0
  131. package/dist/hooks/useSessions.d.ts +45 -0
  132. package/dist/hooks/useSolanaAuth.d.ts +30 -0
  133. package/dist/hooks/useSystemSettings.d.ts +47 -0
  134. package/dist/hooks/useThemeManager.d.ts +11 -0
  135. package/dist/hooks/useTotp.d.ts +52 -0
  136. package/dist/hooks/useTotpVerify.d.ts +38 -0
  137. package/dist/hooks/useTransactionSigning.d.ts +45 -0
  138. package/dist/hooks/useWallet.d.ts +10 -0
  139. package/dist/hooks/useWalletDiscovery.d.ts +24 -0
  140. package/dist/hooks/useWalletEnrollment.d.ts +9 -0
  141. package/dist/hooks/useWalletMaterial.d.ts +10 -0
  142. package/dist/hooks/useWalletRecovery.d.ts +9 -0
  143. package/dist/hooks/useWalletSigning.d.ts +31 -0
  144. package/dist/hooks/useWebAuthn.d.ts +25 -0
  145. package/dist/i18n/I18nProvider.d.ts +16 -0
  146. package/dist/i18n/context.d.ts +6 -0
  147. package/dist/i18n/index.d.ts +5 -0
  148. package/dist/i18n/translations.d.ts +66 -0
  149. package/dist/i18n/useI18n.d.ts +9 -0
  150. package/dist/index.cjs +2061 -0
  151. package/dist/index.cjs.map +1 -0
  152. package/dist/index.d.ts +126 -0
  153. package/dist/index.js +14910 -0
  154. package/dist/index.js.map +1 -0
  155. package/dist/login-react.css +1 -0
  156. package/dist/solana-only.cjs +1 -0
  157. package/dist/solana-only.cjs.map +1 -0
  158. package/dist/solana-only.d.ts +13 -0
  159. package/dist/solana-only.js +11 -0
  160. package/dist/solana-only.js.map +1 -0
  161. package/dist/style.cjs +1 -0
  162. package/dist/style.cjs.map +1 -0
  163. package/dist/style.js +1 -0
  164. package/dist/style.js.map +1 -0
  165. package/dist/types/adminUser.d.ts +112 -0
  166. package/dist/types/auth.d.ts +122 -0
  167. package/dist/types/config.d.ts +266 -0
  168. package/dist/types/deposit.d.ts +488 -0
  169. package/dist/types/index.d.ts +11 -0
  170. package/dist/types/invite.d.ts +71 -0
  171. package/dist/types/member.d.ts +45 -0
  172. package/dist/types/org.d.ts +101 -0
  173. package/dist/types/session.d.ts +28 -0
  174. package/dist/types/systemSettings.d.ts +81 -0
  175. package/dist/types/totp.d.ts +52 -0
  176. package/dist/types/wallet.d.ts +309 -0
  177. package/dist/utils/adminUserApi.d.ts +51 -0
  178. package/dist/utils/apiClient.d.ts +78 -0
  179. package/dist/utils/cryptoShim.d.ts +17 -0
  180. package/dist/utils/csrf.d.ts +1 -0
  181. package/dist/utils/deviceDetection.d.ts +17 -0
  182. package/dist/utils/embeddedWallet.d.ts +75 -0
  183. package/dist/utils/inviteApi.d.ts +31 -0
  184. package/dist/utils/memberApi.d.ts +23 -0
  185. package/dist/utils/orgApi.d.ts +36 -0
  186. package/dist/utils/sanitization.d.ts +66 -0
  187. package/dist/utils/sessionApi.d.ts +16 -0
  188. package/dist/utils/silentWalletEnroll.d.ts +41 -0
  189. package/dist/utils/systemSettingsApi.d.ts +18 -0
  190. package/dist/utils/tabSync.d.ts +46 -0
  191. package/dist/utils/tokenManager.d.ts +107 -0
  192. package/dist/utils/unlockCredential.d.ts +5 -0
  193. package/dist/utils/validation.d.ts +48 -0
  194. package/dist/utils/walletDetection.d.ts +23 -0
  195. package/dist/utils/webauthnJson.d.ts +21 -0
  196. package/dist/validation-BeXIfuHB.cjs +1 -0
  197. package/dist/validation-BeXIfuHB.cjs.map +1 -0
  198. package/dist/validation-BebL7hMF.js +56 -0
  199. package/dist/validation-BebL7hMF.js.map +1 -0
  200. package/package.json +109 -0
@@ -0,0 +1 @@
1
+ "use strict";const u=require("react/jsx-runtime"),r=require("react"),E=require("./ErrorMessage-CntMyn93.cjs"),k={loading:!1,loaded:!1,error:null,callbacks:[],load(){return typeof window>"u"||typeof document>"u"?Promise.reject(new Error("Google Sign-In script loader cannot run in SSR")):this.loaded?Promise.resolve():this.loading?new Promise((e,d)=>{this.callbacks.push({resolve:e,reject:d})}):(this.loading=!0,new Promise((e,d)=>{this.callbacks.push({resolve:e,reject:d});const g=document.getElementById("google-gsi-script");if(g){window.google?.accounts?.id?(this.loaded=!0,this.loading=!1,this.callbacks.forEach(t=>t.resolve()),this.callbacks=[]):g.addEventListener("load",()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(t=>t.resolve()),this.callbacks=[]});return}const o=document.createElement("script");o.src="https://accounts.google.com/gsi/client",o.async=!0,o.defer=!0,o.id="google-gsi-script",o.onload=()=>{this.loaded=!0,this.loading=!1,this.callbacks.forEach(t=>t.resolve()),this.callbacks=[]},o.onerror=()=>{this.loading=!1,o.remove();const t=new Error("Failed to load Google Sign-In script");this.callbacks.forEach(f=>f.reject(t)),this.callbacks=[]},document.head.appendChild(o)}))},_reset(){this.loading=!1,this.loaded=!1,this.error=null,this.callbacks=[]}};function w(){const{config:e,_internal:d}=E.useCedrosLogin(),[g,o]=r.useState(!1),[t,f]=r.useState(!1),[b,n]=r.useState(null),c=r.useRef(null),p=r.useRef(e),m=r.useMemo(()=>new E.ApiClient({baseUrl:e.serverUrl,timeoutMs:e.requestTimeout,retryAttempts:e.retryAttempts}),[e.serverUrl,e.requestTimeout,e.retryAttempts]);r.useEffect(()=>{p.current=e},[e]);const R=r.useCallback(async s=>{const i=c.current;if(i)try{const a=await m.post("/google",{idToken:s.credential});p.current.callbacks?.onLoginSuccess?.(a.user,"google"),d?.handleLoginSuccess(a.user,a.tokens),o(!1),i.resolve(a)}catch(a){const l=E.handleApiError(a,"Google sign-in failed");n(l),o(!1),i.reject(l)}finally{c.current=null}},[m,d]);r.useEffect(()=>{if(!e.googleClientId)return;let s=!0;const i=()=>{s&&(window.google?.accounts?.id?.initialize({client_id:e.googleClientId,callback:R,auto_select:!1,cancel_on_tap_outside:!0}),s&&f(!0))};return k.load().then(()=>{s&&i()}).catch(()=>{s&&n({code:"SERVER_ERROR",message:"Failed to load Google Sign-In"})}),()=>{s=!1}},[e.googleClientId,R]);const h=r.useCallback(async()=>{if(!e.googleClientId){const s={code:"VALIDATION_ERROR",message:"Google Client ID not configured"};throw n(s),s}if(!t){const s={code:"VALIDATION_ERROR",message:"Google Sign-In not initialized"};throw n(s),s}if(c.current){const s={code:"VALIDATION_ERROR",message:"Google Sign-In already in progress"};throw n(s),s}return o(!0),n(null),new Promise((s,i)=>{c.current={resolve:s,reject:i},window.google?.accounts?.id?.prompt(a=>{if(a.isNotDisplayed()){const l={code:"SERVER_ERROR",message:"Google Sign-In popup was blocked. Please allow popups or try again."};n(l),o(!1),c.current=null,i(l)}else if(a.isSkippedMoment()){const l={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(l),o(!1),c.current=null,i(l)}else if(a.isDismissedMoment()){const l={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(l),o(!1),c.current=null,i(l)}})})},[e.googleClientId,t]),I=r.useCallback(()=>n(null),[]);return{signIn:h,isLoading:g,isInitialized:t,error:b,clearError:I}}function S({onSuccess:e,onError:d,className:g="",variant:o="default",size:t="md",disabled:f=!1}){const{signIn:b,isLoading:n,isInitialized:c}=w(),p=async()=>{try{await b(),e?.()}catch(h){const I=h instanceof Error?h:new Error(String(h));d?.(I)}},m={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},R={default:"cedros-button-google",outline:"cedros-button-google-outline"};return u.jsxs("button",{type:"button",className:`cedros-button ${R[o]} ${m[t]} ${g}`,onClick:p,disabled:f||!c||n,"aria-label":"Sign in with Google",children:[n?u.jsx(E.LoadingSpinner,{size:"sm"}):u.jsxs("svg",{className:"cedros-button-icon",width:"18",height:"18",viewBox:"0 0 18 18",fill:"none","aria-hidden":"true",children:[u.jsx("path",{d:"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z",fill:"#4285F4"}),u.jsx("path",{d:"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z",fill:"#34A853"}),u.jsx("path",{d:"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z",fill:"#FBBC05"}),u.jsx("path",{d:"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z",fill:"#EA4335"})]}),u.jsx("span",{children:"Continue with Google"})]})}exports.GoogleLoginButton=S;exports.useGoogleAuth=w;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleLoginButton-CJNJ-THo.cjs","sources":["../src/hooks/useGoogleAuth.ts","../src/components/google/GoogleLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthResponse, AuthError } from '../types';\n\n/**\n * Module-level singleton for Google script loading (P-01)\n *\n * Prevents race conditions when multiple components mount simultaneously.\n * Uses a promise queue pattern to ensure the script is only loaded once.\n *\n * ## SSR Limitations (F-08)\n *\n * This singleton persists across the module lifecycle. In SSR environments:\n * - Module state persists between requests (potential cross-request leakage)\n * - Google Sign-In requires browser APIs (document, window)\n * - The hook guards against SSR with `googleClientId` checks\n *\n * For SSR frameworks (Next.js, Remix), ensure this hook is only used\n * in client-side components.\n *\n * ## Test Isolation\n *\n * For test isolation, call `scriptLoader._reset()` in test setup/teardown.\n *\n * @internal\n */\nconst scriptLoader = {\n loading: false,\n loaded: false,\n error: null as Error | null,\n callbacks: [] as Array<{ resolve: () => void; reject: (err: Error) => void }>,\n\n load(): Promise<void> {\n // SSR guard: avoid touching browser globals when running in Node/SSR.\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return Promise.reject(new Error('Google Sign-In script loader cannot run in SSR'));\n }\n\n // Already loaded\n if (this.loaded) {\n return Promise.resolve();\n }\n\n // Loading in progress - queue callback\n if (this.loading) {\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n });\n }\n\n // Start loading\n this.loading = true;\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n\n // Check if script already exists (from previous session or SSR)\n const existingScript = document.getElementById('google-gsi-script');\n if (existingScript) {\n if (window.google?.accounts?.id) {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n } else {\n existingScript.addEventListener('load', () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n });\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.id = 'google-gsi-script';\n\n script.onload = () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n };\n\n script.onerror = () => {\n this.loading = false;\n // M-02: Remove failed script from DOM to allow retry on next load() call.\n // Without removal, retry finds existingScript and waits for a load event\n // that will never fire on an already-failed script.\n script.remove();\n const error = new Error('Failed to load Google Sign-In script');\n this.callbacks.forEach((cb) => cb.reject(error));\n this.callbacks = [];\n };\n\n document.head.appendChild(script);\n });\n },\n\n /**\n * Reset singleton state for test isolation (F-08)\n * @internal - Only use in test setup/teardown\n */\n _reset(): void {\n this.loading = false;\n this.loaded = false;\n this.error = null;\n this.callbacks = [];\n },\n};\n\n/** @internal */\nexport const _internalGoogleScriptLoader = scriptLoader;\n\nexport interface UseGoogleAuthReturn {\n signIn: () => Promise<AuthResponse>;\n isLoading: boolean;\n isInitialized: boolean;\n error: AuthError | null;\n clearError: () => void;\n}\n\ninterface PromiseCallbacks {\n resolve: (value: AuthResponse) => void;\n reject: (error: AuthError) => void;\n}\n\n/**\n * Hook for Google OAuth authentication.\n *\n * @example\n * ```tsx\n * function GoogleButton() {\n * const { signIn, isLoading, isInitialized, error } = useGoogleAuth();\n *\n * return (\n * <button onClick={signIn} disabled={!isInitialized || isLoading}>\n * {isLoading ? 'Signing in...' : 'Sign in with Google'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useGoogleAuth(): UseGoogleAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isInitialized, setIsInitialized] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n // Keep config ref in sync\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n // Handle credential response from Google\n const handleCredentialResponse = useCallback(\n async (response: { credential: string }) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n idToken: response.credential,\n });\n configRef.current.callbacks?.onLoginSuccess?.(data.user, 'google');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n setIsLoading(false);\n callbacks.resolve(data);\n } catch (err) {\n const authError = handleApiError(err, 'Google sign-in failed');\n setError(authError);\n setIsLoading(false);\n callbacks.reject(authError);\n } finally {\n promiseCallbacksRef.current = null;\n }\n },\n [apiClient, _internal]\n );\n\n // P-01: Initialize Google Sign-In SDK using singleton loader\n useEffect(() => {\n // Early return if Google auth is not enabled\n if (!config.googleClientId) {\n return;\n }\n\n // Track mounted state to prevent state updates after unmount\n let isMounted = true;\n\n const initializeGoogleSignIn = () => {\n if (!isMounted) return;\n\n window.google?.accounts?.id?.initialize({\n client_id: config.googleClientId!,\n callback: handleCredentialResponse,\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n\n if (isMounted) {\n setIsInitialized(true);\n }\n };\n\n // Use singleton loader to handle script loading\n scriptLoader\n .load()\n .then(() => {\n if (isMounted) {\n initializeGoogleSignIn();\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Failed to load Google Sign-In',\n });\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [config.googleClientId, handleCredentialResponse]);\n\n const signIn = useCallback(async (): Promise<AuthResponse> => {\n if (!config.googleClientId) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Client ID not configured',\n };\n setError(err);\n throw err;\n }\n\n if (!isInitialized) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Sign-In not initialized',\n };\n setError(err);\n throw err;\n }\n\n if (promiseCallbacksRef.current) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Sign-In already in progress',\n };\n setError(err);\n throw err;\n }\n\n setIsLoading(true);\n setError(null);\n\n return new Promise<AuthResponse>((resolve, reject) => {\n promiseCallbacksRef.current = { resolve, reject };\n\n // Show Google One Tap prompt\n window.google?.accounts?.id?.prompt((notification) => {\n if (notification.isNotDisplayed()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In popup was blocked. Please allow popups or try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isSkippedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isDismissedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n }\n });\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n };\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n id?: {\n initialize: (config: {\n client_id: string;\n callback: (response: { credential: string }) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: (\n callback: (notification: {\n isNotDisplayed: () => boolean;\n isSkippedMoment: () => boolean;\n isDismissedMoment: () => boolean;\n getMomentType: () => string;\n }) => void\n ) => void;\n renderButton: (element: HTMLElement, config: object) => void;\n disableAutoSelect: () => void;\n };\n };\n };\n }\n}\n","import { useGoogleAuth } from '../../hooks/useGoogleAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface GoogleLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n}\n\n/**\n * Google OAuth login button\n */\nexport function GoogleLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn();\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-google',\n outline: 'cedros-button-google-outline',\n };\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || !isInitialized || isLoading}\n aria-label=\"Sign in with Google\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z\"\n fill=\"#EA4335\"\n />\n </svg>\n )}\n <span>Continue with Google</span>\n </button>\n );\n}\n"],"names":["scriptLoader","resolve","reject","existingScript","cb","script","error","useGoogleAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","isInitialized","setIsInitialized","setError","promiseCallbacksRef","useRef","configRef","apiClient","useMemo","ApiClient","useEffect","handleCredentialResponse","useCallback","response","callbacks","data","err","authError","handleApiError","isMounted","initializeGoogleSignIn","signIn","notification","clearError","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","variantClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
@@ -0,0 +1,227 @@
1
+ import { jsxs as w, jsx as u } from "react/jsx-runtime";
2
+ import { useState as b, useRef as k, useMemo as C, useEffect as S, useCallback as I } from "react";
3
+ import { u as A, A as y, h as v, L as G } from "./ErrorMessage-Bm1j5mBT.js";
4
+ const L = {
5
+ loading: !1,
6
+ loaded: !1,
7
+ error: null,
8
+ callbacks: [],
9
+ load() {
10
+ return typeof window > "u" || typeof document > "u" ? Promise.reject(new Error("Google Sign-In script loader cannot run in SSR")) : this.loaded ? Promise.resolve() : this.loading ? new Promise((e, c) => {
11
+ this.callbacks.push({ resolve: e, reject: c });
12
+ }) : (this.loading = !0, new Promise((e, c) => {
13
+ this.callbacks.push({ resolve: e, reject: c });
14
+ const d = document.getElementById("google-gsi-script");
15
+ if (d) {
16
+ window.google?.accounts?.id ? (this.loaded = !0, this.loading = !1, this.callbacks.forEach((t) => t.resolve()), this.callbacks = []) : d.addEventListener("load", () => {
17
+ this.loaded = !0, this.loading = !1, this.callbacks.forEach((t) => t.resolve()), this.callbacks = [];
18
+ });
19
+ return;
20
+ }
21
+ const s = document.createElement("script");
22
+ s.src = "https://accounts.google.com/gsi/client", s.async = !0, s.defer = !0, s.id = "google-gsi-script", s.onload = () => {
23
+ this.loaded = !0, this.loading = !1, this.callbacks.forEach((t) => t.resolve()), this.callbacks = [];
24
+ }, s.onerror = () => {
25
+ this.loading = !1, s.remove();
26
+ const t = new Error("Failed to load Google Sign-In script");
27
+ this.callbacks.forEach((g) => g.reject(t)), this.callbacks = [];
28
+ }, document.head.appendChild(s);
29
+ }));
30
+ },
31
+ /**
32
+ * Reset singleton state for test isolation (F-08)
33
+ * @internal - Only use in test setup/teardown
34
+ */
35
+ _reset() {
36
+ this.loading = !1, this.loaded = !1, this.error = null, this.callbacks = [];
37
+ }
38
+ };
39
+ function _() {
40
+ const { config: e, _internal: c } = A(), [d, s] = b(!1), [t, g] = b(!1), [m, n] = b(null), a = k(null), h = k(e), p = C(
41
+ () => new y({
42
+ baseUrl: e.serverUrl,
43
+ timeoutMs: e.requestTimeout,
44
+ retryAttempts: e.retryAttempts
45
+ }),
46
+ [e.serverUrl, e.requestTimeout, e.retryAttempts]
47
+ );
48
+ S(() => {
49
+ h.current = e;
50
+ }, [e]);
51
+ const R = I(
52
+ async (o) => {
53
+ const r = a.current;
54
+ if (r)
55
+ try {
56
+ const i = await p.post("/google", {
57
+ idToken: o.credential
58
+ });
59
+ h.current.callbacks?.onLoginSuccess?.(i.user, "google"), c?.handleLoginSuccess(i.user, i.tokens), s(!1), r.resolve(i);
60
+ } catch (i) {
61
+ const l = v(i, "Google sign-in failed");
62
+ n(l), s(!1), r.reject(l);
63
+ } finally {
64
+ a.current = null;
65
+ }
66
+ },
67
+ [p, c]
68
+ );
69
+ S(() => {
70
+ if (!e.googleClientId)
71
+ return;
72
+ let o = !0;
73
+ const r = () => {
74
+ o && (window.google?.accounts?.id?.initialize({
75
+ client_id: e.googleClientId,
76
+ callback: R,
77
+ auto_select: !1,
78
+ cancel_on_tap_outside: !0
79
+ }), o && g(!0));
80
+ };
81
+ return L.load().then(() => {
82
+ o && r();
83
+ }).catch(() => {
84
+ o && n({
85
+ code: "SERVER_ERROR",
86
+ message: "Failed to load Google Sign-In"
87
+ });
88
+ }), () => {
89
+ o = !1;
90
+ };
91
+ }, [e.googleClientId, R]);
92
+ const f = I(async () => {
93
+ if (!e.googleClientId) {
94
+ const o = {
95
+ code: "VALIDATION_ERROR",
96
+ message: "Google Client ID not configured"
97
+ };
98
+ throw n(o), o;
99
+ }
100
+ if (!t) {
101
+ const o = {
102
+ code: "VALIDATION_ERROR",
103
+ message: "Google Sign-In not initialized"
104
+ };
105
+ throw n(o), o;
106
+ }
107
+ if (a.current) {
108
+ const o = {
109
+ code: "VALIDATION_ERROR",
110
+ message: "Google Sign-In already in progress"
111
+ };
112
+ throw n(o), o;
113
+ }
114
+ return s(!0), n(null), new Promise((o, r) => {
115
+ a.current = { resolve: o, reject: r }, window.google?.accounts?.id?.prompt((i) => {
116
+ if (i.isNotDisplayed()) {
117
+ const l = {
118
+ code: "SERVER_ERROR",
119
+ message: "Google Sign-In popup was blocked. Please allow popups or try again."
120
+ };
121
+ n(l), s(!1), a.current = null, r(l);
122
+ } else if (i.isSkippedMoment()) {
123
+ const l = {
124
+ code: "SERVER_ERROR",
125
+ message: "Google Sign-In was cancelled"
126
+ };
127
+ n(l), s(!1), a.current = null, r(l);
128
+ } else if (i.isDismissedMoment()) {
129
+ const l = {
130
+ code: "SERVER_ERROR",
131
+ message: "Google Sign-In was cancelled"
132
+ };
133
+ n(l), s(!1), a.current = null, r(l);
134
+ }
135
+ });
136
+ });
137
+ }, [e.googleClientId, t]), E = I(() => n(null), []);
138
+ return {
139
+ signIn: f,
140
+ isLoading: d,
141
+ isInitialized: t,
142
+ error: m,
143
+ clearError: E
144
+ };
145
+ }
146
+ function V({
147
+ onSuccess: e,
148
+ onError: c,
149
+ className: d = "",
150
+ variant: s = "default",
151
+ size: t = "md",
152
+ disabled: g = !1
153
+ }) {
154
+ const { signIn: m, isLoading: n, isInitialized: a } = _(), h = async () => {
155
+ try {
156
+ await m(), e?.();
157
+ } catch (f) {
158
+ const E = f instanceof Error ? f : new Error(String(f));
159
+ c?.(E);
160
+ }
161
+ }, p = {
162
+ sm: "cedros-button-sm",
163
+ md: "cedros-button-md",
164
+ lg: "cedros-button-lg"
165
+ };
166
+ return /* @__PURE__ */ w(
167
+ "button",
168
+ {
169
+ type: "button",
170
+ className: `cedros-button ${{
171
+ default: "cedros-button-google",
172
+ outline: "cedros-button-google-outline"
173
+ }[s]} ${p[t]} ${d}`,
174
+ onClick: h,
175
+ disabled: g || !a || n,
176
+ "aria-label": "Sign in with Google",
177
+ children: [
178
+ n ? /* @__PURE__ */ u(G, { size: "sm" }) : /* @__PURE__ */ w(
179
+ "svg",
180
+ {
181
+ className: "cedros-button-icon",
182
+ width: "18",
183
+ height: "18",
184
+ viewBox: "0 0 18 18",
185
+ fill: "none",
186
+ "aria-hidden": "true",
187
+ children: [
188
+ /* @__PURE__ */ u(
189
+ "path",
190
+ {
191
+ d: "M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z",
192
+ fill: "#4285F4"
193
+ }
194
+ ),
195
+ /* @__PURE__ */ u(
196
+ "path",
197
+ {
198
+ d: "M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z",
199
+ fill: "#34A853"
200
+ }
201
+ ),
202
+ /* @__PURE__ */ u(
203
+ "path",
204
+ {
205
+ d: "M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z",
206
+ fill: "#FBBC05"
207
+ }
208
+ ),
209
+ /* @__PURE__ */ u(
210
+ "path",
211
+ {
212
+ d: "M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z",
213
+ fill: "#EA4335"
214
+ }
215
+ )
216
+ ]
217
+ }
218
+ ),
219
+ /* @__PURE__ */ u("span", { children: "Continue with Google" })
220
+ ]
221
+ }
222
+ );
223
+ }
224
+ export {
225
+ V as G,
226
+ _ as u
227
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GoogleLoginButton-CvDoOc-0.js","sources":["../src/hooks/useGoogleAuth.ts","../src/components/google/GoogleLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport type { AuthResponse, AuthError } from '../types';\n\n/**\n * Module-level singleton for Google script loading (P-01)\n *\n * Prevents race conditions when multiple components mount simultaneously.\n * Uses a promise queue pattern to ensure the script is only loaded once.\n *\n * ## SSR Limitations (F-08)\n *\n * This singleton persists across the module lifecycle. In SSR environments:\n * - Module state persists between requests (potential cross-request leakage)\n * - Google Sign-In requires browser APIs (document, window)\n * - The hook guards against SSR with `googleClientId` checks\n *\n * For SSR frameworks (Next.js, Remix), ensure this hook is only used\n * in client-side components.\n *\n * ## Test Isolation\n *\n * For test isolation, call `scriptLoader._reset()` in test setup/teardown.\n *\n * @internal\n */\nconst scriptLoader = {\n loading: false,\n loaded: false,\n error: null as Error | null,\n callbacks: [] as Array<{ resolve: () => void; reject: (err: Error) => void }>,\n\n load(): Promise<void> {\n // SSR guard: avoid touching browser globals when running in Node/SSR.\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return Promise.reject(new Error('Google Sign-In script loader cannot run in SSR'));\n }\n\n // Already loaded\n if (this.loaded) {\n return Promise.resolve();\n }\n\n // Loading in progress - queue callback\n if (this.loading) {\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n });\n }\n\n // Start loading\n this.loading = true;\n return new Promise((resolve, reject) => {\n this.callbacks.push({ resolve, reject });\n\n // Check if script already exists (from previous session or SSR)\n const existingScript = document.getElementById('google-gsi-script');\n if (existingScript) {\n if (window.google?.accounts?.id) {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n } else {\n existingScript.addEventListener('load', () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n });\n }\n return;\n }\n\n const script = document.createElement('script');\n script.src = 'https://accounts.google.com/gsi/client';\n script.async = true;\n script.defer = true;\n script.id = 'google-gsi-script';\n\n script.onload = () => {\n this.loaded = true;\n this.loading = false;\n this.callbacks.forEach((cb) => cb.resolve());\n this.callbacks = [];\n };\n\n script.onerror = () => {\n this.loading = false;\n // M-02: Remove failed script from DOM to allow retry on next load() call.\n // Without removal, retry finds existingScript and waits for a load event\n // that will never fire on an already-failed script.\n script.remove();\n const error = new Error('Failed to load Google Sign-In script');\n this.callbacks.forEach((cb) => cb.reject(error));\n this.callbacks = [];\n };\n\n document.head.appendChild(script);\n });\n },\n\n /**\n * Reset singleton state for test isolation (F-08)\n * @internal - Only use in test setup/teardown\n */\n _reset(): void {\n this.loading = false;\n this.loaded = false;\n this.error = null;\n this.callbacks = [];\n },\n};\n\n/** @internal */\nexport const _internalGoogleScriptLoader = scriptLoader;\n\nexport interface UseGoogleAuthReturn {\n signIn: () => Promise<AuthResponse>;\n isLoading: boolean;\n isInitialized: boolean;\n error: AuthError | null;\n clearError: () => void;\n}\n\ninterface PromiseCallbacks {\n resolve: (value: AuthResponse) => void;\n reject: (error: AuthError) => void;\n}\n\n/**\n * Hook for Google OAuth authentication.\n *\n * @example\n * ```tsx\n * function GoogleButton() {\n * const { signIn, isLoading, isInitialized, error } = useGoogleAuth();\n *\n * return (\n * <button onClick={signIn} disabled={!isInitialized || isLoading}>\n * {isLoading ? 'Signing in...' : 'Sign in with Google'}\n * </button>\n * );\n * }\n * ```\n */\nexport function useGoogleAuth(): UseGoogleAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [isInitialized, setIsInitialized] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const promiseCallbacksRef = useRef<PromiseCallbacks | null>(null);\n const configRef = useRef(config);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n // Keep config ref in sync\n useEffect(() => {\n configRef.current = config;\n }, [config]);\n\n // Handle credential response from Google\n const handleCredentialResponse = useCallback(\n async (response: { credential: string }) => {\n const callbacks = promiseCallbacksRef.current;\n if (!callbacks) return;\n\n try {\n const data = await apiClient.post<AuthResponse>('/google', {\n idToken: response.credential,\n });\n configRef.current.callbacks?.onLoginSuccess?.(data.user, 'google');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n setIsLoading(false);\n callbacks.resolve(data);\n } catch (err) {\n const authError = handleApiError(err, 'Google sign-in failed');\n setError(authError);\n setIsLoading(false);\n callbacks.reject(authError);\n } finally {\n promiseCallbacksRef.current = null;\n }\n },\n [apiClient, _internal]\n );\n\n // P-01: Initialize Google Sign-In SDK using singleton loader\n useEffect(() => {\n // Early return if Google auth is not enabled\n if (!config.googleClientId) {\n return;\n }\n\n // Track mounted state to prevent state updates after unmount\n let isMounted = true;\n\n const initializeGoogleSignIn = () => {\n if (!isMounted) return;\n\n window.google?.accounts?.id?.initialize({\n client_id: config.googleClientId!,\n callback: handleCredentialResponse,\n auto_select: false,\n cancel_on_tap_outside: true,\n });\n\n if (isMounted) {\n setIsInitialized(true);\n }\n };\n\n // Use singleton loader to handle script loading\n scriptLoader\n .load()\n .then(() => {\n if (isMounted) {\n initializeGoogleSignIn();\n }\n })\n .catch(() => {\n if (isMounted) {\n setError({\n code: 'SERVER_ERROR',\n message: 'Failed to load Google Sign-In',\n });\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [config.googleClientId, handleCredentialResponse]);\n\n const signIn = useCallback(async (): Promise<AuthResponse> => {\n if (!config.googleClientId) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Client ID not configured',\n };\n setError(err);\n throw err;\n }\n\n if (!isInitialized) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Sign-In not initialized',\n };\n setError(err);\n throw err;\n }\n\n if (promiseCallbacksRef.current) {\n const err: AuthError = {\n code: 'VALIDATION_ERROR',\n message: 'Google Sign-In already in progress',\n };\n setError(err);\n throw err;\n }\n\n setIsLoading(true);\n setError(null);\n\n return new Promise<AuthResponse>((resolve, reject) => {\n promiseCallbacksRef.current = { resolve, reject };\n\n // Show Google One Tap prompt\n window.google?.accounts?.id?.prompt((notification) => {\n if (notification.isNotDisplayed()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In popup was blocked. Please allow popups or try again.',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isSkippedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n } else if (notification.isDismissedMoment()) {\n const err: AuthError = {\n code: 'SERVER_ERROR',\n message: 'Google Sign-In was cancelled',\n };\n setError(err);\n setIsLoading(false);\n promiseCallbacksRef.current = null;\n reject(err);\n }\n });\n });\n }, [config.googleClientId, isInitialized]);\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n signIn,\n isLoading,\n isInitialized,\n error,\n clearError,\n };\n}\n\n// Type declaration for Google Identity Services\ndeclare global {\n interface Window {\n google?: {\n accounts?: {\n id?: {\n initialize: (config: {\n client_id: string;\n callback: (response: { credential: string }) => void;\n auto_select?: boolean;\n cancel_on_tap_outside?: boolean;\n }) => void;\n prompt: (\n callback: (notification: {\n isNotDisplayed: () => boolean;\n isSkippedMoment: () => boolean;\n isDismissedMoment: () => boolean;\n getMomentType: () => string;\n }) => void\n ) => void;\n renderButton: (element: HTMLElement, config: object) => void;\n disableAutoSelect: () => void;\n };\n };\n };\n }\n}\n","import { useGoogleAuth } from '../../hooks/useGoogleAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\n\nexport interface GoogleLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n}\n\n/**\n * Google OAuth login button\n */\nexport function GoogleLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n}: GoogleLoginButtonProps) {\n const { signIn, isLoading, isInitialized } = useGoogleAuth();\n\n const handleClick = async () => {\n try {\n await signIn();\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n }\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-google',\n outline: 'cedros-button-google-outline',\n };\n\n return (\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || !isInitialized || isLoading}\n aria-label=\"Sign in with Google\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M9.003 18c2.43 0 4.467-.806 5.956-2.18l-2.909-2.26c-.806.54-1.836.86-3.047.86-2.344 0-4.328-1.584-5.036-3.711H.96v2.332A8.997 8.997 0 0 0 9.003 18z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M3.964 10.712A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.96A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.96 4.042l3.004-2.33z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M9.003 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.464.891 11.428 0 9.002 0A8.997 8.997 0 0 0 .96 4.958l3.005 2.332c.708-2.127 2.692-3.71 5.036-3.71z\"\n fill=\"#EA4335\"\n />\n </svg>\n )}\n <span>Continue with Google</span>\n </button>\n );\n}\n"],"names":["scriptLoader","resolve","reject","existingScript","cb","script","error","useGoogleAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","isInitialized","setIsInitialized","setError","promiseCallbacksRef","useRef","configRef","apiClient","useMemo","ApiClient","useEffect","handleCredentialResponse","useCallback","response","callbacks","data","err","authError","handleApiError","isMounted","initializeGoogleSignIn","signIn","notification","clearError","GoogleLoginButton","onSuccess","onError","className","variant","size","disabled","handleClick","sizeClasses","jsxs","jsx","LoadingSpinner"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ "use strict";const t=require("react/jsx-runtime"),l=require("react"),S=require("./ErrorMessage-CntMyn93.cjs"),P=require("./validation-BeXIfuHB.cjs");function q(){const{config:a,_internal:n}=S.useCedrosLogin(),[u,f]=l.useState(!1),[C,r]=l.useState(null),g=l.useMemo(()=>new S.ApiClient({baseUrl:a.serverUrl,timeoutMs:a.requestTimeout,retryAttempts:a.retryAttempts}),[a.serverUrl,a.requestTimeout,a.retryAttempts]),s=l.useCallback(async h=>{if(!P.validateSolanaPublicKey(h)){const c={code:"INVALID_PUBLIC_KEY",message:"Invalid Solana public key format"};throw r(c),c}f(!0),r(null);try{return await g.post("/solana/challenge",{publicKey:h},{credentials:"omit"})}catch(c){const i=S.handleApiError(c,"Failed to get challenge");throw r(i),i}finally{f(!1)}},[g]),y=l.useCallback(async(h,c,i)=>{if(!P.validateSolanaPublicKey(h)){const o={code:"INVALID_PUBLIC_KEY",message:"Invalid Solana public key format"};throw r(o),o}f(!0),r(null);try{const o=await g.post("/solana",{publicKey:h,signature:c,message:i});return a.callbacks?.onLoginSuccess?.(o.user,"solana"),n?.handleLoginSuccess(o.user,o.tokens),o}catch(o){const d=S.handleApiError(o,"Solana sign-in failed");throw r(d),d}finally{f(!1)}},[g,a.callbacks,n]),L=l.useCallback(()=>r(null),[]);return{requestChallenge:s,signIn:y,isLoading:u,error:C,clearError:L}}const D=["phantom","solflare","backpack","glow","slope","sollet","coin98","clover","mathWallet","ledger","torus","walletconnect"];function B(a){if(!a||typeof a!="object")return!1;const n=a;return typeof n.connect=="function"||typeof n.signMessage=="function"||typeof n.signTransaction=="function"||"isConnected"in n}function T(){if(typeof window>"u")return!1;const a=window;for(const n of D){const u=a[n];if(u&&typeof u=="object"&&"solana"in u&&B(u.solana))return!0}return!!B(a.solana)}function F({onSuccess:a,onError:n,className:u="",variant:f="default",size:C="md",disabled:r=!1,hideIfNoWallet:g=!0,walletContext:s}){const{requestChallenge:y,signIn:L,isLoading:h}=q(),[c,i]=l.useState(!1),[o,d]=l.useState(!1),[W,j]=l.useState(!1),k=l.useRef(!1),[U]=l.useState(()=>T()),m=s?.connected??!1,v=s?.connecting??!1,p=s?.publicKey,b=s?.signMessage,x=s?.wallet,w=(s?.wallets??[]).filter(e=>e.adapter.readyState==="Installed"||e.adapter.readyState==="Loadable"),_=s?w.length>0:U,I=l.useCallback(async()=>{if(!k.current){if(!p||!b){n?.(new Error("Wallet not ready"));return}k.current=!0;try{const e=p.toBase58(),E=await y(e),V=new TextEncoder().encode(E.message),N=await b(V);if(!(N instanceof Uint8Array)||N.length===0)throw new Error("Wallet returned invalid signature");let M;try{M=btoa(String.fromCharCode(...N))}catch{throw new Error("Failed to encode signature")}await L(e,M,E.message),a?.()}catch(e){const E=e instanceof Error?e:new Error(String(e));n?.(E)}finally{k.current=!1,d(!1)}}},[p,b,y,L,a,n]);if(l.useEffect(()=>{W&&x&&!m&&!v&&s?.connect&&(j(!1),s.connect().catch(e=>{n?.(e instanceof Error?e:new Error(String(e))),d(!1)}))},[W,x,m,v,s,n]),l.useEffect(()=>{o&&m&&p&&b&&!k.current&&I().catch(()=>{})},[o,m,p,b,I]),g&&!_)return null;const z=async()=>{r||h||v||(m&&p&&b?(d(!0),await I()):x?(d(!0),j(!0)):w.length===1?(s?.select(w[0].adapter.name),d(!0),j(!0)):w.length>1?i(!0):n?.(new Error("No Solana wallet found. Please install Phantom or another Solana wallet.")))},K=e=>{i(!1),s?.select(e),d(!0),j(!0)},R={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},H={default:"cedros-button-solana",outline:"cedros-button-solana-outline"},A=h||v||o&&!m;return t.jsxs(t.Fragment,{children:[t.jsxs("button",{type:"button",className:`cedros-button ${H[f]} ${R[C]} ${u}`,onClick:z,disabled:r||A,"aria-label":"Continue with Solana",children:[A?t.jsx(S.LoadingSpinner,{size:"sm"}):t.jsxs("svg",{className:"cedros-button-icon",width:"18",height:"18",viewBox:"0 0 128 128",fill:"currentColor","aria-hidden":"true",children:[t.jsx("path",{d:"M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z"}),t.jsx("path",{d:"M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z"}),t.jsx("path",{d:"M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z"})]}),t.jsx("span",{children:"Continue with Solana"})]}),c&&t.jsx("div",{className:"cedros-modal-backdrop",onClick:()=>i(!1),role:"presentation",children:t.jsxs("div",{className:"cedros-modal cedros-wallet-selector",role:"dialog","aria-modal":"true","aria-labelledby":"wallet-selector-title",onClick:e=>e.stopPropagation(),children:[t.jsxs("div",{className:"cedros-modal-header",children:[t.jsx("h2",{id:"wallet-selector-title",className:"cedros-modal-title",children:"Select Wallet"}),t.jsx("button",{type:"button",className:"cedros-modal-close",onClick:()=>i(!1),"aria-label":"Close",children:t.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none","aria-hidden":"true",children:t.jsx("path",{d:"M18 6L6 18M6 6l12 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"})})})]}),t.jsx("div",{className:"cedros-modal-content",children:t.jsx("div",{className:"cedros-wallet-list",children:w.map(e=>t.jsxs("button",{type:"button",className:"cedros-wallet-option",onClick:()=>K(e.adapter.name),children:[t.jsx("img",{src:e.adapter.icon,alt:"",width:"32",height:"32",className:"cedros-wallet-icon"}),t.jsx("span",{children:e.adapter.name})]},e.adapter.name))})})]})})]})}exports.SolanaLoginButton=F;exports.detectSolanaWallets=T;exports.useSolanaAuth=q;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SolanaLoginButton-BlSgPW50.cjs","sources":["../src/hooks/useSolanaAuth.ts","../src/utils/walletDetection.ts","../src/components/solana/SolanaLoginButton.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useCedrosLogin } from '../context/useCedrosLogin';\nimport { ApiClient, handleApiError } from '../utils/apiClient';\nimport { validateSolanaPublicKey } from '../utils/validation';\nimport type { AuthResponse, AuthError, ChallengeResponse } from '../types';\n\nexport interface UseSolanaAuthReturn {\n requestChallenge: (publicKey: string) => Promise<ChallengeResponse>;\n signIn: (publicKey: string, signature: string, message: string) => Promise<AuthResponse>;\n isLoading: boolean;\n error: AuthError | null;\n clearError: () => void;\n}\n\n/**\n * Hook for Solana wallet authentication.\n *\n * @example\n * ```tsx\n * function SolanaLogin() {\n * const { requestChallenge, signIn, isLoading } = useSolanaAuth();\n * const { publicKey, signMessage } = useWallet();\n *\n * const handleLogin = async () => {\n * const challenge = await requestChallenge(publicKey.toBase58());\n * const signature = await signMessage(new TextEncoder().encode(challenge.message));\n * const result = await signIn(\n * publicKey.toBase58(),\n * Buffer.from(signature).toString('base64'),\n * challenge.message\n * );\n * };\n * }\n * ```\n */\nexport function useSolanaAuth(): UseSolanaAuthReturn {\n const { config, _internal } = useCedrosLogin();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<AuthError | null>(null);\n\n const apiClient = useMemo(\n () =>\n new ApiClient({\n baseUrl: config.serverUrl,\n timeoutMs: config.requestTimeout,\n retryAttempts: config.retryAttempts,\n }),\n [config.serverUrl, config.requestTimeout, config.retryAttempts]\n );\n\n const requestChallenge = useCallback(\n async (publicKey: string): Promise<ChallengeResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<ChallengeResponse>(\n '/solana/challenge',\n { publicKey },\n { credentials: 'omit' }\n );\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Failed to get challenge');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient]\n );\n\n const signIn = useCallback(\n async (publicKey: string, signature: string, message: string): Promise<AuthResponse> => {\n // Validate public key format before making API call\n if (!validateSolanaPublicKey(publicKey)) {\n const authError: AuthError = {\n code: 'INVALID_PUBLIC_KEY',\n message: 'Invalid Solana public key format',\n };\n setError(authError);\n throw authError;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const data = await apiClient.post<AuthResponse>('/solana', {\n publicKey,\n signature,\n message,\n });\n config.callbacks?.onLoginSuccess?.(data.user, 'solana');\n _internal?.handleLoginSuccess(data.user, data.tokens);\n return data;\n } catch (err) {\n const authError = handleApiError(err, 'Solana sign-in failed');\n setError(authError);\n throw authError;\n } finally {\n setIsLoading(false);\n }\n },\n [apiClient, config.callbacks, _internal]\n );\n\n const clearError = useCallback(() => setError(null), []);\n\n return {\n requestChallenge,\n signIn,\n isLoading,\n error,\n clearError,\n };\n}\n","/**\n * Wallet detection utilities for Solana browser wallets\n */\n\n/**\n * Type for window with potential Solana wallet extensions\n */\ninterface WindowWithWallets extends Window {\n phantom?: { solana?: unknown };\n solflare?: { solana?: unknown };\n backpack?: { solana?: unknown };\n glow?: { solana?: unknown };\n slope?: { solana?: unknown };\n sollet?: { solana?: unknown };\n coin98?: { solana?: unknown };\n clover?: { solana?: unknown };\n mathWallet?: { solana?: unknown };\n ledger?: { solana?: unknown };\n torus?: { solana?: unknown };\n walletconnect?: { solana?: unknown };\n solana?: unknown;\n}\n\n/**\n * Known Solana wallet provider names to check for on the window object\n */\ntype WalletProviderName = Exclude<keyof WindowWithWallets, keyof Window>;\n\nconst WALLET_PROVIDERS: WalletProviderName[] = [\n 'phantom',\n 'solflare',\n 'backpack',\n 'glow',\n 'slope',\n 'sollet',\n 'coin98',\n 'clover',\n 'mathWallet',\n 'ledger',\n 'torus',\n 'walletconnect',\n];\n\n/**\n * UI-9 FIX: Validates that a wallet provider object has expected Solana wallet methods.\n * Prevents spoofed wallet objects from being accepted.\n */\nfunction isValidSolanaProvider(provider: unknown): boolean {\n if (!provider || typeof provider !== 'object') return false;\n const wallet = provider as Record<string, unknown>;\n // Check for at least one expected wallet method/property\n // Real wallets have connect, signMessage, signTransaction, etc.\n return (\n typeof wallet.connect === 'function' ||\n typeof wallet.signMessage === 'function' ||\n typeof wallet.signTransaction === 'function' ||\n 'isConnected' in wallet\n );\n}\n\n/**\n * Detects if any Solana wallet extensions are installed in the browser.\n * Checks for common wallet adapters like Phantom, Solflare, Backpack, etc.\n *\n * @returns true if at least one Solana wallet is detected\n *\n * @example\n * ```tsx\n * if (detectSolanaWallets()) {\n * // Show Solana login button\n * }\n * ```\n */\nexport function detectSolanaWallets(): boolean {\n if (typeof window === 'undefined') {\n return false;\n }\n\n const win = window as WindowWithWallets;\n\n // Check window object for wallet injections\n // UI-9: Validate wallet has expected methods to reject spoofed providers\n for (const provider of WALLET_PROVIDERS) {\n const walletObj = win[provider];\n if (\n walletObj &&\n typeof walletObj === 'object' &&\n 'solana' in walletObj &&\n isValidSolanaProvider(walletObj.solana)\n ) {\n return true;\n }\n }\n\n // Check for generic Solana provider (e.g., from some mobile wallet browsers)\n // UI-9: Also validate generic provider\n if (isValidSolanaProvider(win.solana)) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Returns list of detected Solana wallet names (for debugging/display)\n *\n * @returns Array of detected wallet provider names\n */\nexport function getDetectedWalletNames(): string[] {\n if (typeof window === 'undefined') {\n return [];\n }\n\n const win = window as WindowWithWallets;\n const detected: string[] = [];\n\n // UI-9: Use validation to reject spoofed providers\n for (const provider of WALLET_PROVIDERS) {\n const walletObj = win[provider];\n if (\n walletObj &&\n typeof walletObj === 'object' &&\n 'solana' in walletObj &&\n isValidSolanaProvider(walletObj.solana)\n ) {\n detected.push(provider);\n }\n }\n\n if (isValidSolanaProvider(win.solana) && detected.length === 0) {\n detected.push('solana');\n }\n\n return detected;\n}\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport { useSolanaAuth } from '../../hooks/useSolanaAuth';\nimport { LoadingSpinner } from '../shared/LoadingSpinner';\nimport { detectSolanaWallets } from '../../utils/walletDetection';\n\nexport interface SolanaLoginButtonProps {\n onSuccess?: () => void;\n onError?: (error: Error) => void;\n className?: string;\n variant?: 'default' | 'outline';\n size?: 'sm' | 'md' | 'lg';\n disabled?: boolean;\n /**\n * Hide the button if no Solana wallets are detected.\n * When true (default), button renders nothing if no wallets are installed.\n * When false, button always renders (useful for showing \"install wallet\" prompts).\n * @default true\n */\n hideIfNoWallet?: boolean;\n /**\n * Solana wallet adapter context. Pass this from @solana/wallet-adapter-react's useWallet().\n * The button will handle connection and signing automatically for a one-click experience.\n */\n walletContext?: {\n publicKey: { toBase58: () => string } | null;\n signMessage: ((message: Uint8Array) => Promise<Uint8Array>) | null;\n connected: boolean;\n connecting: boolean;\n connect: () => Promise<void>;\n wallet: { adapter: { name: string } } | null;\n select: (walletName: string) => void;\n wallets: Array<{\n adapter: {\n name: string;\n icon: string;\n readyState: string;\n };\n }>;\n };\n}\n\n/**\n * Solana wallet login button with one-click authentication.\n *\n * Handles wallet connection and message signing automatically.\n * If wallet is already connected, signs immediately.\n * If not connected, connects first then auto-signs.\n */\nexport function SolanaLoginButton({\n onSuccess,\n onError,\n className = '',\n variant = 'default',\n size = 'md',\n disabled = false,\n hideIfNoWallet = true,\n walletContext,\n}: SolanaLoginButtonProps) {\n const { requestChallenge, signIn, isLoading: isAuthLoading } = useSolanaAuth();\n const [showWalletSelector, setShowWalletSelector] = useState(false);\n const [pendingLogin, setPendingLogin] = useState(false);\n const [triggerConnect, setTriggerConnect] = useState(false);\n const isProcessingRef = useRef(false);\n\n // Detect wallets from browser (used when walletContext not provided)\n // Use lazy initializer to avoid re-detecting on every render\n const [browserHasWallets] = useState(() => detectSolanaWallets());\n\n const connected = walletContext?.connected ?? false;\n const connecting = walletContext?.connecting ?? false;\n const publicKey = walletContext?.publicKey;\n const signMessage = walletContext?.signMessage;\n const wallet = walletContext?.wallet;\n const wallets = walletContext?.wallets ?? [];\n\n // Get installed/ready wallets from wallet adapter context\n const installedWallets = wallets.filter(\n (w) => w.adapter.readyState === 'Installed' || w.adapter.readyState === 'Loadable'\n );\n\n // Determine if any wallets are available\n // If walletContext is provided, use its wallet list; otherwise use browser detection\n const hasWallets = walletContext ? installedWallets.length > 0 : browserHasWallets;\n\n // Execute the actual sign-in flow\n const executeSignIn = useCallback(async () => {\n if (isProcessingRef.current) return;\n if (!publicKey || !signMessage) {\n onError?.(new Error('Wallet not ready'));\n return;\n }\n\n isProcessingRef.current = true;\n try {\n const pubKeyString = publicKey.toBase58();\n\n // Request challenge from server\n const challenge = await requestChallenge(pubKeyString);\n\n // Sign the message with wallet\n const messageBytes = new TextEncoder().encode(challenge.message);\n const signatureBytes = await signMessage(messageBytes);\n\n // Validate signature\n if (!(signatureBytes instanceof Uint8Array) || signatureBytes.length === 0) {\n throw new Error('Wallet returned invalid signature');\n }\n\n // Encode signature\n let signature: string;\n try {\n signature = btoa(String.fromCharCode(...signatureBytes));\n } catch {\n throw new Error('Failed to encode signature');\n }\n\n // Authenticate with server\n await signIn(pubKeyString, signature, challenge.message);\n\n onSuccess?.();\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n onError?.(error);\n } finally {\n isProcessingRef.current = false;\n setPendingLogin(false);\n }\n }, [publicKey, signMessage, requestChallenge, signIn, onSuccess, onError]);\n\n // Auto-connect when wallet is selected and triggerConnect is set\n useEffect(() => {\n if (triggerConnect && wallet && !connected && !connecting && walletContext?.connect) {\n setTriggerConnect(false);\n walletContext.connect().catch((err) => {\n onError?.(err instanceof Error ? err : new Error(String(err)));\n setPendingLogin(false);\n });\n }\n }, [triggerConnect, wallet, connected, connecting, walletContext, onError]);\n\n // Auto-execute sign-in when connected with pending login\n // COMP-04: Catch unhandled rejections from executeSignIn (defensive - errors already\n // handled internally, but .catch() prevents unhandledrejection events)\n useEffect(() => {\n if (pendingLogin && connected && publicKey && signMessage && !isProcessingRef.current) {\n executeSignIn().catch(() => {\n /* Errors already passed to onError callback inside executeSignIn */\n });\n }\n }, [pendingLogin, connected, publicKey, signMessage, executeSignIn]);\n\n // Hide button if no wallets detected and hideIfNoWallet is true\n // Note: This must come after all hooks to satisfy React rules of hooks\n if (hideIfNoWallet && !hasWallets) {\n return null;\n }\n\n const handleClick = async () => {\n if (disabled || isAuthLoading || connecting) return;\n\n if (connected && publicKey && signMessage) {\n // Already connected - sign immediately\n setPendingLogin(true);\n await executeSignIn();\n } else if (wallet) {\n // Wallet selected but not connected - connect and queue login\n setPendingLogin(true);\n setTriggerConnect(true);\n } else if (installedWallets.length === 1) {\n // Only one wallet installed - auto-select it\n walletContext?.select(installedWallets[0].adapter.name);\n setPendingLogin(true);\n setTriggerConnect(true);\n } else if (installedWallets.length > 1) {\n // Multiple wallets - show selector\n setShowWalletSelector(true);\n } else {\n // No wallets - show error\n onError?.(\n new Error('No Solana wallet found. Please install Phantom or another Solana wallet.')\n );\n }\n };\n\n const handleSelectWallet = (walletName: string) => {\n setShowWalletSelector(false);\n walletContext?.select(walletName);\n setPendingLogin(true);\n setTriggerConnect(true);\n };\n\n const sizeClasses = {\n sm: 'cedros-button-sm',\n md: 'cedros-button-md',\n lg: 'cedros-button-lg',\n };\n\n const variantClasses = {\n default: 'cedros-button-solana',\n outline: 'cedros-button-solana-outline',\n };\n\n const isLoading = isAuthLoading || connecting || (pendingLogin && !connected);\n\n return (\n <>\n <button\n type=\"button\"\n className={`cedros-button ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}\n onClick={handleClick}\n disabled={disabled || isLoading}\n aria-label=\"Continue with Solana\"\n >\n {isLoading ? (\n <LoadingSpinner size=\"sm\" />\n ) : (\n <svg\n className=\"cedros-button-icon\"\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 128 128\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M25.38 96.04a4.35 4.35 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7l-17.71 17.72a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7l17.71-17.72z\" />\n <path d=\"M25.38 11.81a4.47 4.47 0 0 1 3.07-1.27h91.68c1.93 0 2.9 2.34 1.54 3.7L103.96 31.96a4.35 4.35 0 0 1-3.07 1.27H9.21c-1.93 0-2.9-2.34-1.54-3.7L25.38 11.81z\" />\n <path d=\"M102.62 53.76a4.35 4.35 0 0 0-3.07-1.27H7.87c-1.93 0-2.9 2.34-1.54 3.7l17.71 17.72a4.35 4.35 0 0 0 3.07 1.27h91.68c1.93 0 2.9-2.34 1.54-3.7L102.62 53.76z\" />\n </svg>\n )}\n <span>Continue with Solana</span>\n </button>\n\n {/* Wallet Selector Modal */}\n {showWalletSelector && (\n <div\n className=\"cedros-modal-backdrop\"\n onClick={() => setShowWalletSelector(false)}\n role=\"presentation\"\n >\n <div\n className=\"cedros-modal cedros-wallet-selector\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"wallet-selector-title\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"cedros-modal-header\">\n <h2 id=\"wallet-selector-title\" className=\"cedros-modal-title\">\n Select Wallet\n </h2>\n <button\n type=\"button\"\n className=\"cedros-modal-close\"\n onClick={() => setShowWalletSelector(false)}\n aria-label=\"Close\"\n >\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M18 6L6 18M6 6l12 12\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </div>\n <div className=\"cedros-modal-content\">\n <div className=\"cedros-wallet-list\">\n {installedWallets.map((w) => (\n <button\n key={w.adapter.name}\n type=\"button\"\n className=\"cedros-wallet-option\"\n onClick={() => handleSelectWallet(w.adapter.name)}\n >\n <img\n src={w.adapter.icon}\n alt=\"\"\n width=\"32\"\n height=\"32\"\n className=\"cedros-wallet-icon\"\n />\n <span>{w.adapter.name}</span>\n </button>\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n </>\n );\n}\n"],"names":["useSolanaAuth","config","_internal","useCedrosLogin","isLoading","setIsLoading","useState","error","setError","apiClient","useMemo","ApiClient","requestChallenge","useCallback","publicKey","validateSolanaPublicKey","authError","err","handleApiError","signIn","signature","message","data","clearError","WALLET_PROVIDERS","isValidSolanaProvider","provider","wallet","detectSolanaWallets","win","walletObj","SolanaLoginButton","onSuccess","onError","className","variant","size","disabled","hideIfNoWallet","walletContext","isAuthLoading","showWalletSelector","setShowWalletSelector","pendingLogin","setPendingLogin","triggerConnect","setTriggerConnect","isProcessingRef","useRef","browserHasWallets","connected","connecting","signMessage","installedWallets","w","hasWallets","executeSignIn","pubKeyString","challenge","messageBytes","signatureBytes","useEffect","handleClick","handleSelectWallet","walletName","sizeClasses","variantClasses","jsxs","Fragment","jsx","LoadingSpinner"],"mappings":"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"}