112 lines
4.3 KiB
JavaScript
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(', ')}`);
|
|
}
|
|
};
|
|
};
|