Complete Phase 2: Frontend implementation with React + TypeScript

Frontend Features:
- React 18 + Vite + TypeScript + Tailwind CSS + Zustand
- JWT authentication with automatic token refresh
- Tree library with search and category filtering
- Full tree navigation (decision/action/solution nodes)
- Session management with notes and completion
- Session history with export (Markdown/Text/HTML)
- ErrorBoundary for graceful error handling

Backend Fixes:
- CORS: Added port 5174 to allowed origins
- Sessions: Fixed JSONB datetime serialization (mode='json')

Documentation:
- Updated PROGRESS.md with Phase 2 completion
- Updated 03-DEVELOPMENT-ROADMAP.md with checked items
- Added PHASE-2.5-PERSONAL-BRANCHING.md spec

Seed Data:
- Added backend/scripts/seed_data.py with Password Reset tree

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-01-27 22:42:22 -05:00
parent 7d96807fb1
commit cd10ecd42c
51 changed files with 9014 additions and 111 deletions

View File

@@ -0,0 +1,101 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { User, Token, UserCreate, UserLogin } from '@/types'
import { authApi } from '@/api'
interface AuthState {
user: User | null
token: Token | null
isAuthenticated: boolean
isLoading: boolean
error: string | null
// Actions
login: (credentials: UserLogin) => Promise<void>
register: (data: UserCreate) => Promise<void>
logout: () => Promise<void>
fetchUser: () => Promise<void>
clearError: () => void
setLoading: (loading: boolean) => void
}
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
login: async (credentials: UserLogin) => {
set({ isLoading: true, error: null })
try {
const token = await authApi.login(credentials)
// Store tokens
localStorage.setItem('access_token', token.access_token)
localStorage.setItem('refresh_token', token.refresh_token)
set({ token, isAuthenticated: true })
// Fetch user info
await get().fetchUser()
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Login failed'
set({ error: message, isLoading: false })
throw error
}
},
register: async (data: UserCreate) => {
set({ isLoading: true, error: null })
try {
await authApi.register(data)
// After registration, log the user in
await get().login({ email: data.email, password: data.password })
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Registration failed'
set({ error: message, isLoading: false })
throw error
}
},
logout: async () => {
try {
await authApi.logout()
} catch {
// Ignore logout errors
} finally {
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
set({ user: null, token: null, isAuthenticated: false, error: null })
}
},
fetchUser: async () => {
set({ isLoading: true })
try {
const user = await authApi.me()
set({ user, isLoading: false })
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Failed to fetch user'
set({ error: message, isLoading: false })
throw error
}
},
clearError: () => set({ error: null }),
setLoading: (loading: boolean) => set({ isLoading: loading }),
}),
{
name: 'auth-storage',
partialize: (state) => ({
token: state.token,
isAuthenticated: state.isAuthenticated,
}),
}
)
)
export default useAuthStore