Backend: - Express server with JWT httpOnly cookie auth - POST /api/auth/register, /api/auth/login, /api/auth/logout, GET /api/auth/me - bcrypt 12 rounds, generic 401 errors (no email/password field disclosure) - Auth middleware protects all /api/* routes except register/login - pg Pool database connection Frontend (React + Vite + TailwindCSS + shadcn/ui): - AuthContext with session restore on page load via /api/auth/me - ProtectedRoute redirects unauthenticated users to /login - LoginPage, RegisterPage — Hebrew RTL layout (dir=rtl), inline validation - DashboardPage placeholder - shadcn/ui components: Button, Input, Label, Card Database: - 9 migrations (001-009): extensions, users, events, vendors, guests, bookings, invitations, vendor_ratings, organizer_preferences - pg_trgm for fuzzy Hebrew search, GIN indexes on style_tags - Phase 2+3 fields included: source, payment_status, contract_value, vendor ratings 6-dimension, organizer preferences - Idempotent migration runner with schema_migrations tracking table Infrastructure: - Dockerfile (multi-stage: build React → production node:20-alpine) - docker-compose.yml with PostgreSQL healthcheck, expose not ports - Migrations run automatically on container start Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
display_name: string;
|
|
role: 'organizer' | 'vendor';
|
|
}
|
|
|
|
interface AuthContextValue {
|
|
user: User | null;
|
|
loading: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (email: string, password: string, displayName: string, role?: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
// Restore session on mount
|
|
useEffect(() => {
|
|
fetch('/api/auth/me', { credentials: 'include' })
|
|
.then(res => res.ok ? res.json() : null)
|
|
.then(data => {
|
|
if (data?.user) setUser(data.user);
|
|
})
|
|
.catch(() => {})
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
async function login(email: string, password: string) {
|
|
const res = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || 'התחברות נכשלה');
|
|
setUser(data.user);
|
|
}
|
|
|
|
async function register(email: string, password: string, displayName: string, role = 'organizer') {
|
|
const res = await fetch('/api/auth/register', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password, display_name: displayName, role }),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(data.error || 'הרשמה נכשלה');
|
|
setUser(data.user);
|
|
}
|
|
|
|
async function logout() {
|
|
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' });
|
|
setUser(null);
|
|
}
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
|
return ctx;
|
|
}
|