diff --git a/app/admin/clients/page.tsx b/app/admin/clients/page.tsx index b321ab7..36fa5b6 100644 --- a/app/admin/clients/page.tsx +++ b/app/admin/clients/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, startTransition } from "react"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -34,7 +34,7 @@ export default function AdminClients() { } }, [query]); - useEffect(() => { load(); }, [load]); + useEffect(() => { startTransition(() => load()); }, [load]); const handleSearch = (e: React.FormEvent) => { e.preventDefault(); diff --git a/app/admin/commandes/page.tsx b/app/admin/commandes/page.tsx index 8d0e5f7..1a79da9 100644 --- a/app/admin/commandes/page.tsx +++ b/app/admin/commandes/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, startTransition } from "react"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -56,7 +56,7 @@ export default function AdminCommandes() { } }, [filter]); - useEffect(() => { load(); }, [load]); + useEffect(() => { startTransition(() => load()); }, [load]); const handleStatus = async (id: string, status: string) => { setUpdating(id); diff --git a/app/admin/page.tsx b/app/admin/page.tsx index f137c03..646dee9 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, startTransition } from "react"; import { Package, CalendarCheck, Clock, TrendingUp, ShoppingBag, Users, AlertTriangle, Euro } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; @@ -8,6 +8,41 @@ import { useAdmin } from "@/contexts/AdminContext"; import { useLanguage } from "@/contexts/LanguageContext"; import { getDashboardStats, DashboardStats } from "@/lib/api/admin"; +function Skeleton() { + return ( +
+ {Array.from({ length: 4 }).map((_, i) => ( + + +
+
+ + + ))} +
+ ); +} + +function StatGrid({ cards }: { cards: { label: string; value: string | number; icon: React.ElementType; color: string; bg: string }[] }) { + return ( +
+ {cards.map((s) => ( + + +
+

{s.label}

+

{s.value}

+
+
+ +
+
+
+ ))} +
+ ); +} + export default function AdminOverview() { const { reservations } = useAdmin(); const { t, locale } = useLanguage(); @@ -40,37 +75,6 @@ export default function AdminOverview() { ] : []; - const Skeleton = () => ( -
- {Array.from({ length: 4 }).map((_, i) => ( - - -
-
- - - ))} -
- ); - - const StatGrid = ({ cards }: { cards: { label: string; value: string | number; icon: React.ElementType; color: string; bg: string }[] }) => ( -
- {cards.map((s) => ( - - -
-

{s.label}

-

{s.value}

-
-
- -
-
-
- ))} -
- ); - return (
diff --git a/app/admin/planning/page.tsx b/app/admin/planning/page.tsx index 3ad6145..7c6af30 100644 --- a/app/admin/planning/page.tsx +++ b/app/admin/planning/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, startTransition } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -263,7 +263,7 @@ function CalendarTab() { .finally(() => setLoadingSlots(false)); }, [year, month]); - useEffect(() => { loadSlots(); }, [loadSlots]); + useEffect(() => { startTransition(() => loadSlots()); }, [loadSlots]); const prevMonth = () => { if (month === 0) { setYear((y) => y - 1); setMonth(11); } diff --git a/app/admin/services/page.tsx b/app/admin/services/page.tsx index fd3a482..d32e754 100644 --- a/app/admin/services/page.tsx +++ b/app/admin/services/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, startTransition } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -54,7 +54,7 @@ export default function AdminServices() { } }; - useEffect(() => { load(); }, []); + useEffect(() => { startTransition(() => load()); }, []); const openCreate = () => { setEditing(null); diff --git a/app/boutique/page.tsx b/app/boutique/page.tsx index e930236..b23d8fc 100644 --- a/app/boutique/page.tsx +++ b/app/boutique/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { Suspense, useState, useEffect } from "react"; +import { Suspense, useState, useEffect, startTransition } from "react"; import { useLanguage } from "@/contexts/LanguageContext"; import { useSearchParams, useRouter } from "next/navigation"; import { categories } from "@/data/products"; @@ -21,7 +21,7 @@ function ShopContent() { }; useEffect(() => { - setLoading(true); + startTransition(() => setLoading(true)); listProducts({ per_page: 100, category: selectedCategory === "all" ? undefined : selectedCategory, diff --git a/app/mon-compte/page.tsx b/app/mon-compte/page.tsx index f0401c9..855f44d 100644 --- a/app/mon-compte/page.tsx +++ b/app/mon-compte/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, startTransition } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/contexts/AuthContext"; import { useLanguage } from "@/contexts/LanguageContext"; @@ -40,8 +40,10 @@ export default function MonCompte() { useEffect(() => { if (user) { - setName(user.full_name ?? ""); - setPhone(user.phone ?? ""); + startTransition(() => { + setName(user.full_name ?? ""); + setPhone(user.phone ?? ""); + }); } }, [user]); diff --git a/app/produit/[id]/page.tsx b/app/produit/[id]/page.tsx index 7a4b3cf..e4b771b 100644 --- a/app/produit/[id]/page.tsx +++ b/app/produit/[id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, startTransition } from "react"; import { Star, ChevronLeft, Check } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useLanguage } from "@/contexts/LanguageContext"; @@ -26,7 +26,7 @@ export default function ProductDetail() { useEffect(() => { if (!id) return; - setLoading(true); + startTransition(() => setLoading(true)); getProduct(id as string) .then((p) => { setProduct(p); diff --git a/app/reservation/page.tsx b/app/reservation/page.tsx index a2ba6a9..78f3fcb 100644 --- a/app/reservation/page.tsx +++ b/app/reservation/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, startTransition } from "react"; import { Calendar } from "@/components/ui/calendar"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -43,16 +43,20 @@ export default function Booking() { useEffect(() => { if (user) { - setName(user.full_name ?? ""); - setEmail(user.email ?? ""); - setPhone(user.phone ?? ""); + startTransition(() => { + setName(user.full_name ?? ""); + setEmail(user.email ?? ""); + setPhone(user.phone ?? ""); + }); } }, [user]); useEffect(() => { if (!selectedDate) return; - setSlotsLoading(true); - setSelectedSlot(null); + startTransition(() => { + setSlotsLoading(true); + setSelectedSlot(null); + }); const dateStr = toDateStr(selectedDate); getAvailableSlots(dateStr, dateStr) .then(setSlots) diff --git a/contexts/AdminContext.tsx b/contexts/AdminContext.tsx index 25952ed..52c4c53 100644 --- a/contexts/AdminContext.tsx +++ b/contexts/AdminContext.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useContext, useState, useEffect, ReactNode, createContext } from "react"; +import React, { useContext, useState, useEffect, startTransition, ReactNode, createContext } from "react"; import { useAuth } from "@/contexts/AuthContext"; import * as productsApi from "@/lib/api/products"; import * as bookingsApi from "@/lib/api/bookings"; @@ -82,8 +82,10 @@ export const AdminProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { if (isAdmin) { - refreshProducts(); - refreshReservations(); + startTransition(() => { + refreshProducts(); + refreshReservations(); + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isAdmin]); diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx index 54a4857..688b220 100644 --- a/contexts/AuthContext.tsx +++ b/contexts/AuthContext.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { createContext, useContext, useState, useEffect, ReactNode } from "react"; +import React, { createContext, useContext, useState, useEffect, startTransition, ReactNode } from "react"; import { getToken } from "@/lib/api"; import * as authApi from "@/lib/api/auth"; @@ -24,7 +24,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { const token = getToken(); if (!token) { - setIsLoading(false); + startTransition(() => setIsLoading(false)); return; } authApi.getMe() diff --git a/hooks/use-mobile.ts b/hooks/use-mobile.ts index 2b0fe1d..cd560f3 100644 --- a/hooks/use-mobile.ts +++ b/hooks/use-mobile.ts @@ -11,7 +11,7 @@ export function useIsMobile() { setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) } mql.addEventListener("change", onChange) - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + React.startTransition(() => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)) return () => mql.removeEventListener("change", onChange) }, [])