aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/stores/auth.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-06 18:30:04 +0900
committernsfisis <nsfisis@gmail.com>2025-12-06 18:30:04 +0900
commit3923eb2f86c304bbd90c4eae9a338f7bc21c9e90 (patch)
treeafa2a4053cc91eb8379e26a08538cfd58c1479bc /src/client/stores/auth.tsx
parente367c698e03c41c292c3dd5c07bad0a870c3ebc4 (diff)
downloadkioku-3923eb2f86c304bbd90c4eae9a338f7bc21c9e90.tar.gz
kioku-3923eb2f86c304bbd90c4eae9a338f7bc21c9e90.tar.zst
kioku-3923eb2f86c304bbd90c4eae9a338f7bc21c9e90.zip
feat(client): add auth store with React context
Implements token management via AuthProvider context that wraps the app. Provides useAuth hook for components to access auth state and actions (login, register, logout). Includes comprehensive tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'src/client/stores/auth.tsx')
-rw-r--r--src/client/stores/auth.tsx94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/client/stores/auth.tsx b/src/client/stores/auth.tsx
new file mode 100644
index 0000000..cca314a
--- /dev/null
+++ b/src/client/stores/auth.tsx
@@ -0,0 +1,94 @@
+import {
+ createContext,
+ type ReactNode,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
+import { ApiClientError, apiClient } from "../api/client";
+import type { User } from "../api/types";
+
+export interface AuthState {
+ user: User | null;
+ isAuthenticated: boolean;
+ isLoading: boolean;
+}
+
+export interface AuthActions {
+ login: (username: string, password: string) => Promise<void>;
+ register: (username: string, password: string) => Promise<void>;
+ logout: () => void;
+}
+
+export type AuthContextValue = AuthState & AuthActions;
+
+const AuthContext = createContext<AuthContextValue | null>(null);
+
+export interface AuthProviderProps {
+ children: ReactNode;
+}
+
+export function AuthProvider({ children }: AuthProviderProps) {
+ const [user, setUser] = useState<User | null>(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Check for existing auth on mount
+ useEffect(() => {
+ const tokens = apiClient.getTokens();
+ if (tokens) {
+ // We have tokens stored, but we don't have user info cached
+ // For now, just set authenticated state. User info will be fetched when needed.
+ // In a full implementation, we'd decode the JWT or call an API endpoint
+ setIsLoading(false);
+ } else {
+ setIsLoading(false);
+ }
+ }, []);
+
+ const login = useCallback(async (username: string, password: string) => {
+ const response = await apiClient.login(username, password);
+ setUser(response.user);
+ }, []);
+
+ const register = useCallback(
+ async (username: string, password: string) => {
+ await apiClient.register(username, password);
+ // After registration, log in automatically
+ await login(username, password);
+ },
+ [login],
+ );
+
+ const logout = useCallback(() => {
+ apiClient.logout();
+ setUser(null);
+ }, []);
+
+ const isAuthenticated = apiClient.isAuthenticated();
+
+ const value = useMemo<AuthContextValue>(
+ () => ({
+ user,
+ isAuthenticated,
+ isLoading,
+ login,
+ register,
+ logout,
+ }),
+ [user, isAuthenticated, isLoading, login, register, logout],
+ );
+
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
+}
+
+export function useAuth(): AuthContextValue {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error("useAuth must be used within an AuthProvider");
+ }
+ return context;
+}
+
+export { ApiClientError };