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>
119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import { useState } from 'react'
|
|
import { Link, useNavigate, useLocation } from 'react-router-dom'
|
|
import { useAuthStore } from '@/store/authStore'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
export function LoginPage() {
|
|
const navigate = useNavigate()
|
|
const location = useLocation()
|
|
const { login, isLoading, error, clearError } = useAuthStore()
|
|
|
|
const [email, setEmail] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [localError, setLocalError] = useState('')
|
|
|
|
const from = (location.state as { from?: { pathname: string } })?.from?.pathname || '/trees'
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setLocalError('')
|
|
clearError()
|
|
|
|
if (!email || !password) {
|
|
setLocalError('Please enter both email and password')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await login({ email, password })
|
|
navigate(from, { replace: true })
|
|
} catch {
|
|
// Error is set in the store
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-background px-4">
|
|
<div className="w-full max-w-md space-y-8">
|
|
<div className="text-center">
|
|
<h1 className="text-3xl font-bold tracking-tight text-foreground">Apoklisis</h1>
|
|
<p className="mt-2 text-muted-foreground">Sign in to your account</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
|
<div className="space-y-4 rounded-lg border border-border bg-card p-6 shadow-sm">
|
|
{(error || localError) && (
|
|
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
|
|
{localError || error}
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label htmlFor="email" className="block text-sm font-medium text-foreground">
|
|
Email address
|
|
</label>
|
|
<input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
autoComplete="email"
|
|
required
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
className={cn(
|
|
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
|
|
'text-foreground placeholder:text-muted-foreground',
|
|
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
|
)}
|
|
placeholder="you@example.com"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="password" className="block text-sm font-medium text-foreground">
|
|
Password
|
|
</label>
|
|
<input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
autoComplete="current-password"
|
|
required
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
className={cn(
|
|
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
|
|
'text-foreground placeholder:text-muted-foreground',
|
|
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
|
|
)}
|
|
placeholder="••••••••••"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isLoading}
|
|
className={cn(
|
|
'w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
|
|
'hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
|
|
'disabled:cursor-not-allowed disabled:opacity-50'
|
|
)}
|
|
>
|
|
{isLoading ? 'Signing in...' : 'Sign in'}
|
|
</button>
|
|
</div>
|
|
|
|
<p className="text-center text-sm text-muted-foreground">
|
|
Don't have an account?{' '}
|
|
<Link to="/register" className="font-medium text-primary hover:text-primary/90">
|
|
Register
|
|
</Link>
|
|
</p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default LoginPage
|