diff --git a/app/accueil/page.tsx b/app/accueil/page.tsx index 9d9ebf9..9c27d67 100644 --- a/app/accueil/page.tsx +++ b/app/accueil/page.tsx @@ -1,53 +1,37 @@ "use client"; -import { Suspense } from "react"; import dynamic from "next/dynamic"; import Layout from "@/components/layout/Layout"; -import WifiCard from "@/components/accueil/WifiCard"; import Logo from "@/components/Logo"; -import { useClientData } from "@/lib/hooks/useClientData"; +import { config } from "@/lib/config"; const WeatherWidget = dynamic(() => import("@/components/accueil/WeatherWidget"), { - loading: () =>
, + loading: () =>
, ssr: false, }); -function AccueilContent() { - const { bungalowNumber, gerantMessage, loading } = useClientData(); - - if (loading) { - return ( - -
-
-
- - ); - } - +export default function AccueilPage() { return (
-

+

Ia Ora Na

-

- Bienvenue au Bungalow {bungalowNumber} +

+ Bienvenue à la Pension Marama

- - -
-

+
+

Le mot du gérant

-

- {gerantMessage} +

+ {config.gerantMessage}

@@ -55,17 +39,3 @@ function AccueilContent() { ); } -export default function AccueilPage() { - return ( - -
-
-
- - }> - - - ); -} - diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx deleted file mode 100644 index 2d63f69..0000000 --- a/app/admin/login/page.tsx +++ /dev/null @@ -1,92 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; -import Logo from "@/components/Logo"; - -export default function AdminLoginPage() { - const [password, setPassword] = useState(""); - const [error, setError] = useState(""); - const [loading, setLoading] = useState(false); - const router = useRouter(); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(""); - setLoading(true); - - // Vérification simple côté client (la vraie vérification se fait côté serveur) - // Pour l'instant, on stocke le mot de passe dans localStorage - localStorage.setItem("adminPassword", password); - - // Test avec une requête API - try { - const response = await fetch("/api/admin/clients", { - headers: { - Authorization: `Bearer ${password}`, - }, - }); - - if (response.ok) { - router.push("/admin"); - } else if (response.status === 404) { - // API non disponible (mode statique/APK) - accepter quand même - // Le mot de passe sera vérifié côté serveur lors des vraies requêtes - router.push("/admin"); - } else { - setError("Mot de passe incorrect"); - localStorage.removeItem("adminPassword"); - } - } catch (err) { - // Erreur réseau (API non disponible en mode statique/APK) - // Accepter quand même et rediriger - // Le mot de passe sera vérifié côté serveur lors des vraies requêtes - console.warn("API non disponible (mode statique), connexion acceptée localement"); - router.push("/admin"); - } finally { - setLoading(false); - } - }; - - return ( -
- - -
- -
- Administration -
- -
-
- - setPassword(e.target.value)} - className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent" - required - /> -
- - {error && ( -
- {error} -
- )} - - -
-
-
-
- ); -} - diff --git a/app/admin/page.tsx b/app/admin/page.tsx deleted file mode 100644 index e25ab0d..0000000 --- a/app/admin/page.tsx +++ /dev/null @@ -1,97 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { useRouter } from "next/navigation"; -import AdminLayout from "@/components/admin/AdminLayout"; -import ClientForm from "@/components/admin/ClientForm"; -import ClientList from "@/components/admin/ClientList"; -import { Button } from "@/components/ui/button"; -import { Plus } from "lucide-react"; -import { Client } from "@/lib/types/client"; - -export default function AdminPage() { - const [showForm, setShowForm] = useState(false); - const [editingClient, setEditingClient] = useState(); - const [refreshKey, setRefreshKey] = useState(0); - const router = useRouter(); - - useEffect(() => { - // Vérifier si l'admin est connecté - const adminPassword = localStorage.getItem("adminPassword"); - if (!adminPassword) { - router.push("/admin/login"); - return; - } - - // Tester la connexion avec l'API (si disponible) - // Si l'API n'est pas disponible (APK statique), on continue quand même - fetch("/api/admin/clients", { - headers: { - Authorization: `Bearer ${adminPassword}`, - }, - }) - .then((res) => { - if (!res.ok && res.status !== 404) { - // Si erreur autre que 404 (API non disponible), déconnecter - if (res.status === 401 || res.status === 403) { - localStorage.removeItem("adminPassword"); - router.push("/admin/login"); - } - } - // Si 404, c'est normal en mode statique (API non disponible) - // On continue l'affichage - }) - .catch(() => { - // Erreur réseau (API non disponible en mode statique) - // C'est normal pour l'APK, on continue - }); - }, [router]); - - const handleNewClient = () => { - setEditingClient(undefined); - setShowForm(true); - }; - - const handleEdit = (client: Client) => { - setEditingClient(client); - setShowForm(true); - }; - - const handleSuccess = () => { - setShowForm(false); - setEditingClient(undefined); - setRefreshKey((k) => k + 1); - }; - - const handleCancel = () => { - setShowForm(false); - setEditingClient(undefined); - }; - - return ( - -
-
-

Gestion des clients

- {!showForm && ( - - )} -
- - {showForm ? ( - - ) : ( - setRefreshKey((k) => k + 1)} /> - )} -
-
- ); -} - diff --git a/app/api/admin/clients/route.ts b/app/api/admin/clients/route.ts deleted file mode 100644 index 9b78cd7..0000000 --- a/app/api/admin/clients/route.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { writeFile, readFile, mkdir } from "fs/promises"; -import { existsSync } from "fs"; -import path from "path"; -import { Client, ClientInput } from "@/lib/types/client"; - -// Mot de passe admin (à changer en production via variable d'environnement) -const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123"; - -// Chemin vers le fichier de stockage -const DATA_DIR = path.join(process.cwd(), "data"); -const CLIENTS_FILE = path.join(DATA_DIR, "clients.json"); - -// Vérifier l'authentification -function verifyAuth(request: NextRequest): boolean { - const authHeader = request.headers.get("authorization"); - if (!authHeader) return false; - - const token = authHeader.replace("Bearer ", ""); - return token === ADMIN_PASSWORD; -} - -// Charger les clients depuis le fichier -async function loadClients(): Promise { - try { - if (!existsSync(CLIENTS_FILE)) { - // Créer le répertoire et le fichier si nécessaire - if (!existsSync(DATA_DIR)) { - await mkdir(DATA_DIR, { recursive: true }); - } - await writeFile(CLIENTS_FILE, JSON.stringify([], null, 2)); - return []; - } - - const data = await readFile(CLIENTS_FILE, "utf-8"); - return JSON.parse(data); - } catch (error) { - console.error("Erreur lecture clients:", error); - return []; - } -} - -// Sauvegarder les clients dans le fichier -async function saveClients(clients: Client[]): Promise { - try { - if (!existsSync(DATA_DIR)) { - await mkdir(DATA_DIR, { recursive: true }); - } - await writeFile(CLIENTS_FILE, JSON.stringify(clients, null, 2)); - } catch (error) { - console.error("Erreur sauvegarde clients:", error); - throw error; - } -} - -// GET - Récupérer tous les clients -export async function GET(request: NextRequest) { - if (!verifyAuth(request)) { - return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); - } - - try { - const clients = await loadClients(); - return NextResponse.json(clients); - } catch (error) { - console.error("Erreur GET clients:", error); - return NextResponse.json( - { error: "Erreur serveur" }, - { status: 500 } - ); - } -} - -// POST - Créer un nouveau client -export async function POST(request: NextRequest) { - if (!verifyAuth(request)) { - return NextResponse.json({ error: "Non autorisé" }, { status: 401 }); - } - - try { - const input: ClientInput = await request.json(); - - // Validation - if (!input.email || !input.bungalowNumber) { - return NextResponse.json( - { error: "Email et numéro de bungalow requis" }, - { status: 400 } - ); - } - - const clients = await loadClients(); - - // Vérifier si l'email existe déjà - if (clients.some(c => c.email === input.email)) { - return NextResponse.json( - { error: "Un client avec cet email existe déjà" }, - { status: 409 } - ); - } - - // Créer le nouveau client - const newClient: Client = { - id: `client-${Date.now()}`, - token: generateToken(), - email: input.email, - bungalowNumber: input.bungalowNumber, - wifiName: input.wifiName || "Lagon-WiFi", - wifiPassword: input.wifiPassword || "", - gerantMessage: input.gerantMessage || "Bienvenue dans notre pension de famille !", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - - clients.push(newClient); - await saveClients(clients); - - return NextResponse.json(newClient, { status: 201 }); - } catch (error) { - console.error("Erreur POST client:", error); - return NextResponse.json( - { error: "Erreur serveur" }, - { status: 500 } - ); - } -} - -// Générer un token unique -function generateToken(): string { - return Math.random().toString(36).substring(2, 15) + - Math.random().toString(36).substring(2, 15); -} - diff --git a/app/page.tsx b/app/page.tsx index cce2376..e3a6af9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,121 +1,20 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useRouter } from "next/navigation"; export default function Home() { const router = useRouter(); - const [checked, setChecked] = useState(false); useEffect(() => { - // Détecter si on est dans l'app admin - let isAdminApp = false; - - if (typeof window !== "undefined") { - const Capacitor = (window as any).Capacitor; - - // Méthode 1: Vérifier l'appId de Capacitor via le package name Android - if (Capacitor) { - try { - const platform = Capacitor.getPlatform(); - - if (platform === "android") { - // En Android, on peut récupérer le package name via Capacitor.getApp() - // APK admin: com.pensionmarama.admin - // APK client: com.pensionmarama.app - const App = Capacitor.Plugins?.App; - if (App) { - App.getInfo().then((info: any) => { - // Le package name est dans info.id ou info.appId - const appId = info.id || info.appId || ""; - if (appId.includes("admin")) { - isAdminApp = true; - } - }).catch(() => { - // Fallback si getInfo() échoue - }); - } - - // Fallback: Vérifier le localStorage (si adminPassword existe, c'est admin) - const hasAdminPassword = localStorage.getItem("adminPassword") !== null; - if (hasAdminPassword) { - isAdminApp = true; - } - } else { - // Pour web, vérifier le localStorage - const hasAdminPassword = localStorage.getItem("adminPassword") !== null; - if (hasAdminPassword) { - isAdminApp = true; - } - } - } catch (e) { - // En cas d'erreur, fallback sur les autres méthodes - console.warn("Erreur lors de la détection Capacitor:", e); - } - } - - // Méthode 2: Vérifier le path ou query string (pour web) - if (!isAdminApp && ( - window.location.pathname.startsWith("/admin") || - window.location.search.includes("admin=true") - )) { - isAdminApp = true; - } - - // Méthode 3: Si on est dans Capacitor Android sans adminPassword, - // on considère que c'est l'app client (redirige vers /accueil) - // Sauf si le pathname commence par /admin - } - - if (isAdminApp) { - // Vérifier si l'admin est connecté - const adminPassword = typeof window !== "undefined" ? - localStorage.getItem("adminPassword") : null; - - if (adminPassword) { - // Tester la connexion (si API disponible) - fetch("/api/admin/clients", { - headers: { - Authorization: `Bearer ${adminPassword}`, - }, - }) - .then((res) => { - if (res.ok) { - router.replace("/admin"); - } else if (res.status === 404) { - // API non disponible (mode statique) - accepter quand même - router.replace("/admin"); - } else { - localStorage.removeItem("adminPassword"); - router.replace("/admin/login"); - } - }) - .catch(() => { - // Erreur réseau (API non disponible en mode statique) - // Accepter quand même et rediriger vers /admin - router.replace("/admin"); - }); - } else { - // Pas de mot de passe, rediriger vers login - router.replace("/admin/login"); - } - } else { - // App client normale - router.replace("/accueil"); - } - setChecked(true); + router.replace("/accueil"); }, [router]); - if (!checked) { - return ( -
-
-

Chargement...

-
+ return ( +
+
+

Redirection...

- ); - } - - return null; +
+ ); } - diff --git a/components/accueil/WifiCard.tsx b/components/accueil/WifiCard.tsx deleted file mode 100644 index 72c67ef..0000000 --- a/components/accueil/WifiCard.tsx +++ /dev/null @@ -1,129 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Wifi, Copy, Check, AlertCircle } from "lucide-react"; -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { useClientData } from "@/lib/hooks/useClientData"; - -export default function WifiCard() { - const { wifiName, wifiPassword, loading } = useClientData(); - const [copied, setCopied] = useState(false); - const [error, setError] = useState(null); - - // Fonction de copie avec fallback pour les navigateurs qui ne supportent pas l'API Clipboard - const copyToClipboard = async (text: string): Promise => { - // Méthode moderne (nécessite HTTPS ou localhost) - if (navigator.clipboard && window.isSecureContext) { - try { - await navigator.clipboard.writeText(text); - return true; - } catch (err) { - console.error("Erreur avec l'API Clipboard:", err); - } - } - - // Fallback pour les navigateurs plus anciens ou contextes non sécurisés - try { - const textArea = document.createElement("textarea"); - textArea.value = text; - textArea.style.position = "fixed"; - textArea.style.left = "-999999px"; - textArea.style.top = "-999999px"; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - const successful = document.execCommand("copy"); - document.body.removeChild(textArea); - - if (successful) { - return true; - } else { - throw new Error("La commande copy a échoué"); - } - } catch (err) { - console.error("Erreur avec la méthode fallback:", err); - return false; - } - }; - - const handleCopyPassword = async () => { - if (!wifiPassword || wifiPassword.trim() === "") { - setError("Le mot de passe WiFi n'est pas disponible"); - setTimeout(() => setError(null), 3000); - return; - } - - setError(null); - const success = await copyToClipboard(wifiPassword); - - if (success) { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } else { - setError("Impossible de copier. Veuillez sélectionner manuellement le mot de passe ci-dessous."); - setTimeout(() => setError(null), 5000); - } - }; - - // Afficher le mot de passe en cas d'échec de la copie - const showPasswordFallback = error && error.includes("sélectionner manuellement"); - - return ( - - - - - Connexion WiFi - - - -
-

Nom du réseau

-

{wifiName || "Chargement..."}

-
- - {showPasswordFallback && wifiPassword && ( -
-

- {wifiPassword} -

-
- )} - - {error && !showPasswordFallback && ( -
- -

{error}

-
- )} - - -
-
- ); -} - diff --git a/components/admin/AdminLayout.tsx b/components/admin/AdminLayout.tsx deleted file mode 100644 index d7c9a9e..0000000 --- a/components/admin/AdminLayout.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import { LogOut } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useRouter } from "next/navigation"; -import { ThemeToggle } from "@/components/ThemeToggle"; - -export default function AdminLayout({ children }: { children: React.ReactNode }) { - const router = useRouter(); - - const handleLogout = () => { - localStorage.removeItem("adminPassword"); - router.push("/admin/login"); - }; - - return ( -
-
-
-

Administration

-
- - -
-
-
-
{children}
-
- ); -} - diff --git a/components/admin/ClientForm.tsx b/components/admin/ClientForm.tsx deleted file mode 100644 index ea1ee14..0000000 --- a/components/admin/ClientForm.tsx +++ /dev/null @@ -1,264 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; -import { Client, ClientInput } from "@/lib/types/client"; -import QRCodeDisplay from "./QRCodeDisplay"; -import { Copy, Check } from "lucide-react"; - -interface ClientFormProps { - client?: Client; - onSuccess: () => void; - onCancel: () => void; -} - -export default function ClientForm({ client, onSuccess, onCancel }: ClientFormProps) { - const [formData, setFormData] = useState({ - email: client?.email || "", - bungalowNumber: client?.bungalowNumber || "", - wifiName: client?.wifiName || "Lagon-WiFi", - wifiPassword: client?.wifiPassword || "", - gerantMessage: client?.gerantMessage || "Bienvenue dans notre pension de famille !", - }); - - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [createdClient, setCreatedClient] = useState(client || null); - const [copied, setCopied] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - setError(null); - - try { - const adminPassword = localStorage.getItem("adminPassword") || ""; - const url = client - ? `/api/admin/clients/${client.id}` - : "/api/admin/clients"; - - const method = client ? "PUT" : "POST"; - - const response = await fetch(url, { - method, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${adminPassword}`, - }, - body: JSON.stringify(formData), - }); - - if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || "Erreur lors de la sauvegarde"); - } - - const data = await response.json(); - setCreatedClient(data); - onSuccess(); - } catch (err: any) { - setError(err.message); - } finally { - setLoading(false); - } - }; - - const getClientUrl = () => { - if (!createdClient) return ""; - const baseUrl = typeof window !== "undefined" ? window.location.origin : ""; - return `${baseUrl}/accueil?token=${createdClient.token}`; - }; - - const handleCopyLink = async () => { - const url = getClientUrl(); - try { - await navigator.clipboard.writeText(url); - setCopied(true); - // Alerte pour confirmer - alert(`✅ Lien copié !\n\n${url}\n\nVous pouvez maintenant le coller (Ctrl+V) pour le partager avec votre client.`); - setTimeout(() => setCopied(false), 3000); - } catch (err) { - console.error("Erreur lors de la copie:", err); - // Fallback pour les navigateurs plus anciens - const textArea = document.createElement("textarea"); - textArea.value = url; - textArea.style.position = "fixed"; - textArea.style.left = "-999999px"; - document.body.appendChild(textArea); - textArea.select(); - try { - const successful = document.execCommand("copy"); - if (successful) { - setCopied(true); - alert(`✅ Lien copié !\n\n${url}\n\nVous pouvez maintenant le coller (Ctrl+V) pour le partager avec votre client.`); - setTimeout(() => setCopied(false), 3000); - } else { - alert(`❌ Copie automatique non supportée.\n\nVeuillez copier manuellement le lien:\n\n${url}`); - } - } catch (e) { - alert(`❌ Copie automatique non supportée.\n\nVeuillez copier manuellement le lien:\n\n${url}`); - } - document.body.removeChild(textArea); - } - }; - - return ( - - - {client ? "Modifier le client" : "Nouveau client"} - - -
-
- - setFormData({ ...formData, email: e.target.value })} - disabled={!!client} - className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent disabled:bg-gray-100" - /> -
- -
- - - setFormData({ ...formData, bungalowNumber: e.target.value }) - } - className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent" - /> -
- -
- - - setFormData({ ...formData, wifiName: e.target.value }) - } - className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent" - /> -
- -
- - - setFormData({ ...formData, wifiPassword: e.target.value }) - } - className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent" - /> -
- -
- -