199 lines
7.5 KiB
TypeScript
199 lines
7.5 KiB
TypeScript
import React from 'react';
|
|
import { YStack, XStack } from '@tamagui/stacks';
|
|
import { SizableText as Text } from '@tamagui/text';
|
|
import { Button } from '@tamagui/button';
|
|
import { ScrollView } from '@tamagui/scroll-view';
|
|
import { X } from 'lucide-react';
|
|
import { useOptionalNotificationCenter } from '@/guest/context/NotificationCenterContext';
|
|
import { useTranslation } from '@/guest/i18n/useTranslation';
|
|
import { useGuestThemeVariant } from '../lib/guestTheme';
|
|
|
|
type NotificationSheetProps = {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
};
|
|
|
|
export default function NotificationSheet({ open, onOpenChange }: NotificationSheetProps) {
|
|
const { t } = useTranslation();
|
|
const center = useOptionalNotificationCenter();
|
|
const { isDark } = useGuestThemeVariant();
|
|
const mutedButton = isDark ? 'rgba(248, 250, 255, 0.08)' : 'rgba(15, 23, 42, 0.06)';
|
|
const mutedButtonBorder = isDark ? 'rgba(248, 250, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)';
|
|
|
|
const notifications = center?.notifications ?? [];
|
|
const unreadCount = center?.unreadCount ?? 0;
|
|
const uploadCount = (center?.queueCount ?? 0) + (center?.pendingCount ?? 0);
|
|
|
|
return (
|
|
<>
|
|
<YStack
|
|
position="fixed"
|
|
top={0}
|
|
right={0}
|
|
bottom={0}
|
|
left={0}
|
|
zIndex={1200}
|
|
pointerEvents={open ? 'auto' : 'none'}
|
|
style={{
|
|
backgroundColor: isDark ? 'rgba(15, 23, 42, 0.45)' : 'rgba(15, 23, 42, 0.2)',
|
|
opacity: open ? 1 : 0,
|
|
transition: 'opacity 240ms ease',
|
|
}}
|
|
onPress={() => onOpenChange(false)}
|
|
onClick={() => onOpenChange(false)}
|
|
onMouseDown={() => onOpenChange(false)}
|
|
onTouchStart={() => onOpenChange(false)}
|
|
/>
|
|
<YStack
|
|
position="fixed"
|
|
left={0}
|
|
right={0}
|
|
bottom={0}
|
|
zIndex={1300}
|
|
padding="$4"
|
|
backgroundColor={isDark ? '#0B101E' : '#FFFFFF'}
|
|
borderTopLeftRadius="$6"
|
|
borderTopRightRadius="$6"
|
|
pointerEvents={open ? 'auto' : 'none'}
|
|
style={{
|
|
transform: open ? 'translateY(0)' : 'translateY(100%)',
|
|
opacity: open ? 1 : 0,
|
|
transition: 'transform 320ms cubic-bezier(0.22, 0.61, 0.36, 1), opacity 220ms ease',
|
|
maxHeight: '82vh',
|
|
paddingBottom: 'calc(16px + env(safe-area-inset-bottom))',
|
|
}}
|
|
>
|
|
<YStack
|
|
width={52}
|
|
height={5}
|
|
borderRadius={999}
|
|
marginBottom="$3"
|
|
alignSelf="center"
|
|
style={{ backgroundColor: isDark ? 'rgba(148, 163, 184, 0.6)' : '#CBD5E1' }}
|
|
/>
|
|
<XStack alignItems="center" justifyContent="space-between" marginBottom="$3">
|
|
<YStack gap="$1">
|
|
<Text fontSize="$6" fontFamily="$display" fontWeight="$8" color={isDark ? '#F8FAFF' : '#0F172A'}>
|
|
{t('header.notifications.title', 'Updates')}
|
|
</Text>
|
|
<Text color={isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'}>
|
|
{unreadCount > 0
|
|
? t('header.notifications.unread', { count: unreadCount }, '{count} neu')
|
|
: t('header.notifications.allRead', 'Alles gelesen')}
|
|
</Text>
|
|
</YStack>
|
|
<Button
|
|
size="$3"
|
|
circular
|
|
backgroundColor={mutedButton}
|
|
borderColor={mutedButtonBorder}
|
|
borderWidth={1}
|
|
onPress={() => onOpenChange(false)}
|
|
aria-label="Close notifications"
|
|
>
|
|
<X size={18} color={isDark ? '#F8FAFF' : '#0F172A'} />
|
|
</Button>
|
|
</XStack>
|
|
|
|
<ScrollView flex={1} showsVerticalScrollIndicator={false}>
|
|
<YStack gap="$4" paddingBottom="$2">
|
|
{center ? (
|
|
<XStack gap="$3" flexWrap="wrap">
|
|
<InfoBadge label={t('header.notifications.tabUploads', 'Uploads')} value={uploadCount} />
|
|
<InfoBadge label={t('header.notifications.tabUnread', 'Nachrichten')} value={unreadCount} />
|
|
</XStack>
|
|
) : null}
|
|
|
|
{center?.loading ? (
|
|
<Text color={isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'}>
|
|
{t('common.actions.loading', 'Loading...')}
|
|
</Text>
|
|
) : notifications.length === 0 ? (
|
|
<YStack gap="$1">
|
|
<Text color={isDark ? '#F8FAFF' : '#0F172A'} fontSize="$5" fontWeight="$7">
|
|
{t('header.notifications.emptyUnread', 'Du bist auf dem neuesten Stand!')}
|
|
</Text>
|
|
<Text color={isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'}>
|
|
{t('header.notifications.emptyStatus', 'Keine Upload-Hinweise oder Wartungen aktiv.')}
|
|
</Text>
|
|
</YStack>
|
|
) : (
|
|
<YStack gap="$3">
|
|
{notifications.map((item) => (
|
|
<YStack
|
|
key={item.id}
|
|
padding="$3"
|
|
borderRadius="$4"
|
|
backgroundColor={
|
|
item.status === 'new'
|
|
? isDark
|
|
? 'rgba(148, 163, 184, 0.18)'
|
|
: 'rgba(15, 23, 42, 0.06)'
|
|
: isDark
|
|
? 'rgba(15, 23, 42, 0.7)'
|
|
: 'rgba(255, 255, 255, 0.8)'
|
|
}
|
|
borderWidth={1}
|
|
borderColor={isDark ? 'rgba(148, 163, 184, 0.2)' : 'rgba(15, 23, 42, 0.12)'}
|
|
gap="$2"
|
|
>
|
|
<Text fontSize="$4" fontWeight="$7" color={isDark ? '#F8FAFF' : '#0F172A'}>
|
|
{item.title}
|
|
</Text>
|
|
{item.body ? (
|
|
<Text color={isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'}>
|
|
{item.body}
|
|
</Text>
|
|
) : null}
|
|
<XStack gap="$2" flexWrap="wrap">
|
|
<Button
|
|
size="$2"
|
|
backgroundColor="$primary"
|
|
color="#FFFFFF"
|
|
onPress={() => center?.markAsRead(item.id)}
|
|
>
|
|
{t('header.notifications.markRead', 'Als gelesen markieren')}
|
|
</Button>
|
|
<Button
|
|
size="$2"
|
|
backgroundColor={mutedButton}
|
|
borderColor={mutedButtonBorder}
|
|
borderWidth={1}
|
|
onPress={() => center?.dismiss(item.id)}
|
|
>
|
|
{t('header.notifications.dismiss', 'Ausblenden')}
|
|
</Button>
|
|
</XStack>
|
|
</YStack>
|
|
))}
|
|
</YStack>
|
|
)}
|
|
</YStack>
|
|
</ScrollView>
|
|
</YStack>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function InfoBadge({ label, value }: { label: string; value: number }) {
|
|
const { isDark } = useGuestThemeVariant();
|
|
|
|
return (
|
|
<YStack
|
|
padding="$3"
|
|
borderRadius="$4"
|
|
backgroundColor={isDark ? 'rgba(15, 23, 42, 0.7)' : 'rgba(255, 255, 255, 0.8)'}
|
|
borderWidth={1}
|
|
borderColor={isDark ? 'rgba(148, 163, 184, 0.2)' : 'rgba(15, 23, 42, 0.12)'}
|
|
gap="$1"
|
|
>
|
|
<Text fontSize="$2" color={isDark ? 'rgba(226, 232, 240, 0.7)' : 'rgba(15, 23, 42, 0.6)'}>
|
|
{label}
|
|
</Text>
|
|
<Text fontSize="$5" fontWeight="$7" color={isDark ? '#F8FAFF' : '#0F172A'}>
|
|
{value}
|
|
</Text>
|
|
</YStack>
|
|
);
|
|
}
|