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,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<string | null>(null);
|
||||
|
||||
// Fonction de copie avec fallback pour les navigateurs qui ne supportent pas l'API Clipboard
|
||||
const copyToClipboard = async (text: string): Promise<boolean> => {
|
||||
// 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 (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Wifi className="h-6 w-6 text-primary" />
|
||||
Connexion WiFi
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Nom du réseau</p>
|
||||
<p className="text-lg font-semibold text-primary">{wifiName || "Chargement..."}</p>
|
||||
</div>
|
||||
|
||||
{showPasswordFallback && wifiPassword && (
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-xl p-3">
|
||||
<p className="text-sm text-yellow-800 dark:text-yellow-300 font-mono select-all">
|
||||
{wifiPassword}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && !showPasswordFallback && (
|
||||
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-3 flex items-start gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-red-800 dark:text-red-300">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleCopyPassword}
|
||||
disabled={loading || !wifiPassword}
|
||||
className="w-full h-14 text-lg"
|
||||
size="lg"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Copy className="mr-2 h-5 w-5" />
|
||||
Chargement...
|
||||
</>
|
||||
) : copied ? (
|
||||
<>
|
||||
<Check className="mr-2 h-5 w-5" />
|
||||
Mot de passe copié !
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="mr-2 h-5 w-5" />
|
||||
Copier le mot de passe
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<div className="min-h-screen bg-background dark:bg-background-dark">
|
||||
<header className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 shadow-sm">
|
||||
<div className="max-w-4xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold text-primary dark:text-primary">Administration</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<Button variant="outline" size="sm" onClick={handleLogout} className="dark:border-gray-700 dark:text-gray-300">
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Déconnexion
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="max-w-4xl mx-auto px-4 py-6">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<ClientInput>({
|
||||
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<string | null>(null);
|
||||
const [createdClient, setCreatedClient] = useState<Client | null>(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 (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{client ? "Modifier le client" : "Nouveau client"}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Numéro de bungalow *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={formData.bungalowNumber}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Nom du WiFi
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.wifiName}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Mot de passe WiFi
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.wifiPassword}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Message du gérant
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.gerantMessage}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, gerantMessage: e.target.value })
|
||||
}
|
||||
rows={3}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-xl text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button type="button" variant="outline" onClick={onCancel} className="flex-1">
|
||||
Annuler
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading} className="flex-1">
|
||||
{loading ? "Enregistrement..." : client ? "Modifier" : "Créer"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{createdClient && !client && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 className="font-semibold text-primary mb-3">Client créé avec succès !</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-700 mb-2">Lien unique :</p>
|
||||
<div className="bg-secondary rounded-xl p-4 space-y-3">
|
||||
<textarea
|
||||
readOnly
|
||||
value={getClientUrl()}
|
||||
onClick={(e) => e.currentTarget.select()}
|
||||
onFocus={(e) => e.currentTarget.select()}
|
||||
className="w-full p-3 text-sm font-mono text-primary bg-white border-2 border-primary rounded-lg resize-none"
|
||||
rows={3}
|
||||
style={{ cursor: 'text' }}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleCopyLink}
|
||||
className={`flex-1 ${copied ? "bg-green-600 hover:bg-green-700" : ""}`}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
Copié !
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
Copier
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const textarea = document.querySelector('textarea[readonly]') as HTMLTextAreaElement;
|
||||
if (textarea) {
|
||||
textarea.select();
|
||||
}
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
Sélectionner
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
💡 Cliquez sur le lien pour le sélectionner, puis Ctrl+C pour copier
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 mb-2">QR Code :</p>
|
||||
<QRCodeDisplay url={getClientUrl()} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,179 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Trash2, Edit, Copy, QrCode } from "lucide-react";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Client } from "@/lib/types/client";
|
||||
import QRCodeDisplay from "./QRCodeDisplay";
|
||||
|
||||
interface ClientListProps {
|
||||
onEdit: (client: Client) => void;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export default function ClientList({ onEdit, onRefresh }: ClientListProps) {
|
||||
const [clients, setClients] = useState<Client[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedClient, setSelectedClient] = useState<Client | null>(null);
|
||||
const [showQR, setShowQR] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchClients();
|
||||
}, []);
|
||||
|
||||
const fetchClients = async () => {
|
||||
try {
|
||||
const adminPassword = localStorage.getItem("adminPassword") || "";
|
||||
const response = await fetch("/api/admin/clients", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminPassword}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setClients(data);
|
||||
} else if (response.status === 404) {
|
||||
// API non disponible (mode statique/APK)
|
||||
// Afficher un message d'information
|
||||
console.warn("API non disponible en mode statique");
|
||||
setClients([]);
|
||||
} else {
|
||||
console.error("Erreur lors du chargement des clients:", response.status);
|
||||
setClients([]);
|
||||
}
|
||||
} catch (error) {
|
||||
// Erreur réseau (API non disponible en mode statique/APK)
|
||||
console.warn("API non disponible (mode statique/APK). Les fonctionnalités admin nécessitent un serveur.");
|
||||
setClients([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm("Êtes-vous sûr de vouloir supprimer ce client ?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const adminPassword = localStorage.getItem("adminPassword") || "";
|
||||
const response = await fetch(`/api/admin/clients/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminPassword}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
fetchClients();
|
||||
onRefresh();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la suppression:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getClientUrl = (token: string) => {
|
||||
const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
|
||||
return `${baseUrl}/accueil?token=${token}`;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<p className="text-gray-600 dark:text-gray-400">Chargement...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (clients.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="py-8 text-center space-y-4">
|
||||
<p className="text-gray-600 dark:text-gray-400">Aucun client pour le moment.</p>
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-xl p-4 text-sm text-yellow-800 dark:text-yellow-300">
|
||||
<p className="font-semibold mb-1">⚠️ Mode hors ligne</p>
|
||||
<p>L'application admin nécessite une connexion au serveur pour fonctionner. Les API routes ne sont pas disponibles en mode statique.</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{clients.map((client) => (
|
||||
<Card key={client.id}>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-lg">{client.email}</CardTitle>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Bungalow {client.bungalowNumber}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const url = getClientUrl(client.token);
|
||||
navigator.clipboard.writeText(url);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
setShowQR(showQR === client.id ? null : client.id)
|
||||
}
|
||||
>
|
||||
<QrCode className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onEdit(client)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleDelete(client.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div>
|
||||
<span className="font-medium">WiFi:</span> {client.wifiName}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Message:</span>{" "}
|
||||
{client.gerantMessage.substring(0, 50)}
|
||||
{client.gerantMessage.length > 50 ? "..." : ""}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
Créé le {new Date(client.createdAt).toLocaleDateString("fr-FR")}
|
||||
</div>
|
||||
</div>
|
||||
{showQR === client.id && (
|
||||
<div className="mt-4 pt-4 border-t border-gray-200">
|
||||
<QRCodeDisplay url={getClientUrl(client.token)} size={150} />
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
|
||||
interface QRCodeDisplayProps {
|
||||
url: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export default function QRCodeDisplay({ url, size = 200 }: QRCodeDisplayProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
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);
|
||||
} catch (err) {
|
||||
console.error("Erreur lors de la copie:", err);
|
||||
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(`Copiez ce lien manuellement:\n\n${url}`);
|
||||
}
|
||||
} catch (e) {
|
||||
alert(`Copiez ce lien manuellement:\n\n${url}`);
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4 p-6 bg-white rounded-2xl border border-gray-200">
|
||||
<QRCodeSVG value={url} size={size} level="H" />
|
||||
|
||||
<div className="w-full space-y-3">
|
||||
<p className="text-sm font-medium text-gray-700">Lien unique :</p>
|
||||
<textarea
|
||||
readOnly
|
||||
value={url}
|
||||
onClick={(e) => e.currentTarget.select()}
|
||||
onFocus={(e) => e.currentTarget.select()}
|
||||
className="w-full p-3 text-sm font-mono text-primary bg-secondary border-2 border-primary rounded-lg resize-none"
|
||||
rows={3}
|
||||
style={{ cursor: 'text' }}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleCopyLink}
|
||||
className={`flex-1 ${copied ? "bg-green-600 hover:bg-green-700" : ""}`}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
Copié !
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
Copier
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const textarea = document.querySelector('textarea[readonly]') as HTMLTextAreaElement;
|
||||
if (textarea) {
|
||||
textarea.select();
|
||||
}
|
||||
}}
|
||||
className="flex-1"
|
||||
>
|
||||
Sélectionner
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 text-center">
|
||||
💡 Cliquez sur le lien pour le sélectionner, puis Ctrl+C pour copier
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user