Files
shokuninmarche/client/src/contexts/AuthContext.tsx
airewit-developer c8909befb1 feat: Foundation — auth system, 9 migrations, React frontend
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>
2026-02-21 18:22:42 +00:00

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;
}