114 lines
2.9 KiB
TypeScript
114 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Toast } from '@/ui/Toast';
|
|
import { Text } from '@/ui/Text';
|
|
import { Box } from '@/ui/Box';
|
|
import { Group } from '@/ui/Group';
|
|
import {
|
|
AlertTriangle,
|
|
Bell,
|
|
ExternalLink,
|
|
Flag,
|
|
Shield,
|
|
Trophy,
|
|
Users,
|
|
Vote,
|
|
} from 'lucide-react';
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
import type { Notification } from './notificationTypes';
|
|
|
|
interface ToastNotificationProps {
|
|
notification: Notification;
|
|
onDismiss: (notification: Notification) => void;
|
|
onRead: (notification: Notification) => void;
|
|
onNavigate?: (href: string) => void;
|
|
autoHideDuration?: number;
|
|
}
|
|
|
|
const notificationIcons: Record<string, typeof Bell> = {
|
|
protest_filed: AlertTriangle,
|
|
protest_defense_requested: Shield,
|
|
protest_vote_required: Vote,
|
|
penalty_issued: AlertTriangle,
|
|
race_results_posted: Trophy,
|
|
league_invite: Users,
|
|
race_reminder: Flag,
|
|
};
|
|
|
|
export function ToastNotification({
|
|
notification,
|
|
onDismiss,
|
|
onRead,
|
|
onNavigate,
|
|
autoHideDuration = 5000,
|
|
}: ToastNotificationProps) {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
const [isExiting, setIsExiting] = useState(false);
|
|
const [progress, setProgress] = useState(100);
|
|
|
|
const handleDismiss = useCallback(() => {
|
|
setIsExiting(true);
|
|
setTimeout(() => {
|
|
onDismiss(notification);
|
|
}, 300);
|
|
}, [notification, onDismiss]);
|
|
|
|
useEffect(() => {
|
|
const showTimeout = setTimeout(() => setIsVisible(true), 10);
|
|
|
|
const startTime = Date.now();
|
|
const interval = setInterval(() => {
|
|
const elapsed = Date.now() - startTime;
|
|
const remaining = Math.max(0, 100 - (elapsed / autoHideDuration) * 100);
|
|
setProgress(remaining);
|
|
if (remaining === 0) {
|
|
clearInterval(interval);
|
|
handleDismiss();
|
|
}
|
|
}, 10);
|
|
|
|
return () => {
|
|
clearTimeout(showTimeout);
|
|
clearInterval(interval);
|
|
};
|
|
}, [autoHideDuration, handleDismiss]);
|
|
|
|
const handleClick = () => {
|
|
onRead(notification);
|
|
if (notification.actionUrl && onNavigate) {
|
|
onNavigate(notification.actionUrl);
|
|
}
|
|
handleDismiss();
|
|
};
|
|
|
|
const NotificationIcon = notificationIcons[notification.type] || Bell;
|
|
|
|
return (
|
|
<Toast
|
|
title={notification.title ?? 'Notification'}
|
|
onClose={handleDismiss}
|
|
icon={<Icon icon={NotificationIcon} size={5} intent="primary" />}
|
|
isVisible={isVisible}
|
|
isExiting={isExiting}
|
|
progress={progress}
|
|
>
|
|
<Text size="xs" variant="low" lineClamp={2}>
|
|
{notification.message}
|
|
</Text>
|
|
{notification.actionUrl && (
|
|
<Box marginTop={2}>
|
|
<Box onClick={handleClick} style={{ cursor: 'pointer' }}>
|
|
<Group gap={1}>
|
|
<Text size="xs" weight="medium" variant="primary">
|
|
View details
|
|
</Text>
|
|
<Icon icon={ExternalLink} size={3} intent="primary" />
|
|
</Group>
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
</Toast>
|
|
);
|
|
}
|