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:
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
117
app/page.tsx
117
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 (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user