From 04d9909e92716ef6d2e5c734ce30b8921ec6d664 Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 19:13:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(P3.T6):=20Settings=20'Ger=C3=A4te'=20tab?= =?UTF-8?q?=20with=20revoke=20+=20this-device=20badge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/settings/DeviceListTab.tsx | 126 ++++++++++++++++++ apps/desktop/src/pages/SettingsPage.tsx | 15 ++- 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/components/settings/DeviceListTab.tsx diff --git a/apps/desktop/src/components/settings/DeviceListTab.tsx b/apps/desktop/src/components/settings/DeviceListTab.tsx new file mode 100644 index 0000000..bc75727 --- /dev/null +++ b/apps/desktop/src/components/settings/DeviceListTab.tsx @@ -0,0 +1,126 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useOwnDevices } from '../../hooks/useOwnDevices'; +import { getDeviceRowId } from '../../lib/deviceRowId'; +import { SpinnerIcon } from '../icons'; + +function formatLastSeen(iso: string, locale: string): string { + try { + const d = new Date(iso); + return d.toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' }); + } catch { + return iso; + } +} + +export function DeviceListTab() { + const { t, i18n } = useTranslation(); + const { devices, loading, error, revoke } = useOwnDevices(); + const ownId = getDeviceRowId(); + const [busy, setBusy] = useState(null); + const [opError, setOpError] = useState(null); + + const handleRevoke = async (id: string) => { + setOpError(null); + setBusy(id); + try { + await revoke(id); + } catch (err) { + setOpError(err instanceof Error ? err.message : 'revoke failed'); + } finally { + setBusy(null); + } + }; + + if (loading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +

+ {t('app:settings.devices.error', { defaultValue: 'Geräteliste konnte nicht geladen werden.' })} +

+ ); + } + + if (devices.length === 0) { + return ( +

+ {t('app:settings.devices.empty', { defaultValue: 'Noch keine Geräte angemeldet.' })} +

+ ); + } + + return ( +
+ {opError && ( +

+ {opError} +

+ )} +
    + {devices.map((d) => { + const isOwn = d.id === ownId; + const isRevoked = d.revokedAt !== null; + return ( +
  • +
    +
    + {d.name} + {isOwn && ( + + {t('app:settings.devices.this_device', { defaultValue: 'Dieses Gerät' })} + + )} + {isRevoked && ( + + {t('app:settings.devices.revoked', { defaultValue: 'Abgemeldet' })} + + )} +
    +

    + {d.platform} · {t('app:settings.devices.last_seen', { defaultValue: 'zuletzt' })}{' '} + {formatLastSeen(d.lastSeenAt, i18n.language)} +

    +
    + {isOwn ? ( + + ) : isRevoked ? ( + + {t('app:settings.devices.already_revoked', { defaultValue: '—' })} + + ) : ( + + )} +
  • + ); + })} +
+
+ ); +} diff --git a/apps/desktop/src/pages/SettingsPage.tsx b/apps/desktop/src/pages/SettingsPage.tsx index 49b4da0..5a7b991 100644 --- a/apps/desktop/src/pages/SettingsPage.tsx +++ b/apps/desktop/src/pages/SettingsPage.tsx @@ -23,6 +23,7 @@ import { import { MicTestSection } from '../components/MicTestSection'; import { NotificationSoundSettings } from '../components/NotificationSoundSettings'; import { RingtoneSettings } from '../components/RingtoneSettings'; +import { DeviceListTab } from '../components/settings/DeviceListTab'; import { SecurityCenter } from '../components/SecurityCenter'; import { SoundboardSettings } from '../components/SoundboardSettings'; import { useAuth } from '../context/AuthContext'; @@ -117,7 +118,7 @@ export function SettingsPage() { // routing conflict with HashRouter. type TabId = | 'profile' | 'appearance' | 'privacy' | 'notifications' - | 'voice' | 'screen-share' | 'soundboard' | 'security' | 'account'; + | 'voice' | 'screen-share' | 'soundboard' | 'security' | 'devices' | 'account'; const tabs: Array<{ id: TabId; label: string; Icon: typeof UsersIcon }> = [ { id: 'profile', label: t('app:settings.nav_profile', { defaultValue: 'Profil' }), Icon: UsersIcon }, @@ -128,6 +129,7 @@ export function SettingsPage() { { id: 'screen-share', label: t('app:settings.nav_screen_share', { defaultValue: 'Bildschirmfreigabe' }), Icon: MonitorShareIcon }, { id: 'soundboard', label: t('app:settings.nav_soundboard', { defaultValue: 'Soundboard' }), Icon: MusicIcon }, { id: 'security', label: t('app:settings.nav_security', { defaultValue: 'Sicherheit' }), Icon: LockIcon }, + { id: 'devices', label: t('app:settings.nav_devices', { defaultValue: 'Geräte' }), Icon: MonitorShareIcon }, { id: 'account', label: t('app:settings.nav_account', { defaultValue: 'Konto' }), Icon: SignOutIcon }, ]; @@ -352,6 +354,17 @@ export function SettingsPage() { )} + {activeTab === 'devices' && ( +
+ +
+ )} + {activeTab === 'account' && (