|
@@ -0,0 +1,603 @@
|
|
|
|
|
+"use client"
|
|
|
|
|
+
|
|
|
|
|
+import { useState } from "react"
|
|
|
|
|
+import { useSession } from "next-auth/react"
|
|
|
|
|
+import { redirect } from "next/navigation"
|
|
|
|
|
+import AuthenticatedLayout from "@/components/AuthenticatedLayout"
|
|
|
|
|
+import { Button } from "@/components/ui/button"
|
|
|
|
|
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
|
+import { Separator } from "@/components/ui/separator"
|
|
|
|
|
+import { Badge } from "@/components/ui/badge"
|
|
|
|
|
+import { Input } from "@/components/ui/input"
|
|
|
|
|
+import { Label } from "@/components/ui/label"
|
|
|
|
|
+import {
|
|
|
|
|
+ CheckCircle2,
|
|
|
|
|
+ XCircle,
|
|
|
|
|
+ Info,
|
|
|
|
|
+ AlertTriangle,
|
|
|
|
|
+ Loader2,
|
|
|
|
|
+ Calendar,
|
|
|
|
|
+ User,
|
|
|
|
|
+ FileText,
|
|
|
|
|
+ Heart,
|
|
|
|
|
+ Settings,
|
|
|
|
|
+ Sparkles
|
|
|
|
|
+} from "lucide-react"
|
|
|
|
|
+import { notifications, withLoadingToast, promiseToast } from "@/lib/notifications"
|
|
|
|
|
+
|
|
|
|
|
+export default function ToastDemoPage() {
|
|
|
|
|
+ const { data: session, status } = useSession()
|
|
|
|
|
+ const [customMessage, setCustomMessage] = useState("")
|
|
|
|
|
+ const [customDescription, setCustomDescription] = useState("")
|
|
|
|
|
+ const [isLoading, setIsLoading] = useState(false)
|
|
|
|
|
+
|
|
|
|
|
+ if (status === "loading") {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <AuthenticatedLayout>
|
|
|
|
|
+ <div className="flex items-center justify-center min-h-screen">
|
|
|
|
|
+ <Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </AuthenticatedLayout>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!session || session.user.role !== "ADMIN") {
|
|
|
|
|
+ redirect("/dashboard")
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Simulación de operación async
|
|
|
|
|
+ const mockAsyncOperation = () => {
|
|
|
|
|
+ return new Promise((resolve) => {
|
|
|
|
|
+ setTimeout(() => resolve("Operación completada"), 2000)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const mockFailingOperation = () => {
|
|
|
|
|
+ return new Promise((_, reject) => {
|
|
|
|
|
+ setTimeout(() => reject(new Error("Operación falló")), 2000)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handleWithLoadingToast = async () => {
|
|
|
|
|
+ setIsLoading(true)
|
|
|
|
|
+ try {
|
|
|
|
|
+ await withLoadingToast(
|
|
|
|
|
+ mockAsyncOperation(),
|
|
|
|
|
+ "Procesando...",
|
|
|
|
|
+ "¡Operación completada con éxito!"
|
|
|
|
|
+ )
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setIsLoading(false)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const handlePromiseToast = () => {
|
|
|
|
|
+ promiseToast.execute(mockAsyncOperation(), {
|
|
|
|
|
+ loading: "Cargando datos...",
|
|
|
|
|
+ success: "Datos cargados correctamente",
|
|
|
|
|
+ error: "Error al cargar datos"
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <AuthenticatedLayout>
|
|
|
|
|
+ <div className="container mx-auto px-4 py-8 max-w-6xl">
|
|
|
|
|
+ {/* Header */}
|
|
|
|
|
+ <div className="mb-8">
|
|
|
|
|
+ <div className="flex items-center gap-3 mb-2">
|
|
|
|
|
+ <Sparkles className="h-8 w-8 text-primary" />
|
|
|
|
|
+ <h1 className="text-3xl font-bold">Toast Demo</h1>
|
|
|
|
|
+ <Badge variant="outline" className="ml-auto">Admin Only</Badge>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <p className="text-muted-foreground">
|
|
|
|
|
+ Demostración del sistema centralizado de notificaciones. Prueba todos los tipos de toasts disponibles.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div className="grid gap-6 md:grid-cols-2">
|
|
|
|
|
+ {/* Appointments Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Calendar className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Appointments</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones relacionadas con citas</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.created()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Cita Creada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.approved()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Cita Aprobada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.rejected()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Cita Rechazada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.cancelled()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Cita Cancelada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.completed()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Cita Completada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Separator />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.loadError()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error al Cargar
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.createError("Fecha no disponible")}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error al Crear (custom)
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.appointments.videocallError("Cámara no disponible")}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error Videollamada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Profile Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <User className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Profile</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones de perfil de usuario</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.profile.updated()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Perfil Actualizado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.profile.imageRemoved()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Imagen Eliminada
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Separator />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.profile.updateError("Conexión perdida")}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error al Actualizar
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.profile.invalidImage()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Imagen Inválida
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.profile.imageTooLarge()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Imagen Muy Grande
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Auth Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Settings className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Authentication</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones de autenticación</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.auth.loginSuccess()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Login Exitoso
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.auth.registerSuccess()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Registro Exitoso
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Separator />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.auth.loginError()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error de Login
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.auth.passwordMismatch()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Contraseñas No Coinciden
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.auth.passwordTooShort()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Contraseña Muy Corta
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Records Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <FileText className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Records</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones de reportes médicos</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.records.generating()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Info className="h-4 w-4 mr-2 text-blue-500" />
|
|
|
|
|
+ Generando Reporte
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.records.generated()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Reporte Generado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.records.saved()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Reporte Guardado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.records.downloaded()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Reporte Descargado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.records.copied()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Copiado al Portapapeles
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Separator />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.records.generateError()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error al Generar
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Patients Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Heart className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Patients</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones de gestión de pacientes</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.patients.assigned()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Paciente Asignado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.patients.unassigned()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Paciente Desasignado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.patients.updated()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Paciente Actualizado
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Separator />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.patients.loadError()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error al Cargar
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.patients.assignError("Doctor no disponible")}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error al Asignar
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Chat Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Info className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Chat</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones del chatbot</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.chat.newConsultation()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Nueva Consulta
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.chat.lastMessageWarning()}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <AlertTriangle className="h-4 w-4 mr-2 text-yellow-500" />
|
|
|
|
|
+ Última Consulta (Warning)
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Validation Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <AlertTriangle className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Validation</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones de validación</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.validation.requiredField("Nombre")}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Campo Requerido
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.validation.invalidFormat("Email")}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full justify-start"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Formato Inválido
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Generic Toasts */}
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Sparkles className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Generic</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Notificaciones genéricas personalizables</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-4">
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label htmlFor="customMessage">Mensaje</Label>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ id="customMessage"
|
|
|
|
|
+ placeholder="Ej: Operación exitosa"
|
|
|
|
|
+ value={customMessage}
|
|
|
|
|
+ onChange={(e) => setCustomMessage(e.target.value)}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <Label htmlFor="customDescription">Descripción (opcional)</Label>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ id="customDescription"
|
|
|
|
|
+ placeholder="Ej: Los cambios se guardaron"
|
|
|
|
|
+ value={customDescription}
|
|
|
|
|
+ onChange={(e) => setCustomDescription(e.target.value)}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="grid grid-cols-2 gap-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.generic.success(
|
|
|
|
|
+ customMessage || "Éxito",
|
|
|
|
|
+ customDescription || undefined
|
|
|
|
|
+ )}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <CheckCircle2 className="h-4 w-4 mr-2 text-green-500" />
|
|
|
|
|
+ Success
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.generic.error(
|
|
|
|
|
+ customMessage || "Error",
|
|
|
|
|
+ customDescription || undefined
|
|
|
|
|
+ )}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <XCircle className="h-4 w-4 mr-2 text-red-500" />
|
|
|
|
|
+ Error
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.generic.info(
|
|
|
|
|
+ customMessage || "Información",
|
|
|
|
|
+ customDescription || undefined
|
|
|
|
|
+ )}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Info className="h-4 w-4 mr-2 text-blue-500" />
|
|
|
|
|
+ Info
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => notifications.generic.warning(
|
|
|
|
|
+ customMessage || "Advertencia",
|
|
|
|
|
+ customDescription || undefined
|
|
|
|
|
+ )}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <AlertTriangle className="h-4 w-4 mr-2 text-yellow-500" />
|
|
|
|
|
+ Warning
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Advanced Helpers */}
|
|
|
|
|
+ <Card className="md:col-span-2">
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <Loader2 className="h-5 w-5 text-primary" />
|
|
|
|
|
+ <CardTitle>Advanced Helpers</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <CardDescription>Helpers avanzados para operaciones asíncronas</CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <div className="grid gap-4 md:grid-cols-2">
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <h4 className="font-semibold">withLoadingToast</h4>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground mb-2">
|
|
|
|
|
+ Muestra un toast de loading que se convierte en success al completar
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handleWithLoadingToast}
|
|
|
|
|
+ disabled={isLoading}
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ {isLoading ? (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
|
|
|
+ Procesando...
|
|
|
|
|
+ </>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ "Probar withLoadingToast"
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <h4 className="font-semibold">promiseToast</h4>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground mb-2">
|
|
|
|
|
+ Ejecuta una promesa con estados loading/success/error automáticos
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handlePromiseToast}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ Probar promiseToast
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Footer Info */}
|
|
|
|
|
+ <Card className="mt-6 border-dashed">
|
|
|
|
|
+ <CardContent className="pt-6">
|
|
|
|
|
+ <div className="flex items-start gap-3">
|
|
|
|
|
+ <Info className="h-5 w-5 text-blue-500 mt-0.5" />
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ <p className="text-sm font-medium">Sobre este sistema</p>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">
|
|
|
|
|
+ Este sistema de notificaciones está centralizado en <code className="bg-muted px-1 py-0.5 rounded text-xs">src/lib/notifications.ts</code>.
|
|
|
|
|
+ Todas las notificaciones son type-safe, consistentes y fáciles de mantener.
|
|
|
|
|
+ Para más información, consulta la documentación en <code className="bg-muted px-1 py-0.5 rounded text-xs">docs/TOAST_REFACTORING.md</code>.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </AuthenticatedLayout>
|
|
|
|
|
+ )
|
|
|
|
|
+}
|