only front-end init

This commit is contained in:
Rustico77
2026-05-05 21:48:23 +00:00
parent ac76a80c7b
commit b32a70cd0e
53 changed files with 11684 additions and 206 deletions

102
contexts/AdminContext.tsx Normal file
View File

@@ -0,0 +1,102 @@
"use client";
import React, { useContext, useState, ReactNode, createContext } from "react";
import { Product, products } from "@/data/products";
export interface Reservation {
id: string;
clientName: string;
email: string;
phone: string;
service: string;
date: string;
time: string;
status: "pending" | "confirmed" | "cancelled";
createdAt: string;
}
const initialReservations: Reservation[] = [
{ id: "r1", clientName: "Marie Dupont", email: "marie@example.com", phone: "+33 6 12 34 56 78", service: "Pose complète", date: "2026-04-22", time: "10:00", status: "confirmed", createdAt: "2026-04-15" },
{ id: "r2", clientName: "Sophie Laurent", email: "sophie@example.com", phone: "+33 6 98 76 54 32", service: "Conseil personnalisé", date: "2026-04-23", time: "14:30", status: "pending", createdAt: "2026-04-16" },
{ id: "r3", clientName: "Amira Benali", email: "amira@example.com", phone: "+33 7 11 22 33 44", service: "Retouche", date: "2026-04-25", time: "11:00", status: "confirmed", createdAt: "2026-04-17" },
{ id: "r4", clientName: "Léa Martin", email: "lea@example.com", phone: "+33 6 55 44 33 22", service: "Pose complète", date: "2026-04-28", time: "15:00", status: "pending", createdAt: "2026-04-17" },
];
interface AdminContextType {
isAdmin: boolean;
login: (password: string) => boolean;
logout: () => void;
products: Product[];
addProduct: (product: Omit<Product, "id">) => void;
updateProduct: (id: string, product: Partial<Product>) => void;
deleteProduct: (id: string) => void;
reservations: Reservation[];
updateReservationStatus: (id: string, status: Reservation["status"]) => void;
deleteReservation: (id: string) => void;
}
const AdminContext = createContext<AdminContextType | undefined>(undefined);
const ADMIN_PASSWORD = "admin123";
export const AdminProvider = ({ children }: { children: ReactNode }) => {
const [isAdmin, setIsAdmin] = useState(false);
const [productList, setProducts] = useState<Product[]>(products);
const [reservations, setReservations] = useState<Reservation[]>(initialReservations);
const login = (password: string) => {
if (password === ADMIN_PASSWORD) {
setIsAdmin(true);
return true;
}
return false;
};
const logout = () => setIsAdmin(false);
const addProduct = (product: Omit<Product, "id">) => {
const newProduct: Product = { ...product, id: `p-${Date.now()}` };
setProducts((prev) => [newProduct, ...prev]);
};
const updateProduct = (id: string, updates: Partial<Product>) => {
setProducts((prev) => prev.map((p) => (p.id === id ? { ...p, ...updates } : p)));
};
const deleteProduct = (id: string) => {
setProducts((prev) => prev.filter((p) => p.id !== id));
};
const updateReservationStatus = (id: string, status: Reservation["status"]) => {
setReservations((prev) => prev.map((r) => (r.id === id ? { ...r, status } : r)));
};
const deleteReservation = (id: string) => {
setReservations((prev) => prev.filter((r) => r.id !== id));
};
return (
<AdminContext.Provider
value={{
isAdmin,
login,
logout,
products: productList,
addProduct,
updateProduct,
deleteProduct,
reservations,
updateReservationStatus,
deleteReservation,
}}
>
{children}
</AdminContext.Provider>
);
};
export const useAdmin = () => {
const ctx = useContext(AdminContext);
if (!ctx) throw new Error("useAdmin must be used within AdminProvider");
return ctx;
};

74
contexts/CartContext.tsx Normal file
View File

@@ -0,0 +1,74 @@
"use client";
import React, { createContext, useContext, useState, ReactNode } from "react";
import { Product } from "@/data/products";
export interface CartItem {
product: Product;
quantity: number;
selectedColor: string;
selectedLength: string;
}
interface CartContextType {
items: CartItem[];
addItem: (product: Product, color: string, length: string) => void;
removeItem: (productId: string) => void;
updateQuantity: (productId: string, quantity: number) => void;
clearCart: () => void;
totalItems: number;
totalPrice: number;
isCartOpen: boolean;
setIsCartOpen: (open: boolean) => void;
}
const CartContext = createContext<CartContextType | undefined>(undefined);
export const CartProvider = ({ children }: { children: ReactNode }) => {
const [items, setItems] = useState<CartItem[]>([]);
const [isCartOpen, setIsCartOpen] = useState(false);
const addItem = (product: Product, color: string, length: string) => {
setItems((prev) => {
const existing = prev.find((i) => i.product.id === product.id && i.selectedColor === color && i.selectedLength === length);
if (existing) {
return prev.map((i) =>
i.product.id === product.id && i.selectedColor === color && i.selectedLength === length
? { ...i, quantity: i.quantity + 1 }
: i
);
}
return [...prev, { product, quantity: 1, selectedColor: color, selectedLength: length }];
});
setIsCartOpen(true);
};
const removeItem = (productId: string) => {
setItems((prev) => prev.filter((i) => i.product.id !== productId));
};
const updateQuantity = (productId: string, quantity: number) => {
if (quantity <= 0) {
removeItem(productId);
return;
}
setItems((prev) => prev.map((i) => (i.product.id === productId ? { ...i, quantity } : i)));
};
const clearCart = () => setItems([]);
const totalItems = items.reduce((sum, i) => sum + i.quantity, 0);
const totalPrice = items.reduce((sum, i) => sum + i.product.price * i.quantity, 0);
return (
<CartContext.Provider value={{ items, addItem, removeItem, updateQuantity, clearCart, totalItems, totalPrice, isCartOpen, setIsCartOpen }}>
{children}
</CartContext.Provider>
);
};
export const useCart = () => {
const context = useContext(CartContext);
if (!context) throw new Error("useCart must be used within CartProvider");
return context;
};

View File

@@ -0,0 +1,80 @@
"use client";
import { useContext, useState, ReactNode, createContext } from "react";
export type Language = "fr" | "de" | "en" | "ar" | "tr";
interface LanguageContextType {
language: Language;
setLanguage: (lang: Language) => void;
t: (key: string) => string;
isRTL: boolean;
}
const translations: Record<string, Record<Language, string>> = {
"nav.shop": { fr: "Boutique", de: "Shop", en: "Shop", ar: "المتجر", tr: "Mağaza" },
"nav.booking": { fr: "Réservation", de: "Terminbuchung", en: "Book Appointment", ar: "حجز موعد", tr: "Randevu" },
"nav.about": { fr: "À propos", de: "Über uns", en: "About", ar: "من نحن", tr: "Hakkımızda" },
"nav.contact": { fr: "Contact", de: "Kontakt", en: "Contact", ar: "اتصل بنا", tr: "İletişim" },
"nav.account": { fr: "Mon compte", de: "Mein Konto", en: "My Account", ar: "حسابي", tr: "Hesabım" },
"hero.title": { fr: "Sublimez votre beauté naturelle", de: "Unterstreichen Sie Ihre natürliche Schönheit", en: "Enhance your natural beauty", ar: "عززي جمالك الطبيعي", tr: "Doğal güzelliğinizi ortaya çıkarın" },
"hero.subtitle": { fr: "Extensions de cheveux 100% naturels, qualité premium", de: "100% natürliche Haarverlängerungen, Premium-Qualität", en: "100% natural hair extensions, premium quality", ar: "وصلات شعر طبيعية 100%، جودة فاخرة", tr: "100% doğal saç eklentileri, premium kalite" },
"hero.cta": { fr: "Découvrir la collection", de: "Kollektion entdecken", en: "Discover the collection", ar: "اكتشفي المجموعة", tr: "Koleksiyonu keşfedin" },
"categories.title": { fr: "Nos Collections", de: "Unsere Kollektionen", en: "Our Collections", ar: "مجموعاتنا", tr: "Koleksiyonlarımız" },
"bestsellers.title": { fr: "Les Plus Vendus", de: "Bestseller", en: "Bestsellers", ar: "الأكثر مبيعاً", tr: "En Çok Satanlar" },
"booking.title": { fr: "Réservez votre rendez-vous", de: "Termin buchen", en: "Book your appointment", ar: "احجزي موعدك", tr: "Randevunuzu alın" },
"booking.subtitle": { fr: "Consultation gratuite et personnalisée", de: "Kostenlose und persönliche Beratung", en: "Free personalized consultation", ar: "استشارة مجانية وشخصية", tr: "Ücretsiz kişisel danışmanlık" },
"booking.cta": { fr: "Prendre rendez-vous", de: "Termin vereinbaren", en: "Book now", ar: "احجزي الآن", tr: "Randevu al" },
"reviews.title": { fr: "Ce que disent nos clientes", de: "Was unsere Kundinnen sagen", en: "What our clients say", ar: "ماذا تقول عميلاتنا", tr: "Müşterilerimiz ne diyor" },
"cart.title": { fr: "Mon Panier", de: "Warenkorb", en: "My Cart", ar: "سلة التسوق", tr: "Sepetim" },
"cart.empty": { fr: "Votre panier est vide", de: "Ihr Warenkorb ist leer", en: "Your cart is empty", ar: "سلة التسوق فارغة", tr: "Sepetiniz boş" },
"cart.total": { fr: "Total", de: "Gesamt", en: "Total", ar: "المجموع", tr: "Toplam" },
"cart.checkout": { fr: "Commander", de: "Bestellen", en: "Checkout", ar: "إتمام الشراء", tr: "Sipariş ver" },
"cart.add": { fr: "Ajouter au panier", de: "In den Warenkorb", en: "Add to cart", ar: "أضيفي إلى السلة", tr: "Sepete ekle" },
"product.similar": { fr: "Produits similaires", de: "Ähnliche Produkte", en: "Similar products", ar: "منتجات مشابهة", tr: "Benzer ürünler" },
"product.color": { fr: "Couleur", de: "Farbe", en: "Color", ar: "اللون", tr: "Renk" },
"product.length": { fr: "Longueur", de: "Länge", en: "Length", ar: "الطول", tr: "Uzunluk" },
"product.features": { fr: "Caractéristiques", de: "Eigenschaften", en: "Features", ar: "المميزات", tr: "Özellikler" },
"shop.title": { fr: "Notre Boutique", de: "Unser Shop", en: "Our Shop", ar: "متجرنا", tr: "Mağazamız" },
"shop.filter.all": { fr: "Tous", de: "Alle", en: "All", ar: "الكل", tr: "Tümü" },
"auth.login": { fr: "Connexion", de: "Anmelden", en: "Login", ar: "تسجيل الدخول", tr: "Giriş" },
"auth.register": { fr: "Inscription", de: "Registrieren", en: "Sign Up", ar: "إنشاء حساب", tr: "Kayıt ol" },
"auth.email": { fr: "Adresse email", de: "E-Mail-Adresse", en: "Email address", ar: "البريد الإلكتروني", tr: "E-posta adresi" },
"auth.password": { fr: "Mot de passe", de: "Passwort", en: "Password", ar: "كلمة المرور", tr: "Şifre" },
"auth.name": { fr: "Nom complet", de: "Vollständiger Name", en: "Full name", ar: "الاسم الكامل", tr: "Tam ad" },
"about.title": { fr: "Notre Histoire", de: "Unsere Geschichte", en: "Our Story", ar: "قصتنا", tr: "Hikayemiz" },
"contact.title": { fr: "Contactez-nous", de: "Kontaktieren Sie uns", en: "Contact us", ar: "اتصلي بنا", tr: "Bize ulaşın" },
"contact.send": { fr: "Envoyer", de: "Senden", en: "Send", ar: "إرسال", tr: "Gönder" },
"contact.message": { fr: "Votre message", de: "Ihre Nachricht", en: "Your message", ar: "رسالتك", tr: "Mesajınız" },
"footer.rights": { fr: "Tous droits réservés", de: "Alle Rechte vorbehalten", en: "All rights reserved", ar: "جميع الحقوق محفوظة", tr: "Tüm hakları saklıdır" },
"booking.select_service": { fr: "Choisir un service", de: "Service wählen", en: "Select service", ar: "اختاري الخدمة", tr: "Hizmet seçin" },
"booking.select_date": { fr: "Choisir une date", de: "Datum wählen", en: "Select date", ar: "اختاري التاريخ", tr: "Tarih seçin" },
"booking.select_time": { fr: "Choisir un créneau", de: "Zeitfenster wählen", en: "Select time", ar: "اختاري الوقت", tr: "Saat seçin" },
"booking.confirm": { fr: "Confirmer la réservation", de: "Buchung bestätigen", en: "Confirm booking", ar: "تأكيد الحجز", tr: "Rezervasyonu onayla" },
"booking.phone": { fr: "Téléphone", de: "Telefon", en: "Phone", ar: "الهاتف", tr: "Telefon" },
"booking.free": { fr: "Gratuit", de: "Kostenlos", en: "Free", ar: "مجاني", tr: "Ücretsiz" },
};
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
export const LanguageProvider = ({ children }: { children: ReactNode }) => {
const [language, setLanguage] = useState<Language>("fr");
const t = (key: string): string => {
return translations[key]?.[language] || key;
};
const isRTL = language === "ar";
return (
<LanguageContext.Provider value={{ language, setLanguage, t, isRTL }}>
<div dir={isRTL ? "rtl" : "ltr"}>{children}</div>
</LanguageContext.Provider>
);
};
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) throw new Error("useLanguage must be used within LanguageProvider");
return context;
};