upgrade to tamagui v2 and guest pwa overhaul
This commit is contained in:
200
resources/js/guest-v2/components/NotificationSheet.tsx
Normal file
200
resources/js/guest-v2/components/NotificationSheet.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
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 { useAppearance } from '@/hooks/use-appearance';
|
||||
|
||||
type NotificationSheetProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
};
|
||||
|
||||
export default function NotificationSheet({ open, onOpenChange }: NotificationSheetProps) {
|
||||
const { t } = useTranslation();
|
||||
const center = useOptionalNotificationCenter();
|
||||
const { resolved } = useAppearance();
|
||||
const isDark = resolved === 'dark';
|
||||
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 { resolved } = useAppearance();
|
||||
const isDark = resolved === 'dark';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user