feat: redesign + avatars + theme + call presence fixes (v0.6.0)
Visual:
- Light/dark theme via ThemeContext + CSS vars
- New design tokens (surface/fg/accent/line/etc.) across all pages
- Reusable Avatar component (img + letter fallback) wired into UserBar,
ConversationHeader, ChatsPage, FriendsPage, GroupInfoPanel, IncomingCallPanel
Calls:
- Split call UI: IncomingCallPanel, InCallPanel, CallControls,
CallParticipantTile, ScreenShareViewer
- Active speaker hook (useActiveSpeakers)
- Fix: ActiveCallBanner stayed hidden after hangup while peers in room.
- useCallPresence: bind presence callbacks only when we own subscribe
(Supabase forbids .on() after .subscribe() on shared dedup'd channels)
- useCallPresence: never removeChannel — channel is shared with CallContext
so tearing it down on ConversationHeader unmount killed live tracking
- ActiveCallBanner: lastCallConversationId fallback so banner shows
instantly after hangup, auto-dismiss when room confirmed empty
- Drop unused useAnyActiveCall
Bump tauri version 0.5.0 -> 0.6.0
This commit is contained in:
@@ -5,22 +5,19 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { Avatar } from './Avatar';
|
||||
import { ChevronDownIcon } from './icons';
|
||||
|
||||
const PRESENCE_OPTIONS: PresenceState[] = ['online', 'idle', 'dnd', 'invisible', 'offline'];
|
||||
|
||||
const PRESENCE_DOT: Record<PresenceState, string> = {
|
||||
online: 'bg-emerald-400',
|
||||
online: 'bg-emerald-500',
|
||||
idle: 'bg-amber-400',
|
||||
dnd: 'bg-rose-500',
|
||||
invisible: 'bg-neutral-500',
|
||||
offline: 'bg-neutral-600',
|
||||
offline: 'bg-neutral-400 dark:bg-neutral-600',
|
||||
};
|
||||
|
||||
function avatarLetter(input: string | undefined): string {
|
||||
return (input ?? '?').trim().charAt(0).toUpperCase() || '?';
|
||||
}
|
||||
|
||||
export function UserBar() {
|
||||
const { t } = useTranslation(['app']);
|
||||
const { profile, refreshProfile } = useAuth();
|
||||
@@ -53,34 +50,38 @@ export function UserBar() {
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
className="flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-left transition hover:bg-white/5 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/40"
|
||||
className="flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-left transition hover:bg-surface-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
|
||||
>
|
||||
<div className="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-brand-500/30 text-sm font-semibold text-white ring-1 ring-brand-400/30">
|
||||
{avatarLetter(profile?.displayName ?? profile?.username)}
|
||||
<div className="relative">
|
||||
<Avatar
|
||||
url={profile?.avatarUrl ?? null}
|
||||
displayName={profile?.displayName ?? profile?.username}
|
||||
className="h-9 w-9 text-sm"
|
||||
/>
|
||||
<span
|
||||
className={
|
||||
'absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full ring-2 ring-ink-900 ' +
|
||||
'absolute -bottom-0.5 -right-0.5 h-3 w-3 rounded-full ring-2 ring-surface-2 ' +
|
||||
PRESENCE_DOT[presence]
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-white">
|
||||
<p className="truncate text-sm font-medium text-fg">
|
||||
{profile?.displayName ?? '—'}
|
||||
</p>
|
||||
<p className="truncate text-xs text-neutral-400">
|
||||
{profile?.username ? '@' + profile.username : '—'}
|
||||
<p className="truncate text-xs text-fg-muted">
|
||||
{t('app:presence.' + presence)}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronDownIcon
|
||||
className={'h-4 w-4 text-neutral-500 transition ' + (open ? 'rotate-180' : '')}
|
||||
className={'h-4 w-4 text-fg-muted transition ' + (open ? 'rotate-180' : '')}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div
|
||||
role="menu"
|
||||
className="absolute bottom-full left-0 right-0 mb-2 overflow-hidden rounded-lg border border-white/10 bg-ink-900/95 shadow-xl backdrop-blur-xl"
|
||||
className="absolute bottom-full left-0 right-0 mb-2 overflow-hidden rounded-lg border border-line bg-surface-3 shadow-xl"
|
||||
>
|
||||
{PRESENCE_OPTIONS.map((opt) => (
|
||||
<button
|
||||
@@ -90,12 +91,12 @@ export function UserBar() {
|
||||
aria-checked={opt === presence}
|
||||
onClick={() => void changePresence(opt)}
|
||||
disabled={busy}
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-2 text-left text-sm text-neutral-200 transition hover:bg-white/5 disabled:opacity-50"
|
||||
className="flex w-full cursor-pointer items-center gap-3 px-3 py-2 text-left text-sm text-fg transition hover:bg-surface-2 disabled:opacity-50"
|
||||
>
|
||||
<span className={'h-2.5 w-2.5 rounded-full ' + PRESENCE_DOT[opt]} />
|
||||
<span className="flex-1">{t('app:presence.' + opt)}</span>
|
||||
{opt === presence && (
|
||||
<span className="text-xs text-brand-300">●</span>
|
||||
<span className="text-xs text-accent">●</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user