profile-window.svelte RawHistoryFolder
1<script lang="ts">
2 import { useQuery } from 'convex-svelte';
3 import { api } from '../../../convex/_generated/api';
4 import type { Id } from '../../../convex/_generated/dataModel';
5 import XpWindow from './xp-window.svelte';
6 import { desktop } from '$lib/states/desktop.svelte';
7 import { chatState } from '$lib/states/chat.svelte';
8 import { useAimClient } from '$lib/aim/client';
9 import { STATUS_LABELS_FR } from '$lib/types/aim';
10 import { formatFrenchDateTime } from '$lib/utils/date-format';
11
12 let { userId, nickname } = $props<{ userId: Id<'users'>; nickname: string }>();
13 const id = $derived(`profile-${userId}`);
14 const profileQuery = useQuery(api.aim.getUserProfile, () => ({ userId }));
15 const client = useAimClient();
16 const profile = $derived(profileQuery.data);
17 const own = $derived(userId === chatState.getCurrentUser()?.id);
18 let text = $state('');
19 let saved = $state(false);
20 let saving = $state(false);
21 let error = $state('');
22 let initializedFor = $state<string | null>(null);
23 $effect(() => {
24 if (profile && initializedFor !== userId) {
25 text = profile.profile ?? '';
26 initializedFor = userId;
27 }
28 });
29
30 async function save() {
31 saving = true;
32 saved = false;
33 error = '';
34 try {
35 await client.updateProfile({ profile: text });
36 saved = true;
37 } catch (err) {
38 error = err instanceof Error ? err.message : 'Impossible d’enregistrer.';
39 } finally {
40 saving = false;
41 }
42 }
43</script>
44
45<XpWindow {id} width={380} height={400} minWidth={300} minHeight={260}>
46 <div class="profile-content">
47 {#if profile}
48 <div class="identity">
49 <img class="buddy-icon" src="/aim/running-man-48.png" alt="" width="48" height="48" />
50 <div>
51 <strong>{profile.nickname}</strong>
52 <div class="status">
53 <img
54 src={`/aim/status-${profile.status}.svg`}
55 alt=""
56 width="16"
57 height="16"
58 />{STATUS_LABELS_FR[profile.status]}
59 </div>
60 {#if profile.createdAt}<small
61 >Membre depuis {formatFrenchDateTime(new Date(profile.createdAt))}</small
62 >{/if}
63 </div>
64 </div>
65 {#if profile.awayMessage}<div class="away-message">{profile.awayMessage}</div>{/if}
66 <label for="profile-text">Profil</label>
67 {#if own}
68 <textarea
69 id="profile-text"
70 class="sunken-panel"
71 maxlength="1000"
72 bind:value={text}
73 oninput={() => (saved = false)}
74 ></textarea>
75 <small>{text.length}/1000</small>
76 {:else}
77 <div id="profile-text" class="sunken-panel profile-text">
78 {profile.profile ?? 'Aucun profil pour le moment.'}
79 </div>
80 {/if}
81 {:else if profile === null}
82 <p>Profil introuvable : {nickname}</p>
83 {:else}
84 <p>Chargement du profil…</p>
85 {/if}
86 <div class="actions">
87 {#if error}<span role="alert">{error}</span>{/if}
88 {#if saved}<span role="status">Enregistré ✓</span>{/if}
89 {#if own && profile}<button disabled={saving || text.length > 1000} onclick={save}
90 >Enregistrer</button
91 >{/if}
92 <button onclick={() => desktop.close(id)}>Fermer</button>
93 </div>
94 </div>
95</XpWindow>
96
97<style>
98 .profile-content {
99 display: flex;
100 flex-direction: column;
101 gap: 6px;
102 height: 100%;
103 overflow: auto;
104 font:
105 11px Tahoma,
106 'Pixelated MS Sans Serif',
107 sans-serif;
108 }
109 .identity {
110 display: flex;
111 align-items: center;
112 gap: 10px;
113 }
114 .buddy-icon {
115 flex: 0 0 48px;
116 }
117 .status {
118 display: flex;
119 align-items: center;
120 gap: 4px;
121 }
122 .away-message {
123 border: 1px solid #d0c7a0;
124 background: #fff9dc;
125 padding: 6px;
126 white-space: pre-wrap;
127 }
128 .sunken-panel {
129 background: white;
130 padding: 5px;
131 box-sizing: border-box;
132 width: 100%;
133 min-height: 65px;
134 font: inherit;
135 }
136 .profile-text {
137 flex: 1;
138 overflow: auto;
139 white-space: pre-wrap;
140 overflow-wrap: anywhere;
141 }
142 textarea {
143 resize: vertical;
144 }
145 .actions {
146 margin-top: auto;
147 display: flex;
148 justify-content: flex-end;
149 align-items: center;
150 gap: 6px;
151 }
152 .actions span[role='alert'] {
153 color: #a00;
154 }
155</style>