From b32a70cd0e1bf4ae6e50aa4356ed8b09f56b3119 Mon Sep 17 00:00:00 2001 From: Rustico77 Date: Tue, 5 May 2026 21:48:23 +0000 Subject: [PATCH] only front-end init --- app/a-propos/page.tsx | 63 + app/admin/layout.tsx | 56 + app/admin/login/page.tsx | 67 + app/admin/page.tsx | 79 + app/admin/produits/page.tsx | 277 ++ app/admin/reservations/page.tsx | 162 + app/boutique/page.tsx | 58 + app/connexion/page.tsx | 61 + app/contact/page.tsx | 82 + app/globals.css | 138 +- app/layout.tsx | 66 +- app/page.tsx | 169 +- app/panier/page.tsx | 77 + app/produit/[id]/page.tsx | 166 + app/reservation/page.tsx | 141 + components.json | 25 + components/CartDrawer.tsx | 89 + components/Footer.tsx | 198 + components/Header.tsx | 98 + components/LanguageSwitcher.tsx | 42 + components/ProductCard.tsx | 57 + components/admin/AdminSidebar.tsx | 96 + components/ui/alert-dialog.tsx | 199 + components/ui/badge.tsx | 49 + components/ui/button.tsx | 67 + components/ui/calendar.tsx | 222 ++ components/ui/card.tsx | 103 + components/ui/command.tsx | 195 + components/ui/dialog.tsx | 168 + components/ui/dropdown-menu.tsx | 269 ++ components/ui/input-group.tsx | 156 + components/ui/input.tsx | 19 + components/ui/label.tsx | 24 + components/ui/popover.tsx | 89 + components/ui/select.tsx | 192 + components/ui/separator.tsx | 28 + components/ui/sheet.tsx | 147 + components/ui/sidebar.tsx | 702 ++++ components/ui/skeleton.tsx | 13 + components/ui/sonner.tsx | 49 + components/ui/table.tsx | 116 + components/ui/tabs.tsx | 90 + components/ui/textarea.tsx | 18 + components/ui/tooltip.tsx | 57 + contexts/AdminContext.tsx | 102 + contexts/CartContext.tsx | 74 + contexts/LanguageContext.tsx | 80 + data/products.ts | 147 + data/services.ts | 36 + hooks/use-mobile.ts | 19 + lib/utils.ts | 6 + package-lock.json | 6169 ++++++++++++++++++++++++++++- package.json | 18 +- 53 files changed, 11684 insertions(+), 206 deletions(-) create mode 100644 app/a-propos/page.tsx create mode 100644 app/admin/layout.tsx create mode 100644 app/admin/login/page.tsx create mode 100644 app/admin/page.tsx create mode 100644 app/admin/produits/page.tsx create mode 100644 app/admin/reservations/page.tsx create mode 100644 app/boutique/page.tsx create mode 100644 app/connexion/page.tsx create mode 100644 app/contact/page.tsx create mode 100644 app/panier/page.tsx create mode 100644 app/produit/[id]/page.tsx create mode 100644 app/reservation/page.tsx create mode 100644 components.json create mode 100644 components/CartDrawer.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Header.tsx create mode 100644 components/LanguageSwitcher.tsx create mode 100644 components/ProductCard.tsx create mode 100644 components/admin/AdminSidebar.tsx create mode 100644 components/ui/alert-dialog.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/calendar.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/input-group.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 contexts/AdminContext.tsx create mode 100644 contexts/CartContext.tsx create mode 100644 contexts/LanguageContext.tsx create mode 100644 data/products.ts create mode 100644 data/services.ts create mode 100644 hooks/use-mobile.ts create mode 100644 lib/utils.ts diff --git a/app/a-propos/page.tsx b/app/a-propos/page.tsx new file mode 100644 index 0000000..1213563 --- /dev/null +++ b/app/a-propos/page.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useLanguage } from "@/contexts/LanguageContext"; +import { Heart, Award, Sparkles } from "lucide-react"; + +export default function About() { + const { t } = useLanguage(); + + return ( +
+ {/* Hero */} +
+
+ À propos +
+
+
+

{t("about.title")}

+
+
+ + {/* Story */} +
+

+ Passionnée par la beauté et le bien-être capillaire depuis toujours, j'ai créé BADO HAIR pour offrir à chaque femme la possibilité de sublimer sa chevelure avec des extensions de qualité exceptionnelle. +

+

+ Chaque produit est sélectionné avec soin : des cheveux 100% naturels Remy, sourcés de manière éthique, traités avec les technologies les plus avancées pour garantir douceur, brillance et longévité. +

+

+ Mon objectif est simple : vous aider à vous sentir belle et confiante, que ce soit pour un événement spécial ou au quotidien. Chaque cliente mérite une expérience personnalisée et des conseils adaptés à ses besoins. +

+
+ + {/* Values */} +
+
+
+
+ +

Passion

+

Chaque produit est choisi avec amour et expertise pour garantir votre satisfaction.

+
+
+ +

Qualité Premium

+

100% cheveux naturels Remy, sourcés éthiquement et contrôlés rigoureusement.

+
+
+ +

Expertise

+

Conseils personnalisés et pose professionnelle pour un résultat naturel.

+
+
+
+
+
+ ); +}; diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx new file mode 100644 index 0000000..1e77a43 --- /dev/null +++ b/app/admin/layout.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AdminSidebar } from "@/components/admin/AdminSidebar"; +import { useAdmin } from "@/contexts/AdminContext"; +import { usePathname, useRouter } from "next/navigation"; +import { useEffect } from "react"; + +export default function AdminLayout({ children }: { children: React.ReactNode }) { + const { isAdmin } = useAdmin(); + const router = useRouter(); + const pathname = usePathname(); + const isLoginRoute = pathname === "/admin/login"; + + // useEffect(() => { + // if (!isAdmin && !isLoginRoute) { + // router.push("/admin/login"); + // } + // }, [isAdmin, isLoginRoute, router]); + + // if (!isAdmin && !isLoginRoute) { + // return ( + //
+ //
Chargement...
+ //
+ // ); + // } + + if (isLoginRoute) { + return ( +
+
+
{children}
+
+
+ ); + } + + // Layout pour l'admin connecté (avec la sidebar) + return ( + +
+ +
+
+ +

+ Tableau de bord +

+
+
{children}
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx new file mode 100644 index 0000000..205a165 --- /dev/null +++ b/app/admin/login/page.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { useState } from "react"; +import { Lock } from "lucide-react"; +import { useAdmin } from "@/contexts/AdminContext"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { toast } from "sonner"; +import { useRouter } from "next/navigation"; + +export default function AdminLogin() { + const { login, isAdmin } = useAdmin(); + const route = useRouter(); + const [password, setPassword] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setTimeout(() => { + const ok = login(password); + setLoading(false); + if (ok) { + toast.success("Connexion réussie"); + route.push("/admin"); + } else { + toast.error("Mot de passe incorrect"); + } + }, 300); + }; + + return ( +
+ + +
+ +
+ Espace Admin + Accès réservé à la gestion du site +
+ +
+
+ + setPassword(e.target.value)} + placeholder="••••••••" + required + autoFocus + /> +

Démo : admin123

+
+ +
+
+
+
+ ); +}; diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..cedd0b8 --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { Package, CalendarCheck, Clock, TrendingUp } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { useAdmin } from "@/contexts/AdminContext"; + +export default function AdminOverview() { + const { products, reservations } = useAdmin(); + + const pending = reservations.filter((r) => r.status === "pending").length; + const confirmed = reservations.filter((r) => r.status === "confirmed").length; + const totalValue = products.reduce((sum, p) => sum + p.price, 0); + + const stats = [ + { label: "Produits", value: products.length, icon: Package, color: "text-blue-600", bg: "bg-blue-100" }, + { label: "RDV en attente", value: pending, icon: Clock, color: "text-amber-600", bg: "bg-amber-100" }, + { label: "RDV confirmés", value: confirmed, icon: CalendarCheck, color: "text-green-600", bg: "bg-green-100" }, + { label: "Valeur catalogue", value: `${totalValue} €`, icon: TrendingUp, color: "text-primary", bg: "bg-primary/10" }, + ]; + + const upcoming = [...reservations] + .filter((r) => r.status !== "cancelled") + .sort((a, b) => `${a.date}${a.time}`.localeCompare(`${b.date}${b.time}`)) + .slice(0, 5); + + return ( +
+
+

Vue d'ensemble

+

Aperçu de votre activité

+
+ +
+ {stats.map((s) => ( + + +
+

{s.label}

+

{s.value}

+
+
+ +
+
+
+ ))} +
+ + + + Prochains rendez-vous + + + {upcoming.length === 0 ? ( +

Aucun rendez-vous à venir.

+ ) : ( +
+ {upcoming.map((r) => ( +
+
+

{r.clientName}

+

{r.service}

+
+
+

{new Date(r.date).toLocaleDateString("fr-FR", { day: "2-digit", month: "short" })} à {r.time}

+ + {r.status === "confirmed" ? "Confirmé" : "En attente"} + +
+
+ ))} +
+ )} +
+
+
+ ); +}; diff --git a/app/admin/produits/page.tsx b/app/admin/produits/page.tsx new file mode 100644 index 0000000..7834c57 --- /dev/null +++ b/app/admin/produits/page.tsx @@ -0,0 +1,277 @@ +"use client"; + +import { useState } from "react"; +import { Plus, Pencil, Trash2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useAdmin } from "@/contexts/AdminContext"; +import { Product } from "@/data/products"; +import { toast } from "sonner"; + +type FormState = { + name: string; + category: Product["category"]; + price: string; + image: string; + description: string; + colors: string; + lengths: string; + isNew: boolean; + isBestseller: boolean; +}; + +const emptyForm: FormState = { + name: "", + category: "clip-in", + price: "", + image: "", + description: "", + colors: "", + lengths: "", + isNew: false, + isBestseller: false, +}; + +export default function AdminProducts() { + const { products, addProduct, updateProduct, deleteProduct } = useAdmin(); + const [dialogOpen, setDialogOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + const [form, setForm] = useState(emptyForm); + const [deleteId, setDeleteId] = useState(null); + + const openCreate = () => { + setEditingId(null); + setForm(emptyForm); + setDialogOpen(true); + }; + + const openEdit = (p: Product) => { + setEditingId(p.id); + setForm({ + name: p.name, + category: p.category, + price: String(p.price), + image: p.image, + description: p.description, + colors: p.colors.join(", "), + lengths: p.lengths.join(", "), + isNew: !!p.isNew, + isBestseller: !!p.isBestseller, + }); + setDialogOpen(true); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const price = parseFloat(form.price); + if (!form.name || isNaN(price)) { + toast.error("Nom et prix valides requis"); + return; + } + + const payload = { + name: form.name, + category: form.category, + price, + image: form.image || "https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?w=600&h=800&fit=crop", + images: [form.image || "https://images.unsplash.com/photo-1522337360788-8b13dee7a37e?w=600&h=800&fit=crop"], + colors: form.colors.split(",").map((c) => c.trim()).filter(Boolean), + lengths: form.lengths.split(",").map((l) => l.trim()).filter(Boolean), + description: form.description, + features: [], + isNew: form.isNew, + isBestseller: form.isBestseller, + rating: 5, + reviewCount: 0, + }; + + if (editingId) { + updateProduct(editingId, payload); + toast.success("Produit modifié"); + } else { + addProduct(payload); + toast.success("Produit ajouté"); + } + setDialogOpen(false); + }; + + const handleDelete = () => { + if (deleteId) { + deleteProduct(deleteId); + toast.success("Produit supprimé"); + setDeleteId(null); + } + }; + + const categoryLabels: Record = { + "clip-in": "Clip-In", + "tape-in": "Tape-In", + "ponytail": "Ponytail", + "keratin": "Kératine", + }; + + return ( +
+
+
+

Produits

+

{products.length} produit{products.length > 1 ? "s" : ""} au catalogue

+
+ +
+ + + + + + Image + Nom + Catégorie + Prix + Statut + Actions + + + + {products.map((p) => ( + + + {p.name} + + {p.name} + {categoryLabels[p.category]} + {p.price} € + + {p.isNew && Nouveau} + {p.isBestseller && Bestseller} + + + + + + + ))} + +
+
+ + + + + {editingId ? "Modifier le produit" : "Nouveau produit"} + Renseignez les informations du produit + +
+
+
+ + setForm({ ...form, name: e.target.value })} required /> +
+
+ + +
+
+ + setForm({ ...form, price: e.target.value })} required /> +
+
+ + setForm({ ...form, image: e.target.value })} placeholder="https://..." /> +
+
+ +