diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx index aa9856f..a0253e4 100644 --- a/app/admin/login/page.tsx +++ b/app/admin/login/page.tsx @@ -12,7 +12,7 @@ import { toast } from "sonner"; import { useRouter } from "next/navigation"; export default function AdminLogin() { - const { login } = useAuth(); + const { login, logout } = useAuth(); const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); @@ -24,6 +24,7 @@ export default function AdminLogin() { try { const profile = await login(email, password); if (profile.role !== "admin") { + logout(); toast.error("Accès refusé : rôle administrateur requis"); return; } diff --git a/app/admin/reservations/page.tsx b/app/admin/reservations/page.tsx index 134697f..3027447 100644 --- a/app/admin/reservations/page.tsx +++ b/app/admin/reservations/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { Check, X, Trash2, Mail, Phone } from "lucide-react"; +import { Check, X, Trash2, Mail, Phone, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -23,6 +23,7 @@ export default function AdminReservations() { const { t, locale } = useLanguage(); const [filter, setFilter] = useState<"all" | Reservation["status"]>("all"); const [deleteId, setDeleteId] = useState(null); + const [updatingId, setUpdatingId] = useState(null); const statusConfig: Record = { pending: { label: t("admin.status.pending"), variant: "secondary" }, @@ -35,20 +36,26 @@ export default function AdminReservations() { .sort((a, b) => `${b.date}${b.time}`.localeCompare(`${a.date}${a.time}`)); const handleConfirm = async (id: string) => { + setUpdatingId(id); try { await updateReservationStatus(id, "confirmed"); toast.success(t("admin.bookings.confirmed_toast")); } catch (err) { toast.error(err instanceof ApiError ? err.message : t("admin.error")); + } finally { + setUpdatingId(null); } }; const handleCancel = async (id: string) => { + setUpdatingId(id); try { await updateReservationStatus(id, "cancelled"); toast.success(t("admin.bookings.cancelled_toast")); } catch (err) { toast.error(err instanceof ApiError ? err.message : t("admin.error")); + } finally { + setUpdatingId(null); } }; @@ -135,19 +142,27 @@ export default function AdminReservations() { - {r.status !== "confirmed" && ( - + ) : ( + <> + {r.status !== "confirmed" && ( + + )} + {r.status !== "cancelled" && ( + + )} + + )} - {r.status !== "cancelled" && ( - - )} - )) diff --git a/app/connexion/page.tsx b/app/connexion/page.tsx index 7fd4d1c..1cded86 100644 --- a/app/connexion/page.tsx +++ b/app/connexion/page.tsx @@ -15,7 +15,7 @@ type Mode = "login" | "register" | "forgot"; export default function Auth() { const { t } = useLanguage(); - const { login, register, user } = useAuth(); + const { login, register, logout, user } = useAuth(); const router = useRouter(); const [mode, setMode] = useState("login"); const [email, setEmail] = useState(""); @@ -24,9 +24,7 @@ export default function Auth() { const [loading, setLoading] = useState(false); useEffect(() => { - if (user) { - router.replace(user.role === "admin" ? "/admin" : "/"); - } + if (user) router.replace("/"); }, [user, router]); const handleSubmit = async (e: React.FormEvent) => { @@ -35,13 +33,18 @@ export default function Auth() { try { if (mode === "login") { const profile = await login(email, password); + if (profile.role === "admin") { + logout(); + toast.error(t("auth.admin_not_allowed")); + return; + } toast.success(t("auth.login_success")); - router.push(profile.role === "admin" ? "/admin" : "/"); + router.push("/"); } else if (mode === "register") { const profile = await register(email, password, name); if (profile) { toast.success(t("auth.register_success")); - router.push(profile.role === "admin" ? "/admin" : "/"); + router.push("/"); } else { toast.success(t("auth.confirm_email")); router.push("/connexion"); diff --git a/app/mon-compte/page.tsx b/app/mon-compte/page.tsx index 2f1eb65..8a09df5 100644 --- a/app/mon-compte/page.tsx +++ b/app/mon-compte/page.tsx @@ -18,7 +18,7 @@ import { toast } from "sonner"; import { User, CalendarDays, ShoppingBag, X } from "lucide-react"; export default function MonCompte() { - const { user, isLoading } = useAuth(); + const { user, isLoading, refreshUser } = useAuth(); const { t, locale } = useLanguage(); const router = useRouter(); @@ -39,6 +39,12 @@ export default function MonCompte() { if (!isLoading && user?.role === "admin") router.replace("/admin"); }, [user, isLoading, router]); + // Refresh profile from server on mount to get the latest full_name + useEffect(() => { + if (user) refreshUser().catch(() => {}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { if (user) { startTransition(() => { @@ -65,6 +71,7 @@ export default function MonCompte() { setSavingProfile(true); try { await updateProfile(name, phone || null); + await refreshUser(); toast.success(t("account.profile_saved")); } catch (err) { toast.error(err instanceof ApiError ? err.message : t("auth.error")); diff --git a/contexts/AdminContext.tsx b/contexts/AdminContext.tsx index a30f828..5e7fd20 100644 --- a/contexts/AdminContext.tsx +++ b/contexts/AdminContext.tsx @@ -44,7 +44,7 @@ interface AdminContextType { deleteProduct: (id: string) => Promise; reservations: Reservation[]; reservationsLoading: boolean; - refreshReservations: () => Promise; + refreshReservations: (silent?: boolean) => Promise; updateReservationStatus: (id: string, status: "confirmed" | "cancelled") => Promise; deleteReservation: (id: string) => Promise; } @@ -69,14 +69,14 @@ export const AdminProvider = ({ children }: { children: ReactNode }) => { } }; - const refreshReservations = async () => { + const refreshReservations = async (silent = false) => { if (!isAdmin) return; - setReservationsLoading(true); + if (!silent) setReservationsLoading(true); try { const res = await bookingsApi.adminListBookings(); setReservations(res.data.map(toReservation)); } finally { - setReservationsLoading(false); + if (!silent) setReservationsLoading(false); } }; @@ -108,12 +108,11 @@ export const AdminProvider = ({ children }: { children: ReactNode }) => { const updateReservationStatus = async (id: string, status: "confirmed" | "cancelled") => { await bookingsApi.adminUpdateBookingStatus(id, status); - // Optimistic update for immediate feedback setReservations((prev) => prev.map((r) => (r.id === id ? { ...r, status } : r)) ); - // Refresh from server to ensure consistency - refreshReservations(); + // Sync with server silently — no loading flash + refreshReservations(true); }; const deleteReservation = async (id: string) => { diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx index 688b220..7afa5fb 100644 --- a/contexts/AuthContext.tsx +++ b/contexts/AuthContext.tsx @@ -13,6 +13,7 @@ interface AuthContextType { login: (email: string, password: string) => Promise; register: (email: string, password: string, name: string) => Promise; logout: () => void; + refreshUser: () => Promise; } const AuthContext = createContext(undefined); @@ -50,6 +51,11 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { setUser(null); }; + const refreshUser = async () => { + const profile = await authApi.getMe(); + setUser(profile); + }; + return ( { login, register, logout, + refreshUser, }} > {children} diff --git a/contexts/LanguageContext.tsx b/contexts/LanguageContext.tsx index 1fbbfaf..18ceae9 100644 --- a/contexts/LanguageContext.tsx +++ b/contexts/LanguageContext.tsx @@ -89,6 +89,7 @@ const translations: Record> = { "auth.forgot_send": { fr: "Envoyer le lien", de: "Link senden", en: "Send link" }, "auth.forgot_sent": { fr: "Email envoyé ! Vérifiez votre boîte mail.", de: "E-Mail gesendet! Prüfen Sie Ihren Posteingang.", en: "Email sent! Check your inbox." }, "auth.back_to_login": { fr: "Retour à la connexion", de: "Zurück zur Anmeldung", en: "Back to login" }, + "auth.admin_not_allowed": { fr: "Les administrateurs doivent utiliser l'espace admin", de: "Administratoren müssen den Admin-Bereich verwenden", en: "Administrators must use the admin login" }, // ── Booking (public page) ──────────────────────────────────────────────────── "booking.select_service": { fr: "Choisir un service", de: "Service wählen", en: "Select service" },