testes/src_2/features/prafrot/components/FeedbackNotification.jsx

112 lines
4.3 KiB
JavaScript

import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { CheckCircle2, AlertCircle, Info, XCircle, X } from 'lucide-react';
import { create } from 'zustand';
// Store to manage global feedback state
export const useFeedbackStore = create((set) => ({
notifications: [],
notify: (type, title, message) => {
const id = Date.now();
set((state) => ({
notifications: [...state.notifications, { id, type, title, message }]
}));
// Auto remove after 5s
setTimeout(() => {
set((state) => ({
notifications: state.notifications.filter(n => n.id !== id)
}));
}, 5000);
},
remove: (id) => set((state) => ({
notifications: state.notifications.filter(n => n.id !== id)
}))
}));
const icons = {
success: <CheckCircle2 className="w-6 h-6 text-emerald-500" />,
error: <XCircle className="w-6 h-6 text-rose-500" />,
warning: <AlertCircle className="w-6 h-6 text-amber-500" />,
info: <Info className="w-6 h-6 text-blue-500" />
};
const colors = {
success: "border-emerald-500/20 bg-emerald-50/50 dark:bg-emerald-500/10",
error: "border-rose-500/20 bg-rose-50/50 dark:bg-rose-500/10",
warning: "border-amber-500/20 bg-amber-50/50 dark:bg-amber-500/10",
info: "border-blue-500/20 bg-blue-50/50 dark:bg-blue-500/10"
};
export const FeedbackContainer = () => {
const { notifications, remove } = useFeedbackStore();
return (
<div className="fixed top-6 right-6 z-[9999] flex flex-col gap-3 pointer-events-none">
<AnimatePresence>
{notifications.map((n) => (
<motion.div
key={n.id}
initial={{ opacity: 0, x: 50, scale: 0.9 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
exit={{ opacity: 0, scale: 0.9, transition: { duration: 0.2 } }}
className={`pointer-events-auto min-w-[320px] max-w-[400px] p-4 rounded-2xl border backdrop-blur-md shadow-2xl flex gap-4 relative overflow-hidden group ${colors[n.type]}`}
>
{/* Background Glow */}
<div className={`absolute -right-4 -top-4 w-24 h-24 blur-3xl opacity-20 group-hover:opacity-40 transition-opacity ${n.type === 'success' ? 'bg-emerald-500' : n.type === 'error' ? 'bg-rose-500' : 'bg-blue-500'}`}></div>
<div className="flex-shrink-0 mt-0.5">
{icons[n.type]}
</div>
<div className="flex-1 flex flex-col gap-1 pr-6">
<h4 className="text-sm font-black uppercase tracking-tight text-slate-800 dark:text-white">
{n.title}
</h4>
<p className="text-xs font-medium text-slate-500 dark:text-stone-400 leading-relaxed">
{n.message}
</p>
</div>
<button
onClick={() => remove(n.id)}
className="absolute top-3 right-3 p-1 rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
>
<X className="w-3 h-3 text-slate-400" />
</button>
{/* Progress Bar (Auto-expire) */}
<motion.div
initial={{ scaleX: 1 }}
animate={{ scaleX: 0 }}
transition={{ duration: 5, ease: "linear" }}
className={`absolute bottom-0 left-0 right-0 h-1 origin-left ${n.type === 'success' ? 'bg-emerald-500' : n.type === 'error' ? 'bg-rose-500' : 'bg-blue-500'}`}
/>
</motion.div>
))}
</AnimatePresence>
</div>
);
};
export const useFeedback = () => {
const notify = useFeedbackStore(s => s.notify);
return {
success: (title, message) => notify('success', title || 'Sucesso!', message),
error: (title, message) => notify('error', title || 'Ops!', message),
warning: (title, message) => notify('warning', title || 'Atenção!', message),
info: (title, message) => notify('info', title || 'Informação', message),
// Friendly back-end error parser
handleBackendError: (error) => {
console.error(error);
const msg = error.response?.data?.message || error.message || 'Erro desconhecido na comunicação com o servidor.';
notify('error', 'Erro do Sistema', msg);
},
notifyFields: (fields) => {
notify('warning', 'Campos Obrigatórios', `Por favor, preencha: ${fields.join(', ')}`);
}
};
};