import { ActivityIndicator, Alert, Platform, Pressable, ScrollView, Text, TouchableOpacity, View, } from 'react-native'; import { useEffect, useState } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { MenuView } from '@react-native-menu/menu'; import { useTranslation } from 'react-i18next'; import { useColors } from '../lib/theme'; import { useDevicesStore, type UserDevice } from '../stores/devices'; import { useProtectedDevicesStore, type ProtectedDevice } from '../stores/protectedDevices'; import { useUserPlan } from '../hooks/useUserPlan'; import { AppHeader } from '../components/AppHeader'; import { AddMacSheet } from '../components/devices/AddMacSheet'; // ─── Helpers ───────────────────────────────────────────────────────────────── function mobileIcon(platform: string): React.ComponentProps['name'] { if (platform === 'ios') return 'logo-apple'; if (platform === 'android') return 'logo-android'; return 'phone-portrait-outline'; } function protectedDeviceIcon(platform: string): React.ComponentProps['name'] { if (platform === 'mac') return 'laptop-outline'; if (platform === 'windows') return 'desktop-outline'; return 'globe-outline'; } function formatLastSeen(iso: string, t: (k: string, o?: any) => string): string { const ms = Date.now() - new Date(iso).getTime(); const min = Math.floor(ms / 60_000); if (min < 1) return t('settings.devices_just_now'); if (min < 60) return t('settings.devices_mins_ago', { count: min }); const hr = Math.floor(min / 60); if (hr < 24) return t('settings.devices_hours_ago', { count: hr }); const day = Math.floor(hr / 24); if (day < 30) return t('settings.devices_days_ago', { count: day }); return new Date(iso).toLocaleDateString(Platform.OS === 'ios' ? undefined : 'de-DE', { day: '2-digit', month: 'short', year: 'numeric', }); } function formatSince(iso: string): string { return new Date(iso).toLocaleDateString(Platform.OS === 'ios' ? undefined : 'de-DE', { day: '2-digit', month: 'short', year: 'numeric', }); } // ─── Status Badge ───────────────────────────────────────────────────────────── function StatusBadge({ status }: { status: ProtectedDevice['status'] }) { const { t } = useTranslation(); const colors = useColors(); const config = { pending: { label: t('devices.status_pending'), bg: 'rgba(245,158,11,0.12)', fg: '#f59e0b', }, active: { label: t('devices.status_active'), bg: colors.success + '1a', fg: colors.success, }, revoked: { label: t('devices.status_revoked'), bg: 'rgba(0,0,0,0.06)', fg: colors.textMuted, }, }[status] ?? { label: status, bg: 'rgba(0,0,0,0.06)', fg: colors.textMuted, }; return ( {config.label} ); } // ─── Mobile Device Row (existing) ──────────────────────────────────────────── function MobileDeviceRow({ device, onRemove, }: { device: UserDevice; onRemove: (id: string) => void; }) { const { t } = useTranslation(); const colors = useColors(); function confirmRemove() { Alert.alert( t('settings.devices_remove_title'), t('settings.devices_remove_desc'), [ { text: t('common.cancel'), style: 'cancel' }, { text: t('settings.devices_remove_confirm'), style: 'destructive', onPress: () => onRemove(device.id), }, ] ); } return ( {device.name ?? device.model ?? device.platform} {device.isCurrent ? ( {t('settings.devices_this_device')} ) : null} {device.model && device.name && !device.name.includes(device.model) ? ( {device.model} ) : null} {formatLastSeen(device.lastSeenAt, t)} {t('settings.devices_since')} {formatSince(device.createdAt)} {!device.isCurrent ? ( ({ opacity: pressed ? 0.5 : 1 })} > ) : null} ); } // ─── Protected Device Row ──────────────────────────────────────────────────── function ProtectedDeviceRow({ device, onRemove, }: { device: ProtectedDevice; onRemove: (id: string) => void; }) { const { t } = useTranslation(); const colors = useColors(); const menuActions = device.status === 'pending' ? [ { id: 'remove', title: t('settings.devices_remove_confirm'), attributes: { destructive: true } }, ] : [ { id: 'remove', title: t('settings.devices_remove_confirm'), attributes: { destructive: true } }, ]; function handleMenuSelect(id: string) { if (id === 'remove') { Alert.alert( t('devices.remove_warning_title'), t('devices.remove_warning_body'), [ { text: t('common.cancel'), style: 'cancel' }, { text: t('settings.devices_remove_confirm'), style: 'destructive', onPress: () => onRemove(device.id), }, ] ); } } return ( {device.label} {t('settings.devices_since')} {formatSince(device.createdAt)} handleMenuSelect(event)} shouldOpenOnLongPress={false} > ({ opacity: pressed ? 0.5 : 1 })} > ); } // ─── Section Card wrapper ───────────────────────────────────────────────────── function SectionCard({ children }: { children: React.ReactNode }) { const colors = useColors(); return ( {children} ); } function SectionLabel({ title }: { title: string }) { const colors = useColors(); return ( {title} ); } // ─── Screen ────────────────────────────────────────────────────────────────── export default function DevicesScreen() { const insets = useSafeAreaInsets(); const { t } = useTranslation(); const colors = useColors(); const { plan } = useUserPlan(); const isLegend = plan === 'legend'; const { devices: mobileDevices, loading: mobileLoading, loadDevices, removeDevice: removeMobileDevice, } = useDevicesStore(); const { devices: protectedDevices, loading: protectedLoading, load: loadProtected, remove: removeProtected, } = useProtectedDevicesStore(); const [addMacVisible, setAddMacVisible] = useState(false); useEffect(() => { loadDevices(); loadProtected(); }, []); const currentDevice = mobileDevices.find((d) => d.isCurrent); const subtitle = isLegend ? t('devices.subtitle_legend') : t('devices.subtitle_free'); async function handleRemoveProtected(id: string) { try { const { manualRemovalRequired } = await removeProtected(id); if (manualRemovalRequired) { Alert.alert(t('devices.remove_warning_title'), t('devices.remove_warning_body')); } } catch { Alert.alert(t('common.error'), t('common.unknown_error')); } } return ( {/* Subtitle */} {subtitle} {/* Section 1: Dieses Gerät */} {mobileLoading && !currentDevice ? ( ) : currentDevice ? ( ) : ( {t('settings.devices_empty')} )} {/* Section 2: Weitere geschützte Geräte */} {protectedLoading ? ( ) : protectedDevices.length === 0 ? ( {isLegend ? t('devices.add_mac') : t('devices.subtitle_free')} ) : ( protectedDevices.map((device, i) => ( )) )} {/* CTA or Upgrade */} {isLegend ? ( setAddMacVisible(true)} activeOpacity={0.7} style={{ backgroundColor: colors.brandOrange, borderRadius: 14, paddingVertical: 16, alignItems: 'center', flexDirection: 'row', justifyContent: 'center', gap: 8, }} > {t('devices.add_mac')} {t('devices.add_windows')} ) : ( {t('devices.subtitle_legend')} ({ backgroundColor: colors.brandOrange, borderRadius: 12, paddingVertical: 14, alignItems: 'center', opacity: pressed ? 0.7 : 1, })} > {t('devices.upgrade_cta')} )} {t('settings.devices_hint')} { setAddMacVisible(false); loadProtected(); }} /> ); }