feat: backup/restore, user profile popover, image compress, video blur, wake lock

- backup/restore dialog + user profile popover components
- image compression, video blur, wake lock utilities
- message cache + conversation messages hook refinements
- call context, active speakers, screen share dialog tweaks
- audio + screen share settings persistence
- refreshed app icons (smaller sizes) across all platforms
This commit is contained in:
2026-04-21 12:11:09 +02:00
parent 48ac9d2922
commit 1303c8e26f
71 changed files with 1077 additions and 114 deletions
@@ -0,0 +1,86 @@
import { useEffect } from 'react';
import { DeviceRestore } from './DeviceRestore';
import { AlertIcon, ShieldIcon, XIcon } from './icons';
interface Props {
open: boolean;
userId: string;
onClose: () => void;
}
// Modal wrapper around DeviceRestore for the already-signed-in case. A
// successful restore swaps the local device-identity for the one embedded
// in the backup string — the app then hard-reloads so every hook
// re-initialises against the restored keys (simpler than invalidating
// supabase-realtime subscriptions, stronghold caches, livekit rooms, etc.
// individually).
export function BackupRestoreDialog({ open, userId, onClose }: Props) {
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
window.removeEventListener('keydown', onKey);
document.body.style.overflow = prev;
};
}, [open, onClose]);
if (!open) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-label="Backup wiederherstellen"
className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/60 p-6 backdrop-blur-sm"
onClick={onClose}
>
<div
onClick={(e) => e.stopPropagation()}
className="flex w-full max-w-md flex-col gap-4"
>
<div className="flex items-center justify-between rounded-lg border border-white/10 bg-ink-900/70 p-3 backdrop-blur-xl">
<div className="flex items-center gap-2">
<ShieldIcon className="h-4 w-4 text-brand-300" />
<h3 className="text-sm font-semibold text-white">
Gerät aus Backup wiederherstellen
</h3>
</div>
<button
type="button"
onClick={onClose}
aria-label="Schließen"
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-neutral-400 transition hover:bg-white/10 hover:text-white"
>
<XIcon className="h-4 w-4" />
</button>
</div>
<div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-xs text-amber-100">
<AlertIcon className="mt-0.5 h-4 w-4 shrink-0 text-amber-300" />
<p className="min-w-0 flex-1">
Restore ersetzt das aktuelle Gerät durch das aus dem Backup.
Die App lädt danach neu. Nachrichten, die auf diesem Gerät seit
dem Backup eingegangen sind, sind erst wieder lesbar, nachdem
Peer-Geräte den Conversation-Key erneut für die wiederhergestellte
Device-ID wrappen.
</p>
</div>
<DeviceRestore
userId={userId}
onRestored={() => {
// Hard reload — cleanest way to reset every hook, supabase
// realtime channel, stronghold handle, and cached state.
window.location.reload();
}}
/>
</div>
</div>
);
}