222 lines
5.7 KiB
TypeScript
222 lines
5.7 KiB
TypeScript
"use client";
|
|
|
|
import React, { createContext, useContext, useState, useCallback, useEffect } from "react";
|
|
import { toast } from "sonner";
|
|
import type { Notification } from "@/lib/types";
|
|
import { notificationsApi } from "@/lib/api";
|
|
import { useAuth } from "@/contexts/AuthContext";
|
|
|
|
interface NotificationContextType {
|
|
notifications: Notification[];
|
|
addNotification: (
|
|
notification: Omit<Notification, "id" | "createdAt" | "read">
|
|
) => void;
|
|
markAsRead: (id: string) => void;
|
|
markAllAsRead: () => void;
|
|
removeNotification: (id: string) => void;
|
|
clearAllNotifications: () => void;
|
|
unreadCount: number;
|
|
}
|
|
|
|
const NotificationContext = createContext<NotificationContextType | undefined>(
|
|
undefined
|
|
);
|
|
|
|
export function NotificationProvider({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
const { user, loading } = useAuth();
|
|
|
|
useEffect(() => {
|
|
const loadNotifications = async () => {
|
|
const hasLocalSession =
|
|
typeof window !== "undefined" &&
|
|
Boolean(
|
|
localStorage.getItem("job-portal-auth") ||
|
|
localStorage.getItem("auth_token") ||
|
|
localStorage.getItem("token")
|
|
);
|
|
|
|
if (loading) {
|
|
return;
|
|
}
|
|
|
|
if (!user || !hasLocalSession) {
|
|
setNotifications([]);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = await notificationsApi.list();
|
|
setNotifications(data || []);
|
|
} catch (error: any) {
|
|
// Silently handle 401 errors - user is not authenticated
|
|
if (error?.status === 401 || error?.silent) {
|
|
return;
|
|
}
|
|
// Only log other errors in development
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.debug("Could not load notifications:", error?.message || 'Unknown error');
|
|
}
|
|
setNotifications([]);
|
|
}
|
|
};
|
|
loadNotifications();
|
|
}, [loading, user]);
|
|
|
|
const addNotification = useCallback(
|
|
(notification: Omit<Notification, "id" | "createdAt" | "read">) => {
|
|
const newNotification: Notification = {
|
|
...notification,
|
|
id: Date.now().toString(),
|
|
createdAt: new Date().toISOString(),
|
|
read: false,
|
|
};
|
|
|
|
setNotifications((prev) => [newNotification, ...prev]);
|
|
|
|
// Show toast notification
|
|
switch (notification.type) {
|
|
case "success":
|
|
toast.success(notification.title, {
|
|
description: notification.message,
|
|
});
|
|
break;
|
|
case "error":
|
|
toast.error(notification.title, {
|
|
description: notification.message,
|
|
});
|
|
break;
|
|
case "warning":
|
|
toast.warning(notification.title, {
|
|
description: notification.message,
|
|
});
|
|
break;
|
|
default:
|
|
toast.info(notification.title, {
|
|
description: notification.message,
|
|
});
|
|
break;
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
const markAsRead = useCallback((id: string) => {
|
|
setNotifications((prev) =>
|
|
prev.map((notification) =>
|
|
notification.id === id ? { ...notification, read: true } : notification
|
|
)
|
|
);
|
|
}, []);
|
|
|
|
const markAllAsRead = useCallback(() => {
|
|
setNotifications((prev) =>
|
|
prev.map((notification) => ({ ...notification, read: true }))
|
|
);
|
|
}, []);
|
|
|
|
const removeNotification = useCallback((id: string) => {
|
|
setNotifications((prev) =>
|
|
prev.filter((notification) => notification.id !== id)
|
|
);
|
|
}, []);
|
|
|
|
const clearAllNotifications = useCallback(() => {
|
|
setNotifications([]);
|
|
}, []);
|
|
|
|
const unreadCount = notifications.filter((n) => !n.read).length;
|
|
|
|
return (
|
|
<NotificationContext.Provider
|
|
value={{
|
|
notifications,
|
|
addNotification,
|
|
markAsRead,
|
|
markAllAsRead,
|
|
removeNotification,
|
|
clearAllNotifications,
|
|
unreadCount,
|
|
}}
|
|
>
|
|
{children}
|
|
</NotificationContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useNotifications() {
|
|
const context = useContext(NotificationContext);
|
|
if (context === undefined) {
|
|
throw new Error(
|
|
"useNotifications must be used within a NotificationProvider"
|
|
);
|
|
}
|
|
return context;
|
|
}
|
|
|
|
// Hook personalizado para facilitar o uso
|
|
export function useNotify() {
|
|
const { addNotification } = useNotifications();
|
|
|
|
return React.useMemo(() => ({
|
|
success: (
|
|
title: string,
|
|
message: string,
|
|
options?: { actionUrl?: string; actionLabel?: string; userId?: string }
|
|
) =>
|
|
addNotification({
|
|
title,
|
|
message,
|
|
type: "success",
|
|
userId: options?.userId || "current",
|
|
actionUrl: options?.actionUrl,
|
|
actionLabel: options?.actionLabel,
|
|
}),
|
|
|
|
error: (
|
|
title: string,
|
|
message: string,
|
|
options?: { actionUrl?: string; actionLabel?: string; userId?: string }
|
|
) =>
|
|
addNotification({
|
|
title,
|
|
message,
|
|
type: "error",
|
|
userId: options?.userId || "current",
|
|
actionUrl: options?.actionUrl,
|
|
actionLabel: options?.actionLabel,
|
|
}),
|
|
|
|
warning: (
|
|
title: string,
|
|
message: string,
|
|
options?: { actionUrl?: string; actionLabel?: string; userId?: string }
|
|
) =>
|
|
addNotification({
|
|
title,
|
|
message,
|
|
type: "warning",
|
|
userId: options?.userId || "current",
|
|
actionUrl: options?.actionUrl,
|
|
actionLabel: options?.actionLabel,
|
|
}),
|
|
|
|
info: (
|
|
title: string,
|
|
message: string,
|
|
options?: { actionUrl?: string; actionLabel?: string; userId?: string }
|
|
) =>
|
|
addNotification({
|
|
title,
|
|
message,
|
|
type: "info",
|
|
userId: options?.userId || "current",
|
|
actionUrl: options?.actionUrl,
|
|
actionLabel: options?.actionLabel,
|
|
}),
|
|
}), [addNotification]);
|
|
}
|