7ce227ddea
- migration 009 + columna deadline TEXT en cards - backend: CardPatch.HasDeadline, eventos deadline_set/deadline_cleared - KanbanCard: menu derecho con DatePicker, badge countdown con colores por ratio (azul>=50%, amarillo<50%, rojo<10%, red.9 overdue) - App.tsx: filtro "Con deadline", handleSetCardDeadline optimista, jump-to-card + highlight - CalendarView: popover por dia con seq_num + titulo, click navega a card en tablero - HistoryModal: render eventos deadline_set/deadline_cleared - .gitignore: *.log Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
import { createContext, ReactNode, useCallback, useContext, useEffect, useState } from "react";
|
|
import * as api from "./api";
|
|
import { HTTPError } from "./api";
|
|
import type { User } from "./types";
|
|
|
|
interface AuthCtx {
|
|
user: User | null;
|
|
loading: boolean;
|
|
login: (username: string, password: string) => Promise<void>;
|
|
register: (username: string, password: string, displayName: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
setUser: (u: User | null) => void;
|
|
}
|
|
|
|
const Ctx = createContext<AuthCtx | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
api
|
|
.getMe()
|
|
.then(setUser)
|
|
.catch((e) => {
|
|
if (!(e instanceof HTTPError) || e.status !== 401) {
|
|
console.warn("getMe failed", e);
|
|
}
|
|
})
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
const login = useCallback(async (username: string, password: string) => {
|
|
const u = await api.login(username, password);
|
|
setUser(u);
|
|
}, []);
|
|
|
|
const register = useCallback(async (username: string, password: string, displayName: string) => {
|
|
await api.register(username, password, displayName);
|
|
const u = await api.login(username, password);
|
|
setUser(u);
|
|
}, []);
|
|
|
|
const logout = useCallback(async () => {
|
|
await api.logout();
|
|
setUser(null);
|
|
}, []);
|
|
|
|
return <Ctx.Provider value={{ user, loading, login, register, logout, setUser }}>{children}</Ctx.Provider>;
|
|
}
|
|
|
|
export function useAuth(): AuthCtx {
|
|
const v = useContext(Ctx);
|
|
if (!v) throw new Error("useAuth: missing AuthProvider");
|
|
return v;
|
|
}
|