@cedros/login-react 0.0.17 → 0.0.18

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.
@@ -51,16 +51,16 @@ function _() {
51
51
  }, [e]);
52
52
  const R = I(
53
53
  async (o) => {
54
- const r = a.current;
55
- if (r)
54
+ const i = a.current;
55
+ if (i)
56
56
  try {
57
- const i = await p.post("/google", {
57
+ const r = await p.post("/google", {
58
58
  idToken: o.credential
59
59
  });
60
- h.current.callbacks?.onLoginSuccess?.(i.user, "google"), c?.handleLoginSuccess(i.user, i.tokens), s(!1), r.resolve(i);
61
- } catch (i) {
62
- const l = v(i, "Google sign-in failed");
63
- n(l), s(!1), r.reject(l);
60
+ h.current.callbacks?.onLoginSuccess?.(r.user, "google"), c?.handleLoginSuccess(r.user, r.tokens), s(!1), i.resolve(r);
61
+ } catch (r) {
62
+ const l = v(r, "Google sign-in failed");
63
+ n(l), s(!1), i.reject(l);
64
64
  } finally {
65
65
  a.current = null;
66
66
  }
@@ -71,7 +71,7 @@ function _() {
71
71
  if (!e.googleClientId)
72
72
  return;
73
73
  let o = !0;
74
- const r = () => {
74
+ const i = () => {
75
75
  o && (window.google?.accounts?.id?.initialize({
76
76
  client_id: e.googleClientId,
77
77
  callback: R,
@@ -80,7 +80,7 @@ function _() {
80
80
  }), o && g(!0));
81
81
  };
82
82
  return L.load().then(() => {
83
- o && r();
83
+ o && i();
84
84
  }).catch(() => {
85
85
  o && n({
86
86
  code: "SERVER_ERROR",
@@ -112,26 +112,26 @@ function _() {
112
112
  };
113
113
  throw n(o), o;
114
114
  }
115
- return s(!0), n(null), new Promise((o, r) => {
116
- a.current = { resolve: o, reject: r }, window.google?.accounts?.id?.prompt((i) => {
117
- if (i.isNotDisplayed()) {
115
+ return s(!0), n(null), new Promise((o, i) => {
116
+ a.current = { resolve: o, reject: i }, window.google?.accounts?.id?.prompt((r) => {
117
+ if (r.isNotDisplayed()) {
118
118
  const l = {
119
119
  code: "SERVER_ERROR",
120
120
  message: "Google Sign-In popup was blocked. Please allow popups or try again."
121
121
  };
122
- n(l), s(!1), a.current = null, r(l);
123
- } else if (i.isSkippedMoment()) {
122
+ n(l), s(!1), a.current = null, i(l);
123
+ } else if (r.isSkippedMoment()) {
124
124
  const l = {
125
125
  code: "SERVER_ERROR",
126
126
  message: "Google Sign-In was cancelled"
127
127
  };
128
- n(l), s(!1), a.current = null, r(l);
129
- } else if (i.isDismissedMoment()) {
128
+ n(l), s(!1), a.current = null, i(l);
129
+ } else if (r.isDismissedMoment()) {
130
130
  const l = {
131
131
  code: "SERVER_ERROR",
132
132
  message: "Google Sign-In was cancelled"
133
133
  };
134
- n(l), s(!1), a.current = null, r(l);
134
+ n(l), s(!1), a.current = null, i(l);
135
135
  }
136
136
  });
137
137
  });
@@ -169,8 +169,8 @@ function D({
169
169
  {
170
170
  type: "button",
171
171
  className: `cedros-button ${{
172
- default: "cedros-button-google",
173
- outline: "cedros-button-google-outline"
172
+ default: "cedros-button-social",
173
+ outline: "cedros-button-social-outline"
174
174
  }[s]} ${p[t]} ${d}`,
175
175
  onClick: h,
176
176
  disabled: g || !a || n,
@@ -1 +1 @@
1
- {"version":3,"file":"GoogleLoginButton-CXwp4LsQ.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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"}
1
+ {"version":3,"file":"GoogleLoginButton-CwKEUISj.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-social',\n outline: 'cedros-button-social-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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"}
@@ -1 +1 @@
1
- "use strict";const u=require("react/jsx-runtime"),i=require("react"),I=require("./useCedrosLogin-C9MrcZvh.cjs"),S=require("./LoadingSpinner-d6sSxgQN.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}=I.useCedrosLogin(),[g,o]=i.useState(!1),[t,f]=i.useState(!1),[E,n]=i.useState(null),c=i.useRef(null),p=i.useRef(e),m=i.useMemo(()=>new I.ApiClient({baseUrl:e.serverUrl,timeoutMs:e.requestTimeout,retryAttempts:e.retryAttempts}),[e.serverUrl,e.requestTimeout,e.retryAttempts]);i.useEffect(()=>{p.current=e},[e]);const R=i.useCallback(async s=>{const r=c.current;if(r)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),r.resolve(a)}catch(a){const l=I.handleApiError(a,"Google sign-in failed");n(l),o(!1),r.reject(l)}finally{c.current=null}},[m,d]);i.useEffect(()=>{if(!e.googleClientId)return;let s=!0;const r=()=>{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&&r()}).catch(()=>{s&&n({code:"SERVER_ERROR",message:"Failed to load Google Sign-In"})}),()=>{s=!1}},[e.googleClientId,R]);const h=i.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,r)=>{c.current={resolve:s,reject:r},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,r(l)}else if(a.isSkippedMoment()){const l={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(l),o(!1),c.current=null,r(l)}else if(a.isDismissedMoment()){const l={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(l),o(!1),c.current=null,r(l)}})})},[e.googleClientId,t]),b=i.useCallback(()=>n(null),[]);return{signIn:h,isLoading:g,isInitialized:t,error:E,clearError:b}}function C({onSuccess:e,onError:d,className:g="",variant:o="default",size:t="md",disabled:f=!1}){const{signIn:E,isLoading:n,isInitialized:c}=w(),p=async()=>{try{await E(),e?.()}catch(h){const b=h instanceof Error?h:new Error(String(h));d?.(b)}},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(S.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=C;exports.useGoogleAuth=w;
1
+ "use strict";const u=require("react/jsx-runtime"),i=require("react"),I=require("./useCedrosLogin-C9MrcZvh.cjs"),S=require("./LoadingSpinner-d6sSxgQN.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}=I.useCedrosLogin(),[g,o]=i.useState(!1),[t,f]=i.useState(!1),[E,n]=i.useState(null),c=i.useRef(null),p=i.useRef(e),m=i.useMemo(()=>new I.ApiClient({baseUrl:e.serverUrl,timeoutMs:e.requestTimeout,retryAttempts:e.retryAttempts}),[e.serverUrl,e.requestTimeout,e.retryAttempts]);i.useEffect(()=>{p.current=e},[e]);const R=i.useCallback(async s=>{const r=c.current;if(r)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),r.resolve(a)}catch(a){const l=I.handleApiError(a,"Google sign-in failed");n(l),o(!1),r.reject(l)}finally{c.current=null}},[m,d]);i.useEffect(()=>{if(!e.googleClientId)return;let s=!0;const r=()=>{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&&r()}).catch(()=>{s&&n({code:"SERVER_ERROR",message:"Failed to load Google Sign-In"})}),()=>{s=!1}},[e.googleClientId,R]);const h=i.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,r)=>{c.current={resolve:s,reject:r},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,r(l)}else if(a.isSkippedMoment()){const l={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(l),o(!1),c.current=null,r(l)}else if(a.isDismissedMoment()){const l={code:"SERVER_ERROR",message:"Google Sign-In was cancelled"};n(l),o(!1),c.current=null,r(l)}})})},[e.googleClientId,t]),b=i.useCallback(()=>n(null),[]);return{signIn:h,isLoading:g,isInitialized:t,error:E,clearError:b}}function C({onSuccess:e,onError:d,className:g="",variant:o="default",size:t="md",disabled:f=!1}){const{signIn:E,isLoading:n,isInitialized:c}=w(),p=async()=>{try{await E(),e?.()}catch(h){const b=h instanceof Error?h:new Error(String(h));d?.(b)}},m={sm:"cedros-button-sm",md:"cedros-button-md",lg:"cedros-button-lg"},R={default:"cedros-button-social",outline:"cedros-button-social-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(S.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=C;exports.useGoogleAuth=w;
@@ -1 +1 @@
1
- {"version":3,"file":"GoogleLoginButton-zS_69-KV.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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"}
1
+ {"version":3,"file":"GoogleLoginButton-oM5fMYPB.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-social',\n outline: 'cedros-button-social-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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"}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./useAuth-X6Ds6WW4.cjs"),r=require("./useCedrosLogin-C9MrcZvh.cjs"),o=require("./GoogleLoginButton-zS_69-KV.cjs"),n=require("./LoadingSpinner-d6sSxgQN.cjs"),s=require("./ErrorMessage-CHbYbVi2.cjs");exports.CedrosLoginProvider=e.CedrosLoginProvider;exports.useAuth=e.useAuth;exports.useCedrosLogin=r.useCedrosLogin;exports.GoogleLoginButton=o.GoogleLoginButton;exports.useGoogleAuth=o.useGoogleAuth;exports.LoadingSpinner=n.LoadingSpinner;exports.ErrorMessage=s.ErrorMessage;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./useAuth-X6Ds6WW4.cjs"),r=require("./useCedrosLogin-C9MrcZvh.cjs"),o=require("./GoogleLoginButton-oM5fMYPB.cjs"),n=require("./LoadingSpinner-d6sSxgQN.cjs"),s=require("./ErrorMessage-CHbYbVi2.cjs");exports.CedrosLoginProvider=e.CedrosLoginProvider;exports.useAuth=e.useAuth;exports.useCedrosLogin=r.useCedrosLogin;exports.GoogleLoginButton=o.GoogleLoginButton;exports.useGoogleAuth=o.useGoogleAuth;exports.LoadingSpinner=n.LoadingSpinner;exports.ErrorMessage=s.ErrorMessage;
@@ -1,6 +1,6 @@
1
1
  import { C as e, u as s } from "./useAuth-m5Hf89v8.js";
2
2
  import { u as t } from "./useCedrosLogin-_94MmGGq.js";
3
- import { G as g, u as n } from "./GoogleLoginButton-CXwp4LsQ.js";
3
+ import { G as g, u as n } from "./GoogleLoginButton-CwKEUISj.js";
4
4
  import { L as p } from "./LoadingSpinner-6vml-zwr.js";
5
5
  import { E as m } from "./ErrorMessage-CcEK0pYO.js";
6
6
  export {