detached ad216c7

refactor: Improve type safety and login flow in chat components

commit
ad216c719a9c53bbd689da84c4a45e9e1887dc04
author
Kit · · 2025-02-17 10:41 UTC
parent
93e8e7a
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
7 files · +35 -18
M src/lib/components/aim-login.svelte +1 -1
@@ -14,7 +14,7 @@
1414 activeTab?: 'signin' | 'signup';
1515 }
1616
17- let { showAuth, onLoginSuccess, activeTab: initialTab = 'signin' } = $props();
17+ let { showAuth = $bindable(), onLoginSuccess, activeTab: initialTab = 'signin' } = $props();
1818
1919 let activeTab = $state(initialTab);
2020
@@ -14,7 +14,7 @@
14 activeTab?: 'signin' | 'signup';14 activeTab?: 'signin' | 'signup';
15 }15 }
16 16
17- let { showAuth, onLoginSuccess, activeTab: initialTab = 'signin' } = $props();17+ let { showAuth = $bindable(), onLoginSuccess, activeTab: initialTab = 'signin' } = $props();
18 18
19 let activeTab = $state(initialTab);19 let activeTab = $state(initialTab);
20 20
M src/lib/components/chat-room.svelte +7 -2
@@ -381,6 +381,10 @@ let totalUsers = $derived(onlineUsers.length);
381381 function openSignup() {
382382 showAuth = true;
383383 }
384+
385+function handleLoginSuccess() {
386+ showAuth = false;
387+}
384388 </script>
385389
386390 {#if showChatRoom}
@@ -537,8 +541,9 @@ function openSignup() {
537541
538542 {#if showAuth}
539543 <AimLogin
540- bind:showAuth
541- activeTab={'signup' as const}
544+ bind:showAuth
545+ activeTab={'signup' as const}
546+ onLoginSuccess={handleLoginSuccess}
542547 />
543548 {/if}
544549
@@ -381,6 +381,10 @@ let totalUsers = $derived(onlineUsers.length);
381 function openSignup() {381 function openSignup() {
382 showAuth = true;382 showAuth = true;
383 }383 }
384+
385+function handleLoginSuccess() {
386+ showAuth = false;
387+}
384 </script>388 </script>
385 389
386 {#if showChatRoom}390 {#if showChatRoom}
@@ -537,8 +541,9 @@ function openSignup() {
537 541
538 {#if showAuth}542 {#if showAuth}
539 <AimLogin 543 <AimLogin
540- bind:showAuth 544+ bind:showAuth
541- activeTab={'signup' as const} 545+ activeTab={'signup' as const}
546+ onLoginSuccess={handleLoginSuccess}
542 />547 />
543 {/if}548 {/if}
544 549
M src/lib/types/chat.ts +8 -3
@@ -33,9 +33,14 @@ export type UserStatus = 'offline' | 'online' | 'away' | string;
3333 export type SafeUser = Omit<User, 'password' | 'createdAt'>;
3434
3535 // Helper function to create a safe user object
36- export function createSafeUser(user: User): SafeUser {
37- // eslint-disable-next-line @typescript-eslint/no-unused-vars
38- const { password, createdAt, ...safeUser } = user;
36+ export function createSafeUser(user: Partial<User> & Pick<User, 'id' | 'nickname' | 'status'>): SafeUser {
37+ const safeUser: SafeUser = {
38+ id: user.id,
39+ nickname: user.nickname,
40+ status: user.status,
41+ avatarUrl: user.avatarUrl,
42+ lastSeen: user.lastSeen
43+ };
3944 return safeUser;
4045 }
4146
@@ -33,9 +33,14 @@ export type UserStatus = 'offline' | 'online' | 'away' | string;
33 export type SafeUser = Omit<User, 'password' | 'createdAt'>;33 export type SafeUser = Omit<User, 'password' | 'createdAt'>;
34 34
35 // Helper function to create a safe user object35 // Helper function to create a safe user object
36- export function createSafeUser(user: User): SafeUser {36+ export function createSafeUser(user: Partial<User> & Pick<User, 'id' | 'nickname' | 'status'>): SafeUser {
37- // eslint-disable-next-line @typescript-eslint/no-unused-vars37+ const safeUser: SafeUser = {
38- const { password, createdAt, ...safeUser } = user;38+ id: user.id,
39+ nickname: user.nickname,
40+ status: user.status,
41+ avatarUrl: user.avatarUrl,
42+ lastSeen: user.lastSeen
43+ };
39 return safeUser;44 return safeUser;
40 }45 }
41 46
M src/routes/+page.svelte +7 -1
@@ -6,6 +6,9 @@
66 let showChatRoom = $state(true);
77 let showAuth = $state(false);
88
9+ function handleLoginSuccess() {
10+ showAuth = false;
11+ }
912 </script>
1013
1114 <div class="desktop content-wrapper">
@@ -16,7 +19,10 @@
1619 {/if}
1720
1821 {#if showAuth}
19- <AimLogin bind:showAuth />
22+ <AimLogin
23+ bind:showAuth
24+ onLoginSuccess={handleLoginSuccess}
25+ />
2026 {/if}
2127 </div>
2228
@@ -6,6 +6,9 @@
6 let showChatRoom = $state(true);6 let showChatRoom = $state(true);
7 let showAuth = $state(false);7 let showAuth = $state(false);
8 8
9+ function handleLoginSuccess() {
10+ showAuth = false;
11+ }
9 </script>12 </script>
10 13
11 <div class="desktop content-wrapper">14 <div class="desktop content-wrapper">
@@ -16,7 +19,10 @@
16 {/if}19 {/if}
17 20
18 {#if showAuth}21 {#if showAuth}
19- <AimLogin bind:showAuth />22+ <AimLogin
23+ bind:showAuth
24+ onLoginSuccess={handleLoginSuccess}
25+ />
20 {/if}26 {/if}
21 </div>27 </div>
22 28
M src/routes/api/chat/messages/+server.ts +2 -2
@@ -7,14 +7,14 @@ import type {
77 SendMessageResponse,
88 GetMessagesResponse
99 } from '$lib/types/payloads';
10-import { desc, eq, lt, and } from 'drizzle-orm';
10+import { and, desc, eq, lt } from 'drizzle-orm/sql';
11+import type { SQL } from 'drizzle-orm/sql';
1112 import { error } from '@sveltejs/kit';
1213 import { sseEmitter } from '$lib/sseEmitter';
1314 import { chatRooms } from '$lib/db/schema';
1415 import { users } from '$lib/db/schema';
1516 import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';
1617 import { createLogger } from '$lib/utils/logger.server';
17-import { SQL } from 'drizzle-orm';
1818
1919 const log = createLogger('chat-server');
2020
@@ -7,14 +7,14 @@ import type {
7 SendMessageResponse, 7 SendMessageResponse,
8 GetMessagesResponse 8 GetMessagesResponse
9 } from '$lib/types/payloads';9 } from '$lib/types/payloads';
10-import { desc, eq, lt, and } from 'drizzle-orm';10+import { and, desc, eq, lt } from 'drizzle-orm/sql';
11+import type { SQL } from 'drizzle-orm/sql';
11 import { error } from '@sveltejs/kit';12 import { error } from '@sveltejs/kit';
12 import { sseEmitter } from '$lib/sseEmitter';13 import { sseEmitter } from '$lib/sseEmitter';
13 import { chatRooms } from '$lib/db/schema';14 import { chatRooms } from '$lib/db/schema';
14 import { users } from '$lib/db/schema';15 import { users } from '$lib/db/schema';
15 import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';16 import { DEFAULT_CHAT_ROOM_ID } from '$lib/utils/chat.server';
16 import { createLogger } from '$lib/utils/logger.server';17 import { createLogger } from '$lib/utils/logger.server';
17-import { SQL } from 'drizzle-orm';
18 18
19 const log = createLogger('chat-server');19 const log = createLogger('chat-server');
20 20
M src/routes/api/register/+server.ts +1 -1
@@ -5,7 +5,7 @@ import { users } from '$lib/db/schema';
55 import { hashPassword } from '$lib/utils/password';
66 import { validateTurnstileToken } from '$lib/utils/turnstile.server';
77 import { createLogger } from '$lib/utils/logger.server';
8-import { eq } from 'drizzle-orm';
8+import { eq } from 'drizzle-orm/sql';
99
1010 const log = createLogger('register-server');
1111
@@ -5,7 +5,7 @@ import { users } from '$lib/db/schema';
5 import { hashPassword } from '$lib/utils/password';5 import { hashPassword } from '$lib/utils/password';
6 import { validateTurnstileToken } from '$lib/utils/turnstile.server';6 import { validateTurnstileToken } from '$lib/utils/turnstile.server';
7 import { createLogger } from '$lib/utils/logger.server';7 import { createLogger } from '$lib/utils/logger.server';
8-import { eq } from 'drizzle-orm';8+import { eq } from 'drizzle-orm/sql';
9 9
10 const log = createLogger('register-server');10 const log = createLogger('register-server');
11 11
M src/routes/api/rooms/[roomId]/+server.ts +9 -8
@@ -1,6 +1,7 @@
1-import { desc, eq } from 'drizzle-orm';
1+import { eq } from 'drizzle-orm/sql';
22 import db from '$lib/db/db.server';
3-import { messages, users } from '$lib/db/schema';
3+import { users } from '$lib/db/schema';
4+import type { SafeUser } from '$lib/types/chat';
45 import type { PublicRoomResponse } from '$lib/types/payloads';
56 import { createSafeUser } from '$lib/types/chat';
67 import { createLogger } from '$lib/utils/logger.server';
@@ -29,7 +30,7 @@ export async function GET({ params, url, locals }): Promise<Response> {
2930 .execute();
3031
3132 // Process users to mark them as offline if they've timed out
32- const processedUsers = fetchedUsers.map(user => {
33+ const processedUsers = fetchedUsers.map((user: SafeUser & { lastSeen: number }) => {
3334 const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;
3435 return {
3536 ...user,
@@ -38,13 +39,13 @@ export async function GET({ params, url, locals }): Promise<Response> {
3839 });
3940
4041 // Update any users that should be offline in the database
41- const usersToUpdate = processedUsers.filter(user =>
42+ const usersToUpdate = processedUsers.filter((user: SafeUser & { lastSeen: number }) =>
4243 user.status === 'offline' &&
43- fetchedUsers.find(u => u.id === user.id)?.status === 'online'
44+ fetchedUsers.find((u: SafeUser & { lastSeen: number }) => u.id === user.id)?.status === 'online'
4445 );
4546
4647 if (usersToUpdate.length > 0) {
47- await Promise.all(usersToUpdate.map(user =>
48+ await Promise.all(usersToUpdate.map((user: SafeUser & { lastSeen: number }) =>
4849 db.update(users)
4950 .set({ status: 'offline', lastSeen: Date.now() })
5051 .where(eq(users.id, user.id))
@@ -53,12 +54,12 @@ export async function GET({ params, url, locals }): Promise<Response> {
5354
5455 log.debug('Updated offline status for users', {
5556 count: usersToUpdate.length,
56- userIds: usersToUpdate.map(u => u.id)
57+ userIds: usersToUpdate.map((u: SafeUser & { lastSeen: number }) => u.id)
5758 });
5859 }
5960
6061 // Sanitize user data using createSafeUser
61- const sanitizedUsers = processedUsers.map(user => createSafeUser(user));
62+ const sanitizedUsers = processedUsers.map((user: SafeUser & { lastSeen: number }) => createSafeUser(user));
6263
6364 const responseData: PublicRoomResponse = {
6465 success: true,
@@ -1,6 +1,7 @@
1-import { desc, eq } from 'drizzle-orm';1+import { eq } from 'drizzle-orm/sql';
2 import db from '$lib/db/db.server';2 import db from '$lib/db/db.server';
3-import { messages, users } from '$lib/db/schema';3+import { users } from '$lib/db/schema';
4+import type { SafeUser } from '$lib/types/chat';
4 import type { PublicRoomResponse } from '$lib/types/payloads';5 import type { PublicRoomResponse } from '$lib/types/payloads';
5 import { createSafeUser } from '$lib/types/chat';6 import { createSafeUser } from '$lib/types/chat';
6 import { createLogger } from '$lib/utils/logger.server';7 import { createLogger } from '$lib/utils/logger.server';
@@ -29,7 +30,7 @@ export async function GET({ params, url, locals }): Promise<Response> {
29 .execute();30 .execute();
30 31
31 // Process users to mark them as offline if they've timed out32 // Process users to mark them as offline if they've timed out
32- const processedUsers = fetchedUsers.map(user => {33+ const processedUsers = fetchedUsers.map((user: SafeUser & { lastSeen: number }) => {
33 const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;34 const shouldBeOffline = user.status === 'online' && user.lastSeen < timeoutThreshold;
34 return {35 return {
35 ...user,36 ...user,
@@ -38,13 +39,13 @@ export async function GET({ params, url, locals }): Promise<Response> {
38 });39 });
39 40
40 // Update any users that should be offline in the database41 // Update any users that should be offline in the database
41- const usersToUpdate = processedUsers.filter(user => 42+ const usersToUpdate = processedUsers.filter((user: SafeUser & { lastSeen: number }) =>
42 user.status === 'offline' && 43 user.status === 'offline' &&
43- fetchedUsers.find(u => u.id === user.id)?.status === 'online'44+ fetchedUsers.find((u: SafeUser & { lastSeen: number }) => u.id === user.id)?.status === 'online'
44 );45 );
45 46
46 if (usersToUpdate.length > 0) {47 if (usersToUpdate.length > 0) {
47- await Promise.all(usersToUpdate.map(user =>48+ await Promise.all(usersToUpdate.map((user: SafeUser & { lastSeen: number }) =>
48 db.update(users)49 db.update(users)
49 .set({ status: 'offline', lastSeen: Date.now() })50 .set({ status: 'offline', lastSeen: Date.now() })
50 .where(eq(users.id, user.id))51 .where(eq(users.id, user.id))
@@ -53,12 +54,12 @@ export async function GET({ params, url, locals }): Promise<Response> {
53 54
54 log.debug('Updated offline status for users', { 55 log.debug('Updated offline status for users', {
55 count: usersToUpdate.length,56 count: usersToUpdate.length,
56- userIds: usersToUpdate.map(u => u.id)57+ userIds: usersToUpdate.map((u: SafeUser & { lastSeen: number }) => u.id)
57 });58 });
58 }59 }
59 60
60 // Sanitize user data using createSafeUser61 // Sanitize user data using createSafeUser
61- const sanitizedUsers = processedUsers.map(user => createSafeUser(user));62+ const sanitizedUsers = processedUsers.map((user: SafeUser & { lastSeen: number }) => createSafeUser(user));
62 63
63 const responseData: PublicRoomResponse = {64 const responseData: PublicRoomResponse = {
64 success: true,65 success: true,

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close