diff --git a/frontend/src/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.tsx b/frontend/src/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.tsx
index 29ac05c..fc1c9c6 100644
--- a/frontend/src/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.tsx
+++ b/frontend/src/app/(auth)/password-reset/confirm/PasswordResetConfirmContent.tsx
@@ -7,10 +7,23 @@
import { useSearchParams, useRouter } from 'next/navigation';
import { useEffect, useRef } from 'react';
-import { PasswordResetConfirmForm } from '@/components/auth/PasswordResetConfirmForm';
+import dynamic from 'next/dynamic';
import { Alert } from '@/components/ui/alert';
import Link from 'next/link';
+// Code-split PasswordResetConfirmForm (319 lines)
+const PasswordResetConfirmForm = dynamic(
+ () => import('@/components/auth/PasswordResetConfirmForm').then((mod) => ({ default: mod.PasswordResetConfirmForm })),
+ {
+ loading: () => (
+
+ ),
+ }
+);
+
export default function PasswordResetConfirmContent() {
const searchParams = useSearchParams();
const router = useRouter();
diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx
index eb94b04..b88d2d0 100644
--- a/frontend/src/app/(auth)/register/page.tsx
+++ b/frontend/src/app/(auth)/register/page.tsx
@@ -1,6 +1,20 @@
'use client';
-import { RegisterForm } from '@/components/auth/RegisterForm';
+import dynamic from 'next/dynamic';
+
+// Code-split RegisterForm (313 lines)
+const RegisterForm = dynamic(
+ () => import('@/components/auth/RegisterForm').then((mod) => ({ default: mod.RegisterForm })),
+ {
+ loading: () => (
+
+ ),
+ }
+);
export default function RegisterPage() {
return (
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index c431123..da42438 100755
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -24,7 +24,33 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
+
+ {/* Theme initialization script - runs before React hydrates to prevent FOUC */}
+
+
diff --git a/frontend/src/app/providers.tsx b/frontend/src/app/providers.tsx
index 60e3c0e..7a349f2 100644
--- a/frontend/src/app/providers.tsx
+++ b/frontend/src/app/providers.tsx
@@ -3,7 +3,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
-import { AuthInitializer } from '@/components/auth';
import { ThemeProvider } from '@/components/theme';
export function Providers({ children }: { children: React.ReactNode }) {
@@ -14,7 +13,8 @@ export function Providers({ children }: { children: React.ReactNode }) {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
- refetchOnWindowFocus: true,
+ refetchOnWindowFocus: false, // Disabled - use selective refetching per query
+ refetchOnReconnect: true, // Keep for session data
},
mutations: {
retry: false,
@@ -26,7 +26,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
return (
-
+ {/* AuthInitializer removed - Zustand persist middleware handles auto-hydration */}
{children}
diff --git a/frontend/src/components/auth/AuthGuard.tsx b/frontend/src/components/auth/AuthGuard.tsx
index 39d4915..6b5326a 100644
--- a/frontend/src/components/auth/AuthGuard.tsx
+++ b/frontend/src/components/auth/AuthGuard.tsx
@@ -8,7 +8,7 @@
import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
-import { useAuthStore } from '@/stores/authStore';
+import { useAuthStore } from '@/lib/stores/authStore';
import { useMe } from '@/lib/api/hooks/useAuth';
import config from '@/config/app.config';
diff --git a/frontend/src/components/auth/AuthInitializer.tsx b/frontend/src/components/auth/AuthInitializer.tsx
index 8c3dad2..81b6072 100644
--- a/frontend/src/components/auth/AuthInitializer.tsx
+++ b/frontend/src/components/auth/AuthInitializer.tsx
@@ -7,7 +7,7 @@
'use client';
import { useEffect } from 'react';
-import { useAuthStore } from '@/stores/authStore';
+import { useAuthStore } from '@/lib/stores/authStore';
/**
* AuthInitializer - Initializes auth state from encrypted storage on mount
diff --git a/frontend/src/components/auth/index.ts b/frontend/src/components/auth/index.ts
index 49d2efd..1f32bf1 100755
--- a/frontend/src/components/auth/index.ts
+++ b/frontend/src/components/auth/index.ts
@@ -1,8 +1,5 @@
// Authentication components
-// Initialization
-export { AuthInitializer } from './AuthInitializer';
-
// Route protection
export { AuthGuard } from './AuthGuard';
diff --git a/frontend/src/components/forms/FormField.tsx b/frontend/src/components/forms/FormField.tsx
new file mode 100644
index 0000000..8fc7841
--- /dev/null
+++ b/frontend/src/components/forms/FormField.tsx
@@ -0,0 +1,101 @@
+/**
+ * FormField Component
+ * Reusable form field with integrated label, input, and error display
+ * Designed for react-hook-form with proper accessibility attributes
+ */
+
+'use client';
+
+import { ComponentProps, ReactNode } from 'react';
+import { FieldError } from 'react-hook-form';
+import { Label } from '@/components/ui/label';
+import { Input } from '@/components/ui/input';
+
+export interface FormFieldProps extends Omit, 'children'> {
+ /** Field label text */
+ label: string;
+ /** Field name/id - optional if provided via register() */
+ name?: string;
+ /** Is field required? Shows asterisk if true */
+ required?: boolean;
+ /** Form error object from react-hook-form */
+ error?: FieldError;
+ /** Label description or helper text */
+ description?: string;
+ /** Additional content after input (e.g., password requirements) */
+ children?: ReactNode;
+}
+
+/**
+ * FormField - Standardized form field with label and error handling
+ *
+ * Features:
+ * - Automatic error ID generation for accessibility
+ * - Required indicator
+ * - Error message display
+ * - Helper text/description support
+ * - Full ARIA attribute support
+ *
+ * @example
+ * ```tsx
+ *
+ * ```
+ */
+export function FormField({
+ label,
+ name: explicitName,
+ required = false,
+ error,
+ description,
+ children,
+ ...inputProps
+}: FormFieldProps) {
+ // Extract name from inputProps (from register()) or use explicit name
+ // register() adds a name property that may not be in the type
+ const registerName = ('name' in inputProps) ? (inputProps as { name: string }).name : undefined;
+ const name = explicitName || registerName;
+
+ if (!name) {
+ throw new Error('FormField: name must be provided either explicitly or via register()');
+ }
+
+ const errorId = error ? `${name}-error` : undefined;
+ const descriptionId = description ? `${name}-description` : undefined;
+ const ariaDescribedBy = [errorId, descriptionId].filter(Boolean).join(' ') || undefined;
+
+ return (
+
+ {label && (
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+
+ {error && (
+
+ {error.message}
+
+ )}
+ {children}
+
+ );
+}
diff --git a/frontend/src/components/forms/index.ts b/frontend/src/components/forms/index.ts
new file mode 100644
index 0000000..775d152
--- /dev/null
+++ b/frontend/src/components/forms/index.ts
@@ -0,0 +1,5 @@
+// Shared form components and utilities
+export { FormField } from './FormField';
+export type { FormFieldProps } from './FormField';
+export { useFormError } from './useFormError';
+export type { UseFormErrorReturn } from './useFormError';
diff --git a/frontend/src/components/forms/useFormError.ts b/frontend/src/components/forms/useFormError.ts
new file mode 100644
index 0000000..e905461
--- /dev/null
+++ b/frontend/src/components/forms/useFormError.ts
@@ -0,0 +1,91 @@
+/**
+ * useFormError Hook
+ * Handles server error state and API error parsing for forms
+ * Standardizes error handling across all form components
+ */
+
+import { useState, useCallback } from 'react';
+import { UseFormReturn, FieldValues, Path } from 'react-hook-form';
+import { getGeneralError, getFieldErrors, isAPIErrorArray } from '@/lib/api/errors';
+
+export interface UseFormErrorReturn {
+ /** Current server error message */
+ serverError: string | null;
+ /** Set server error manually */
+ setServerError: (error: string | null) => void;
+ /** Handle API error and update form with field-specific errors */
+ handleFormError: (error: unknown) => void;
+ /** Clear all errors */
+ clearErrors: () => void;
+}
+
+/**
+ * useFormError - Standardized form error handling
+ *
+ * Features:
+ * - Server error state management
+ * - API error parsing with type guards
+ * - Automatic field error mapping to react-hook-form
+ * - General error message extraction
+ *
+ * @param form - react-hook-form instance
+ * @returns Error handling utilities
+ *
+ * @example
+ * ```tsx
+ * const form = useForm({...});
+ * const { serverError, handleFormError, clearErrors } = useFormError(form);
+ *
+ * const onSubmit = async (data: LoginFormData) => {
+ * try {
+ * clearErrors();
+ * await loginMutation.mutateAsync(data);
+ * } catch (error) {
+ * handleFormError(error);
+ * }
+ * };
+ * ```
+ */
+export function useFormError(
+ form: UseFormReturn
+): UseFormErrorReturn {
+ const [serverError, setServerError] = useState(null);
+
+ const handleFormError = useCallback(
+ (error: unknown) => {
+ // Handle API errors with type guard
+ if (isAPIErrorArray(error)) {
+ // Set general error message
+ const generalError = getGeneralError(error);
+ if (generalError) {
+ setServerError(generalError);
+ }
+
+ // Set field-specific errors
+ const fieldErrors = getFieldErrors(error);
+ Object.entries(fieldErrors).forEach(([field, message]) => {
+ // Check if field exists in form values to avoid setting invalid fields
+ if (field in form.getValues()) {
+ form.setError(field as Path, { message });
+ }
+ });
+ } else {
+ // Unexpected error format
+ setServerError('An unexpected error occurred. Please try again.');
+ }
+ },
+ [form]
+ );
+
+ const clearErrors = useCallback(() => {
+ setServerError(null);
+ form.clearErrors();
+ }, [form]);
+
+ return {
+ serverError,
+ setServerError,
+ handleFormError,
+ clearErrors,
+ };
+}
diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx
index 674f66b..966be53 100644
--- a/frontend/src/components/layout/Header.tsx
+++ b/frontend/src/components/layout/Header.tsx
@@ -8,7 +8,7 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import { useAuthStore } from '@/stores/authStore';
+import { useAuthStore } from '@/lib/stores/authStore';
import { useLogout } from '@/lib/api/hooks/useAuth';
import {
DropdownMenu,
diff --git a/frontend/src/lib/api/client.ts b/frontend/src/lib/api/client.ts
index e9193d7..ee54722 100644
--- a/frontend/src/lib/api/client.ts
+++ b/frontend/src/lib/api/client.ts
@@ -28,7 +28,7 @@ let refreshPromise: Promise | null = null;
* Dynamically imported to avoid circular dependencies
*/
const getAuthStore = async () => {
- const { useAuthStore } = await import('@/stores/authStore');
+ const { useAuthStore } = await import('@/lib/stores/authStore');
return useAuthStore.getState();
};
diff --git a/frontend/src/lib/api/hooks/useAuth.ts b/frontend/src/lib/api/hooks/useAuth.ts
index d1a4ae4..ca20b36 100755
--- a/frontend/src/lib/api/hooks/useAuth.ts
+++ b/frontend/src/lib/api/hooks/useAuth.ts
@@ -20,8 +20,8 @@ import {
confirmPasswordReset,
changeCurrentUserPassword,
} from '../client';
-import { useAuthStore } from '@/stores/authStore';
-import type { User } from '@/stores/authStore';
+import { useAuthStore } from '@/lib/stores/authStore';
+import type { User } from '@/lib/stores/authStore';
import { parseAPIError, getGeneralError } from '../errors';
import { isTokenWithUser } from '../types';
import config from '@/config/app.config';
diff --git a/frontend/src/stores/authStore.ts b/frontend/src/lib/stores/authStore.ts
similarity index 57%
rename from frontend/src/stores/authStore.ts
rename to frontend/src/lib/stores/authStore.ts
index b30e9aa..d4a34de 100644
--- a/frontend/src/stores/authStore.ts
+++ b/frontend/src/lib/stores/authStore.ts
@@ -1,9 +1,10 @@
/**
* Authentication Store - Zustand with secure token storage
- * Implements proper state management with validation
+ * Implements proper state management with validation and automatic persistence
*/
import { create } from 'zustand';
+import { persist, createJSONStorage } from 'zustand/middleware';
import { saveTokens, getTokens, clearTokens } from '@/lib/auth/storage';
/**
@@ -68,14 +69,62 @@ function calculateExpiry(expiresIn?: number): number {
return Date.now() + seconds * 1000;
}
-export const useAuthStore = create((set, get) => ({
- // Initial state
- user: null,
- accessToken: null,
- refreshToken: null,
- isAuthenticated: false,
- isLoading: true, // Start as loading to check stored tokens
- tokenExpiresAt: null,
+/**
+ * Custom storage adapter for Zustand persist
+ * Uses our encrypted token storage functions
+ */
+const authStorage = {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ getItem: async (_name: string): Promise => {
+ try {
+ const tokens = await getTokens();
+ if (!tokens) return null;
+
+ // Return the tokens as a JSON string that persist middleware expects
+ return JSON.stringify({
+ state: {
+ accessToken: tokens.accessToken,
+ refreshToken: tokens.refreshToken,
+ isAuthenticated: !!(tokens.accessToken && tokens.refreshToken),
+ },
+ });
+ } catch (error) {
+ console.error('Failed to load auth from storage:', error);
+ return null;
+ }
+ },
+ setItem: async (_name: string, value: string): Promise => {
+ try {
+ const parsed = JSON.parse(value);
+ const { accessToken, refreshToken } = parsed.state;
+
+ if (accessToken && refreshToken) {
+ await saveTokens({ accessToken, refreshToken });
+ }
+ } catch (error) {
+ console.error('Failed to save auth to storage:', error);
+ }
+ },
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ removeItem: async (_name: string): Promise => {
+ try {
+ await clearTokens();
+ } catch (error) {
+ console.error('Failed to clear auth from storage:', error);
+ }
+ },
+};
+
+export const useAuthStore = create()(
+ persist(
+ (set, get) => ({
+ // Initial state
+ user: null,
+ accessToken: null,
+ refreshToken: null,
+ isAuthenticated: false,
+ isLoading: false, // No longer needed - persist handles hydration
+ tokenExpiresAt: null,
// Set complete auth state (user + tokens)
setAuth: async (user, accessToken, refreshToken, expiresIn) => {
@@ -158,50 +207,49 @@ export const useAuthStore = create((set, get) => ({
});
},
- // Load auth from storage on app start
- loadAuthFromStorage: async () => {
- try {
- const tokens = await getTokens();
+ /**
+ * @deprecated No longer needed with persist middleware
+ * The persist middleware automatically hydrates tokens on store initialization
+ * Kept for backward compatibility but does nothing
+ */
+ loadAuthFromStorage: async () => {
+ // No-op: persist middleware handles this automatically
+ console.warn('loadAuthFromStorage() is deprecated and no longer necessary');
+ },
- if (tokens?.accessToken && tokens?.refreshToken) {
- // Validate token format
- if (isValidToken(tokens.accessToken) && isValidToken(tokens.refreshToken)) {
- set({
- accessToken: tokens.accessToken,
- refreshToken: tokens.refreshToken,
- isAuthenticated: true,
- isLoading: false,
- // User will be loaded separately via API call
- });
- return;
- }
- }
- } catch (error) {
- console.error('Failed to load auth from storage:', error);
+ // Check if current token is expired
+ isTokenExpired: () => {
+ const { tokenExpiresAt } = get();
+ if (!tokenExpiresAt) return true;
+ return Date.now() >= tokenExpiresAt;
+ },
+ }),
+ {
+ name: 'auth_store', // Storage key
+ storage: createJSONStorage(() => authStorage),
+ partialize: (state) => ({
+ // Only persist tokens and auth status, not user or computed values
+ accessToken: state.accessToken,
+ refreshToken: state.refreshToken,
+ isAuthenticated: state.isAuthenticated,
+ }),
+ onRehydrateStorage: () => {
+ return (state, error) => {
+ if (error) {
+ console.error('Failed to rehydrate auth store:', error);
+ }
+ };
+ },
}
-
- // No valid tokens found
- set({ isLoading: false });
- },
-
- // Check if current token is expired
- isTokenExpired: () => {
- const { tokenExpiresAt } = get();
- if (!tokenExpiresAt) return true;
- return Date.now() >= tokenExpiresAt;
- },
-}));
+ )
+);
/**
- * Initialize auth store from storage
- * Call this on app startup
- * Errors are logged but don't throw to prevent app crashes
+ * @deprecated No longer needed with persist middleware
+ * The persist middleware automatically hydrates the store on initialization
+ * Kept for backward compatibility but does nothing
*/
export async function initializeAuth(): Promise {
- try {
- await useAuthStore.getState().loadAuthFromStorage();
- } catch (error) {
- // Log error but don't throw - app should continue even if auth init fails
- console.error('Failed to initialize auth:', error);
- }
+ // No-op: persist middleware handles initialization automatically
+ console.warn('initializeAuth() is deprecated and no longer necessary');
}
diff --git a/frontend/src/stores/index.ts b/frontend/src/lib/stores/index.ts
similarity index 100%
rename from frontend/src/stores/index.ts
rename to frontend/src/lib/stores/index.ts
diff --git a/frontend/tests/components/auth/AuthGuard.test.tsx b/frontend/tests/components/auth/AuthGuard.test.tsx
index e0d554f..6031b91 100644
--- a/frontend/tests/components/auth/AuthGuard.test.tsx
+++ b/frontend/tests/components/auth/AuthGuard.test.tsx
@@ -29,7 +29,7 @@ let mockAuthState: {
user: null,
};
-jest.mock('@/stores/authStore', () => ({
+jest.mock('@/lib/stores/authStore', () => ({
useAuthStore: () => mockAuthState,
}));
diff --git a/frontend/tests/components/auth/AuthInitializer.test.tsx b/frontend/tests/components/auth/AuthInitializer.test.tsx
index 9b7a0a7..7356e7c 100644
--- a/frontend/tests/components/auth/AuthInitializer.test.tsx
+++ b/frontend/tests/components/auth/AuthInitializer.test.tsx
@@ -5,10 +5,10 @@
import { render, waitFor } from '@testing-library/react';
import { AuthInitializer } from '@/components/auth/AuthInitializer';
-import { useAuthStore } from '@/stores/authStore';
+import { useAuthStore } from '@/lib/stores/authStore';
// Mock the auth store
-jest.mock('@/stores/authStore', () => ({
+jest.mock('@/lib/stores/authStore', () => ({
useAuthStore: jest.fn(),
}));
diff --git a/frontend/tests/components/auth/LoginForm.test.tsx b/frontend/tests/components/auth/LoginForm.test.tsx
index 492a451..d2619ea 100644
--- a/frontend/tests/components/auth/LoginForm.test.tsx
+++ b/frontend/tests/components/auth/LoginForm.test.tsx
@@ -40,7 +40,7 @@ jest.mock('next/navigation', () => ({
}));
// Mock auth store
-jest.mock('@/stores/authStore', () => ({
+jest.mock('@/lib/stores/authStore', () => ({
useAuthStore: () => ({
isAuthenticated: false,
setAuth: jest.fn(),
diff --git a/frontend/tests/components/auth/RegisterForm.test.tsx b/frontend/tests/components/auth/RegisterForm.test.tsx
index a5a42d1..01617eb 100644
--- a/frontend/tests/components/auth/RegisterForm.test.tsx
+++ b/frontend/tests/components/auth/RegisterForm.test.tsx
@@ -38,7 +38,7 @@ jest.mock('next/navigation', () => ({
}),
}));
-jest.mock('@/stores/authStore', () => ({
+jest.mock('@/lib/stores/authStore', () => ({
useAuthStore: () => ({
isAuthenticated: false,
setAuth: jest.fn(),
diff --git a/frontend/tests/components/layout/Header.test.tsx b/frontend/tests/components/layout/Header.test.tsx
index 25c9af2..dda85d9 100644
--- a/frontend/tests/components/layout/Header.test.tsx
+++ b/frontend/tests/components/layout/Header.test.tsx
@@ -6,13 +6,13 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Header } from '@/components/layout/Header';
-import { useAuthStore } from '@/stores/authStore';
+import { useAuthStore } from '@/lib/stores/authStore';
import { useLogout } from '@/lib/api/hooks/useAuth';
import { usePathname } from 'next/navigation';
-import type { User } from '@/stores/authStore';
+import type { User } from '@/lib/stores/authStore';
// Mock dependencies
-jest.mock('@/stores/authStore', () => ({
+jest.mock('@/lib/stores/authStore', () => ({
useAuthStore: jest.fn(),
}));
diff --git a/frontend/tests/lib/api/hooks/useAuth.test.tsx b/frontend/tests/lib/api/hooks/useAuth.test.tsx
index 3a962ad..93360b5 100644
--- a/frontend/tests/lib/api/hooks/useAuth.test.tsx
+++ b/frontend/tests/lib/api/hooks/useAuth.test.tsx
@@ -25,7 +25,7 @@ let mockAuthState: {
refreshToken: null,
};
-jest.mock('@/stores/authStore', () => ({
+jest.mock('@/lib/stores/authStore', () => ({
useAuthStore: (selector?: (state: any) => any) => {
if (selector) {
return selector(mockAuthState);
diff --git a/frontend/tests/stores/authStore.test.ts b/frontend/tests/lib/stores/authStore.test.ts
similarity index 84%
rename from frontend/tests/stores/authStore.test.ts
rename to frontend/tests/lib/stores/authStore.test.ts
index 987c416..526de9b 100644
--- a/frontend/tests/stores/authStore.test.ts
+++ b/frontend/tests/lib/stores/authStore.test.ts
@@ -2,7 +2,7 @@
* Tests for auth store
*/
-import { useAuthStore, type User } from '@/stores/authStore';
+import { useAuthStore, type User } from '@/lib/stores/authStore';
import * as storage from '@/lib/auth/storage';
// Mock storage module
@@ -386,73 +386,41 @@ describe('Auth Store', () => {
});
});
- describe('loadAuthFromStorage', () => {
- it('should load valid tokens from storage', async () => {
- (storage.getTokens as jest.Mock).mockResolvedValue({
- accessToken: 'valid.access.token',
- refreshToken: 'valid.refresh.token',
- });
+ describe('loadAuthFromStorage (deprecated)', () => {
+ it('should log deprecation warning', async () => {
+ const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
await useAuthStore.getState().loadAuthFromStorage();
- const state = useAuthStore.getState();
- expect(state.accessToken).toBe('valid.access.token');
- expect(state.refreshToken).toBe('valid.refresh.token');
- expect(state.isAuthenticated).toBe(true);
- expect(state.isLoading).toBe(false);
- });
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ 'loadAuthFromStorage() is deprecated and no longer necessary'
+ );
- it('should handle null tokens from storage', async () => {
- (storage.getTokens as jest.Mock).mockResolvedValue(null);
-
- await useAuthStore.getState().loadAuthFromStorage();
-
- const state = useAuthStore.getState();
- expect(state.isAuthenticated).toBe(false);
- expect(state.isLoading).toBe(false);
- });
-
- it('should reject invalid token format from storage', async () => {
- (storage.getTokens as jest.Mock).mockResolvedValue({
- accessToken: 'invalid',
- refreshToken: 'valid.refresh.token',
- });
-
- await useAuthStore.getState().loadAuthFromStorage();
-
- const state = useAuthStore.getState();
- expect(state.isAuthenticated).toBe(false);
- expect(state.isLoading).toBe(false);
- });
-
- it('should handle storage errors gracefully', async () => {
- (storage.getTokens as jest.Mock).mockRejectedValue(new Error('Storage error'));
-
- await useAuthStore.getState().loadAuthFromStorage();
-
- const state = useAuthStore.getState();
- expect(state.isLoading).toBe(false);
+ consoleWarnSpy.mockRestore();
});
});
- describe('initializeAuth', () => {
- it('should call loadAuthFromStorage', async () => {
- (storage.getTokens as jest.Mock).mockResolvedValue({
- accessToken: 'valid.access.token',
- refreshToken: 'valid.refresh.token',
- });
+ describe('initializeAuth (deprecated)', () => {
+ it('should log deprecation warning', async () => {
+ const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
- const { initializeAuth } = await import('@/stores/authStore');
+ const { initializeAuth } = await import('@/lib/stores/authStore');
await initializeAuth();
- expect(storage.getTokens).toHaveBeenCalled();
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ 'initializeAuth() is deprecated and no longer necessary'
+ );
+
+ consoleWarnSpy.mockRestore();
});
- it('should not throw even if loadAuthFromStorage fails', async () => {
- (storage.getTokens as jest.Mock).mockRejectedValue(new Error('Storage error'));
+ it('should not throw', async () => {
+ const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
- const { initializeAuth } = await import('@/stores/authStore');
+ const { initializeAuth } = await import('@/lib/stores/authStore');
await expect(initializeAuth()).resolves.not.toThrow();
+
+ consoleWarnSpy.mockRestore();
});
});
});