132 lines
4.6 KiB
Svelte
132 lines
4.6 KiB
Svelte
<script lang="ts">
|
|
import { Avatar } from '$lib/components/ui';
|
|
import { createDirectMessage } from '$lib/matrix';
|
|
import { userPresence } from '$lib/stores/matrix';
|
|
import { toasts } from '$lib/stores/ui';
|
|
import { createLogger } from '$lib/utils/logger';
|
|
|
|
const log = createLogger('matrix:profile');
|
|
import type { RoomMember } from '$lib/matrix/types';
|
|
|
|
interface Props {
|
|
member: RoomMember;
|
|
onClose: () => void;
|
|
onStartDM?: (roomId: string) => void;
|
|
}
|
|
|
|
let { member, onClose, onStartDM }: Props = $props();
|
|
|
|
let isStartingDM = $state(false);
|
|
|
|
function handleKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') onClose();
|
|
}
|
|
|
|
const presence = $derived($userPresence.get(member.userId) || 'offline');
|
|
|
|
const presenceLabel = $derived({
|
|
online: { text: 'Online', color: 'text-green-400' },
|
|
offline: { text: 'Offline', color: 'text-gray-400' },
|
|
unavailable: { text: 'Away', color: 'text-yellow-400' },
|
|
}[presence]);
|
|
|
|
async function handleStartDM() {
|
|
isStartingDM = true;
|
|
try {
|
|
const roomId = await createDirectMessage(member.userId);
|
|
toasts.success(`Started DM with ${member.name}`);
|
|
onStartDM?.(roomId);
|
|
onClose();
|
|
} catch (e) {
|
|
log.error('Failed to start DM', { error: e });
|
|
toasts.error('Failed to start direct message');
|
|
} finally {
|
|
isStartingDM = false;
|
|
}
|
|
}
|
|
|
|
function getRoleBadge(powerLevel: number): { label: string; color: string; icon: string } | null {
|
|
if (powerLevel >= 100) return { label: 'Admin', color: 'bg-red-500/20 text-red-400', icon: '👑' };
|
|
if (powerLevel >= 50) return { label: 'Moderator', color: 'bg-blue-500/20 text-blue-400', icon: '🛡️' };
|
|
return null;
|
|
}
|
|
|
|
const roleBadge = $derived(getRoleBadge(member.powerLevel));
|
|
</script>
|
|
|
|
<svelte:window onkeydown={handleKeydown} />
|
|
|
|
<div
|
|
class="fixed inset-0 bg-black/60 flex items-center justify-center z-50"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="profile-title"
|
|
tabindex="-1"
|
|
onclick={onClose}
|
|
onkeydown={(e) => e.key === 'Enter' && onClose()}
|
|
>
|
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
<div
|
|
class="bg-dark rounded-2xl w-full max-w-sm mx-4 overflow-hidden"
|
|
role="document"
|
|
onclick={(e) => e.stopPropagation()}
|
|
onkeydown={(e) => e.stopPropagation()}
|
|
>
|
|
<!-- Header with gradient -->
|
|
<div class="h-24 bg-gradient-to-br from-primary/50 to-primary/20 relative">
|
|
<button
|
|
class="absolute top-3 right-3 w-8 h-8 flex items-center justify-center text-white/70 hover:text-white hover:bg-white/10 rounded-full transition-colors"
|
|
onclick={onClose}
|
|
title="Close"
|
|
>
|
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="18" y1="6" x2="6" y2="18" />
|
|
<line x1="6" y1="6" x2="18" y2="18" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Avatar -->
|
|
<div class="flex justify-center -mt-12 relative z-10">
|
|
<div class="ring-4 ring-dark rounded-full">
|
|
<Avatar src={member.avatarUrl} name={member.name} size="xl" status={presence === 'online' ? 'online' : presence === 'unavailable' ? 'away' : 'offline'} />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="p-6 pt-3 text-center">
|
|
<h2 id="profile-title" class="text-xl font-bold text-light">{member.name}</h2>
|
|
<p class="text-sm text-light/50 mt-1">{member.userId}</p>
|
|
|
|
<!-- Status -->
|
|
<div class="flex items-center justify-center gap-2 mt-3">
|
|
<span class="w-2 h-2 rounded-full {presence === 'online' ? 'bg-green-400' : presence === 'unavailable' ? 'bg-yellow-400' : 'bg-gray-400'}"></span>
|
|
<span class="text-sm {presenceLabel.color}">{presenceLabel.text}</span>
|
|
</div>
|
|
|
|
<!-- Role badge -->
|
|
{#if roleBadge}
|
|
<div class="mt-3">
|
|
<span class="inline-flex items-center gap-1 px-3 py-1 rounded-full text-xs {roleBadge.color}">
|
|
{roleBadge.icon} {roleBadge.label}
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Actions -->
|
|
<div class="mt-6 space-y-2">
|
|
<button
|
|
class="w-full px-4 py-2.5 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
onclick={handleStartDM}
|
|
disabled={isStartingDM}
|
|
>
|
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
</svg>
|
|
{isStartingDM ? 'Starting...' : 'Send Message'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|