Simplification complète de l'app - Suppression admin/WiFi/bungalow

- Suppression de toute la partie admin (routes, composants, API)
- Suppression du WiFi et du numéro de bungalow
- Simplification de l'accueil (logo, météo, message statique)
- App 100% statique maintenant
- Redirection simple vers /accueil
- Nettoyage des hooks et types inutilisés
This commit is contained in:
2025-11-23 19:22:34 +01:00
parent 29d6ab3d80
commit b10d9c515b
195 changed files with 9355 additions and 1441 deletions

View File

@ -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: () => <div className="h-32 bg-gray-100 rounded-2xl animate-pulse" />,
loading: () => <div className="h-32 bg-gray-100 dark:bg-gray-800 rounded-2xl animate-pulse" />,
ssr: false,
});
function AccueilContent() {
const { bungalowNumber, gerantMessage, loading } = useClientData();
if (loading) {
return (
<Layout>
<div className="px-4 py-6 space-y-6">
<div className="h-32 bg-gray-100 rounded-2xl animate-pulse" />
</div>
</Layout>
);
}
export default function AccueilPage() {
return (
<Layout>
<div className="px-4 py-6 space-y-6">
<header className="text-center py-4">
<Logo size={140} className="mb-4" />
<h1 className="text-2xl font-bold text-primary mb-2">
<h1 className="text-2xl font-bold text-primary dark:text-primary mb-2">
Ia Ora Na
</h1>
<p className="text-lg text-gray-700">
Bienvenue au Bungalow {bungalowNumber}
<p className="text-lg text-gray-700 dark:text-gray-300">
Bienvenue à la Pension Marama
</p>
</header>
<WifiCard />
<WeatherWidget />
<section className="bg-secondary rounded-2xl p-6">
<h2 className="text-xl font-semibold text-primary mb-3">
<section className="bg-secondary dark:bg-primary/20 rounded-2xl p-6">
<h2 className="text-xl font-semibold text-primary dark:text-primary mb-3">
Le mot du gérant
</h2>
<p className="text-gray-700 leading-relaxed">
{gerantMessage}
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">
{config.gerantMessage}
</p>
</section>
</div>
@ -55,17 +39,3 @@ function AccueilContent() {
);
}
export default function AccueilPage() {
return (
<Suspense fallback={
<Layout>
<div className="px-4 py-6 space-y-6">
<div className="h-32 bg-gray-100 rounded-2xl animate-pulse" />
</div>
</Layout>
}>
<AccueilContent />
</Suspense>
);
}

View File

@ -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 (
<div className="min-h-screen bg-background flex items-center justify-center px-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<Logo size={100} />
</div>
<CardTitle>Administration</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Mot de passe
</label>
<input
type="password"
value={password}
onChange={(e) => 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
/>
</div>
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-xl text-sm">
{error}
</div>
)}
<Button type="submit" disabled={loading} className="w-full">
{loading ? "Connexion..." : "Se connecter"}
</Button>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@ -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<Client | undefined>();
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 (
<AdminLayout>
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-primary">Gestion des clients</h2>
{!showForm && (
<Button onClick={handleNewClient}>
<Plus className="h-4 w-4 mr-2" />
Nouveau client
</Button>
)}
</div>
{showForm ? (
<ClientForm
client={editingClient}
onSuccess={handleSuccess}
onCancel={handleCancel}
/>
) : (
<ClientList onEdit={handleEdit} onRefresh={() => setRefreshKey((k) => k + 1)} />
)}
</div>
</AdminLayout>
);
}

View File

@ -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<Client[]> {
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<void> {
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);
}

View File

@ -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 (
<div className="flex items-center justify-center min-h-screen bg-background dark:bg-background-dark">
<div className="text-center">
<p className="text-gray-600 dark:text-gray-400">Chargement...</p>
</div>
return (
<div className="flex items-center justify-center min-h-screen bg-background dark:bg-background-dark">
<div className="text-center">
<p className="text-gray-600 dark:text-gray-400">Redirection...</p>
</div>
);
}
return null;
</div>
);
}