feat(ADM-018): completed feature

This commit is contained in:
chattie
2026-08-17 22:23:10 +02:00
parent cf1c69fc8b
commit d595b4871f
871 changed files with 47411 additions and 281 deletions

View File

@@ -0,0 +1,80 @@
'use client';
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
interface User {
id: string;
email: string;
role: string;
}
interface AuthContextValue {
user: User | null;
loading: boolean;
login: (email: string, password: string) => Promise<{ ok: boolean; error?: string }>;
register: (email: string, password: string) => Promise<{ ok: boolean; error?: string }>;
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);
useEffect(() => {
fetch('/api/auth/me')
.then((r) => r.json())
.then((data) => {
setUser(data.user ?? null);
})
.catch(() => setUser(null))
.finally(() => setLoading(false));
}, []);
const login = useCallback(async (email: string, password: string) => {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
credentials: 'include',
});
const data = await res.json();
if (!res.ok) {
return { ok: false, error: data.error?.message || 'Error de login' };
}
setUser(data);
return { ok: true };
}, []);
const register = useCallback(async (email: string, password: string) => {
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
credentials: 'include',
});
const data = await res.json();
if (!res.ok) {
return { ok: false, error: data.error?.message || 'Error de registro' };
}
setUser(data);
return { ok: true };
}, []);
const logout = useCallback(async () => {
await fetch('/api/auth/logout', { method: 'POST' });
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;
}

View File

@@ -0,0 +1,88 @@
'use client';
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
export interface CartItem {
variantId: string;
productId: string;
productName: string;
quantity: number;
priceCents: number;
imageUrl?: string;
}
interface CartContextValue {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (variantId: string) => void;
changeQuantity: (variantId: string, quantity: number) => void;
clearCart: () => void;
itemCount: number;
subtotalCents: number;
}
const CartContext = createContext<CartContextValue | null>(null);
export function CartProvider({ children }: { children: React.ReactNode }) {
const [items, setItems] = useState<CartItem[]>([]);
useEffect(() => {
try {
const stored = localStorage.getItem('mdv_cart');
if (stored) setItems(JSON.parse(stored));
} catch {}
}, []);
const persist = useCallback((newItems: CartItem[]) => {
setItems(newItems);
localStorage.setItem('mdv_cart', JSON.stringify(newItems));
}, []);
const addItem = useCallback((item: CartItem) => {
setItems((prev) => {
const existing = prev.find((i) => i.variantId === item.variantId);
const next = existing
? prev.map((i) => i.variantId === item.variantId ? { ...i, quantity: i.quantity + item.quantity } : i)
: [...prev, item];
localStorage.setItem('mdv_cart', JSON.stringify(next));
return next;
});
}, []);
const removeItem = useCallback((variantId: string) => {
setItems((prev) => {
const next = prev.filter((i) => i.variantId !== variantId);
localStorage.setItem('mdv_cart', JSON.stringify(next));
return next;
});
}, []);
const changeQuantity = useCallback((variantId: string, quantity: number) => {
setItems((prev) => {
const next = quantity <= 0
? prev.filter((i) => i.variantId !== variantId)
: prev.map((i) => i.variantId === variantId ? { ...i, quantity } : i);
localStorage.setItem('mdv_cart', JSON.stringify(next));
return next;
});
}, []);
const clearCart = useCallback(() => {
setItems([]);
localStorage.removeItem('mdv_cart');
}, []);
const itemCount = items.reduce((sum, i) => sum + i.quantity, 0);
const subtotalCents = items.reduce((sum, i) => sum + i.priceCents * i.quantity, 0);
return (
<CartContext.Provider value={{ items, addItem, removeItem, changeQuantity, clearCart, itemCount, subtotalCents }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
const ctx = useContext(CartContext);
if (!ctx) throw new Error('useCart must be used within CartProvider');
return ctx;
}