This commit is contained in:
AlacrisDevs
2026-02-07 23:10:10 +02:00
parent 23693db9ec
commit 75a2aefadb
25 changed files with 457 additions and 679 deletions

879
AUDIT.md

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,6 @@
"vitest-browser-svelte": "^2.0.2" "vitest-browser-svelte": "^2.0.2"
}, },
"dependencies": { "dependencies": {
"@inlang/paraglide-js": "^2.10.0",
"@supabase/ssr": "^0.8.0", "@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.94.0", "@supabase/supabase-js": "^2.94.0",
"@tanstack/svelte-virtual": "^3.13.18", "@tanstack/svelte-virtual": "^3.13.18",

View File

@@ -1,13 +1,14 @@
<!doctype html> <!doctype html>
<html lang="%paraglide.lang%"> <html lang="%paraglide.lang%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta <meta name="viewport" content="width=device-width, initial-scale=1" />
name="viewport"
content="width=device-width, initial-scale=1" <link rel="preconnect" href="https://fonts.googleapis.com" />
/> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" /> <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
@@ -22,4 +23,5 @@
<body data-sveltekit-preload-data="tap"> <body data-sveltekit-preload-data="tap">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html>
</html>

View File

@@ -2,6 +2,9 @@
import { Button, Input } from "$lib/components/ui"; import { Button, Input } from "$lib/components/ui";
import { createRoom } from "$lib/matrix"; import { createRoom } from "$lib/matrix";
import { toasts } from "$lib/stores/ui"; import { toasts } from "$lib/stores/ui";
import { createLogger, getErrorMessage } from "$lib/utils/logger";
const log = createLogger('matrix:room');
import { syncRoomsFromEvent, selectRoom } from "$lib/stores/matrix"; import { syncRoomsFromEvent, selectRoom } from "$lib/stores/matrix";
interface Props { interface Props {
@@ -35,9 +38,9 @@
roomName = ""; roomName = "";
isDirect = false; isDirect = false;
onClose(); onClose();
} catch (e: any) { } catch (e: unknown) {
console.error("Failed to create room:", e); log.error('Failed to create room', { error: e });
toasts.error(e.message || "Failed to create room"); toasts.error(getErrorMessage(e, 'Failed to create room'));
} finally { } finally {
isCreating = false; isCreating = false;
} }

View File

@@ -2,6 +2,9 @@
import { Button, Input } from "$lib/components/ui"; import { Button, Input } from "$lib/components/ui";
import { createSpace, getSpaces } from "$lib/matrix"; import { createSpace, getSpaces } from "$lib/matrix";
import { toasts } from "$lib/stores/ui"; import { toasts } from "$lib/stores/ui";
import { createLogger, getErrorMessage } from "$lib/utils/logger";
const log = createLogger('matrix:space');
import { syncRoomsFromEvent } from "$lib/stores/matrix"; import { syncRoomsFromEvent } from "$lib/stores/matrix";
interface Props { interface Props {
@@ -45,9 +48,9 @@
spaceTopic = ""; spaceTopic = "";
isPublic = false; isPublic = false;
onClose(); onClose();
} catch (e: any) { } catch (e: unknown) {
console.error("Failed to create space:", e); log.error('Failed to create space', { error: e });
toasts.error(e.message || "Failed to create space"); toasts.error(getErrorMessage(e, 'Failed to create space'));
} finally { } finally {
isCreating = false; isCreating = false;
} }

View File

@@ -20,6 +20,9 @@
import EmojiPicker from "$lib/components/ui/EmojiPicker.svelte"; import EmojiPicker from "$lib/components/ui/EmojiPicker.svelte";
import { convertEmojiShortcodes } from "$lib/utils/emojiData"; import { convertEmojiShortcodes } from "$lib/utils/emojiData";
import { getTwemojiUrl } from "$lib/utils/twemoji"; import { getTwemojiUrl } from "$lib/utils/twemoji";
import { createLogger, getErrorMessage } from "$lib/utils/logger";
const log = createLogger('matrix:input');
// Emoji detection regex // Emoji detection regex
const emojiRegex = const emojiRegex =
@@ -134,11 +137,11 @@
} }
// Send typing indicator // Send typing indicator
setTyping(roomId, true).catch(console.error); setTyping(roomId, true).catch((e) => log.error('Failed to send typing', { error: e }));
// Stop typing after 3 seconds of no input // Stop typing after 3 seconds of no input
typingTimeout = setTimeout(() => { typingTimeout = setTimeout(() => {
setTyping(roomId, false).catch(console.error); setTyping(roomId, false).catch((e) => log.error('Failed to stop typing', { error: e }));
}, 3000); }, 3000);
} }
@@ -416,7 +419,7 @@
clearTimeout(typingTimeout); clearTimeout(typingTimeout);
typingTimeout = null; typingTimeout = null;
} }
setTyping(roomId, false).catch(console.error); setTyping(roomId, false).catch((e) => log.error('Failed to stop typing', { error: e }));
// Create a temporary event ID for the pending message // Create a temporary event ID for the pending message
const tempEventId = `pending-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const tempEventId = `pending-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -462,11 +465,11 @@
// If no event ID returned, just mark as not pending // If no event ID returned, just mark as not pending
confirmPendingMessage(roomId, tempEventId, tempEventId); confirmPendingMessage(roomId, tempEventId, tempEventId);
} }
} catch (e: any) { } catch (e: unknown) {
console.error("Failed to send message:", e); log.error('Failed to send message', { error: e });
// Remove the pending message on failure // Remove the pending message on failure
removePendingMessage(roomId, tempEventId); removePendingMessage(roomId, tempEventId);
toasts.error(e.message || "Failed to send message"); toasts.error(getErrorMessage(e, 'Failed to send message'));
} finally { } finally {
isSending = false; isSending = false;
// Refocus after DOM settles from optimistic update // Refocus after DOM settles from optimistic update
@@ -497,9 +500,9 @@
const contentUri = await uploadFile(file); const contentUri = await uploadFile(file);
await sendFileMessage(roomId, file, contentUri); await sendFileMessage(roomId, file, contentUri);
toasts.success("File sent!"); toasts.success("File sent!");
} catch (e: any) { } catch (e: unknown) {
console.error("Failed to upload file:", e); log.error('Failed to upload file', { error: e });
toasts.error(e.message || "Failed to upload file"); toasts.error(getErrorMessage(e, 'Failed to upload file'));
} finally { } finally {
isUploading = false; isUploading = false;
} }

View File

@@ -2,6 +2,9 @@
import { Avatar } from "$lib/components/ui"; import { Avatar } from "$lib/components/ui";
import { setRoomName, setRoomTopic, setRoomAvatar } from "$lib/matrix"; import { setRoomName, setRoomTopic, setRoomAvatar } from "$lib/matrix";
import { toasts } from "$lib/stores/ui"; import { toasts } from "$lib/stores/ui";
import { createLogger } from "$lib/utils/logger";
const log = createLogger('matrix:settings');
import { syncRoomsFromEvent } from "$lib/stores/matrix"; import { syncRoomsFromEvent } from "$lib/stores/matrix";
import type { RoomSummary } from "$lib/matrix/types"; import type { RoomSummary } from "$lib/matrix/types";
@@ -55,7 +58,7 @@
toasts.success("Room settings updated"); toasts.success("Room settings updated");
onClose(); onClose();
} catch (e) { } catch (e) {
console.error("Failed to update room settings:", e); log.error('Failed to update room settings', { error: e });
toasts.error("Failed to update room settings"); toasts.error("Failed to update room settings");
} finally { } finally {
isSaving = false; isSaving = false;

View File

@@ -2,6 +2,9 @@
import { Avatar } from '$lib/components/ui'; import { Avatar } from '$lib/components/ui';
import { searchUsers, createDirectMessage } from '$lib/matrix'; import { searchUsers, createDirectMessage } from '$lib/matrix';
import { toasts } from '$lib/stores/ui'; import { toasts } from '$lib/stores/ui';
import { createLogger, getErrorMessage } from '$lib/utils/logger';
const log = createLogger('matrix:dm');
interface Props { interface Props {
onClose: () => void; onClose: () => void;
@@ -29,7 +32,7 @@
try { try {
searchResults = await searchUsers(searchQuery); searchResults = await searchUsers(searchQuery);
} catch (e) { } catch (e) {
console.error('Search failed:', e); log.error('Search failed', { error: e });
} finally { } finally {
isSearching = false; isSearching = false;
} }
@@ -43,9 +46,9 @@
toasts.success('Direct message started!'); toasts.success('Direct message started!');
onDMCreated(roomId); onDMCreated(roomId);
onClose(); onClose();
} catch (e: any) { } catch (e: unknown) {
console.error('Failed to create DM:', e); log.error('Failed to create DM', { error: e });
toasts.error(e.message || 'Failed to start direct message'); toasts.error(getErrorMessage(e, 'Failed to start direct message'));
} finally { } finally {
isCreating = false; isCreating = false;
} }

View File

@@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import { syncState, syncError, clearState } from "$lib/stores/matrix"; import { syncState, syncError, clearState } from "$lib/stores/matrix";
import { clearAllCache } from "$lib/cache"; import { clearAllCache } from "$lib/cache";
import { createLogger } from "$lib/utils/logger";
const log = createLogger('matrix:sync');
interface Props { interface Props {
onHardRefresh?: () => void; onHardRefresh?: () => void;
@@ -43,7 +46,7 @@
// Reload the page for clean state // Reload the page for clean state
window.location.reload(); window.location.reload();
} catch (error) { } catch (error) {
console.error("[SyncRecovery] Hard refresh failed:", error); log.error('Hard refresh failed', { error });
isRefreshing = false; isRefreshing = false;
} }
} }

View File

@@ -3,6 +3,9 @@
import { createDirectMessage } from '$lib/matrix'; import { createDirectMessage } from '$lib/matrix';
import { userPresence } from '$lib/stores/matrix'; import { userPresence } from '$lib/stores/matrix';
import { toasts } from '$lib/stores/ui'; import { toasts } from '$lib/stores/ui';
import { createLogger } from '$lib/utils/logger';
const log = createLogger('matrix:profile');
import type { RoomMember } from '$lib/matrix/types'; import type { RoomMember } from '$lib/matrix/types';
interface Props { interface Props {
@@ -35,7 +38,7 @@
onStartDM?.(roomId); onStartDM?.(roomId);
onClose(); onClose();
} catch (e) { } catch (e) {
console.error('Failed to start DM:', e); log.error('Failed to start DM', { error: e });
toasts.error('Failed to start direct message'); toasts.error('Failed to start direct message');
} finally { } finally {
isStartingDM = false; isStartingDM = false;

View File

@@ -318,7 +318,7 @@
{#if showAddItemModal} {#if showAddItemModal}
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddItemModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddItemModal = false)}> <div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddItemModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddItemModal = false)}>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-md space-y-4" onclick={(e) => e.stopPropagation()}> <div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-md space-y-4" onclick={(e) => e.stopPropagation()}>
<h3 class="text-body font-heading text-white">{editingItem ? 'Edit' : 'Add'} Budget Item</h3> <h3 class="text-body font-heading text-white">{editingItem ? 'Edit' : 'Add'} Budget Item</h3>
@@ -384,7 +384,7 @@
{#if showAddCategoryModal} {#if showAddCategoryModal}
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddCategoryModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddCategoryModal = false)}> <div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddCategoryModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddCategoryModal = false)}>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-sm space-y-4" onclick={(e) => e.stopPropagation()}> <div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-sm space-y-4" onclick={(e) => e.stopPropagation()}>
<h3 class="text-body font-heading text-white">Add Category</h3> <h3 class="text-body font-heading text-white">Add Category</h3>
@@ -399,10 +399,11 @@
<div class="flex gap-1.5"> <div class="flex gap-1.5">
{#each CATEGORY_COLORS as color} {#each CATEGORY_COLORS as color}
<button <button
class="w-6 h-6 rounded-full border-2 transition-all {newCategoryColor === color ? 'border-white scale-110' : 'border-transparent'}" class="w-6 h-6 rounded-full border-2 transition-all {newCategoryColor === color ? 'border-white scale-110' : 'border-transparent'}"
style="background-color: {color}" style="background-color: {color}"
onclick={() => (newCategoryColor = color)} onclick={() => (newCategoryColor = color)}
></button> aria-label="Select color {color}"
></button>
{/each} {/each}
</div> </div>
</div> </div>

View File

@@ -422,7 +422,7 @@
{#if showAddSponsorModal} {#if showAddSponsorModal}
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddSponsorModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddSponsorModal = false)}> <div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddSponsorModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddSponsorModal = false)}>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-md space-y-4 max-h-[80vh] overflow-auto" onclick={(e) => e.stopPropagation()}> <div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-md space-y-4 max-h-[80vh] overflow-auto" onclick={(e) => e.stopPropagation()}>
<h3 class="text-body font-heading text-white">{editingSponsor ? 'Edit' : 'Add'} Sponsor</h3> <h3 class="text-body font-heading text-white">{editingSponsor ? 'Edit' : 'Add'} Sponsor</h3>
@@ -508,7 +508,7 @@
{#if showAddTierModal} {#if showAddTierModal}
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddTierModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddTierModal = false)}> <div class="fixed inset-0 z-[60] bg-black/60 flex items-center justify-center p-4" onclick={() => (showAddTierModal = false)} onkeydown={(e) => e.key === 'Escape' && (showAddTierModal = false)}>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
<div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-sm space-y-4" onclick={(e) => e.stopPropagation()}> <div class="bg-surface rounded-2xl border border-light/10 p-5 w-full max-w-sm space-y-4" onclick={(e) => e.stopPropagation()}>
<h3 class="text-body font-heading text-white">Manage Tiers</h3> <h3 class="text-body font-heading text-white">Manage Tiers</h3>
@@ -528,10 +528,11 @@
<div class="flex gap-1.5"> <div class="flex gap-1.5">
{#each TIER_COLORS as color} {#each TIER_COLORS as color}
<button <button
class="w-6 h-6 rounded-full border-2 transition-all {tierColor === color ? 'border-white scale-110' : 'border-transparent'}" class="w-6 h-6 rounded-full border-2 transition-all {tierColor === color ? 'border-white scale-110' : 'border-transparent'}"
style="background-color: {color}" style="background-color: {color}"
onclick={() => (tierColor = color)} onclick={() => (tierColor = color)}
></button> aria-label="Select color {color}"
></button>
{/each} {/each}
</div> </div>
</div> </div>

View File

@@ -16,6 +16,9 @@ import {
type PinnedEventsContent, type PinnedEventsContent,
type RoomAvatarContent, type RoomAvatarContent,
} from './sdk-types'; } from './sdk-types';
import { createLogger } from '$lib/utils/logger';
const log = createLogger('matrix:client');
// Matrix message content types // Matrix message content types
interface MessageContent extends IContent { interface MessageContent extends IContent {
@@ -81,7 +84,7 @@ export interface LoginWithPasswordParams {
*/ */
export async function initMatrixClient(credentials: LoginCredentials): Promise<MatrixClient> { export async function initMatrixClient(credentials: LoginCredentials): Promise<MatrixClient> {
if (client) { if (client) {
console.warn('Matrix client already initialized, stopping existing client'); log.warn('Matrix client already initialized, stopping existing client');
await stopClient(); await stopClient();
} }
@@ -103,11 +106,11 @@ export async function initMatrixClient(credentials: LoginCredentials): Promise<M
// Check if crypto module is available before trying to init // Check if crypto module is available before trying to init
if (typeof client.initRustCrypto === 'function') { if (typeof client.initRustCrypto === 'function') {
await client.initRustCrypto(); await client.initRustCrypto();
console.log('E2EE crypto initialized successfully'); log.info('E2EE crypto initialized successfully');
} }
} catch (e) { } catch (e) {
// This is expected in dev mode - WASM loading can be problematic // This is expected in dev mode - WASM loading can be problematic
console.info('Crypto not available - encrypted rooms will show encrypted messages'); log.info('Crypto not available - encrypted rooms will show encrypted messages');
} }
// Start the client (begins sync loop) // Start the client (begins sync loop)
@@ -204,7 +207,7 @@ export async function logout(): Promise<void> {
try { try {
await client.logout(); await client.logout();
} catch (e) { } catch (e) {
console.error('Error during logout:', e); log.error('Error during logout', { error: e });
} }
await stopClient(); await stopClient();
} }
@@ -551,7 +554,7 @@ export async function setRoomNotificationLevel(roomId: string, level: Notificati
} }
} }
} catch (e) { } catch (e) {
console.error('Failed to set notification level:', e); log.error('Failed to set notification level', { error: e });
throw e; throw e;
} }
} }
@@ -628,7 +631,7 @@ export async function loadMoreMessages(roomId: string, limit = 50): Promise<{ ha
return { hasMore, loaded }; return { hasMore, loaded };
} catch (e) { } catch (e) {
console.error('Failed to load more messages:', e); log.error('Failed to load more messages', { error: e });
return { hasMore: false, loaded: 0 }; return { hasMore: false, loaded: 0 };
} }
} }
@@ -697,7 +700,7 @@ export async function sendFileMessage(
content.info.w = dimensions.width; content.info.w = dimensions.width;
content.info.h = dimensions.height; content.info.h = dimensions.height;
} catch (e) { } catch (e) {
console.warn('Failed to get image dimensions:', e); log.warn('Failed to get image dimensions', { error: e });
} }
} }
@@ -782,7 +785,7 @@ export async function getAuthenticatedMediaUrl(mxcUrl: string): Promise<string |
return blobUrl; return blobUrl;
} catch (e) { } catch (e) {
console.error('Failed to fetch authenticated media:', e); log.error('Failed to fetch authenticated media', { error: e });
// Fallback to unauthenticated URL // Fallback to unauthenticated URL
return client.mxcUrlToHttp(mxcUrl); return client.mxcUrlToHttp(mxcUrl);
} }
@@ -834,7 +837,7 @@ export async function getAuthenticatedThumbnailUrl(
return blobUrl; return blobUrl;
} catch (e) { } catch (e) {
console.error('Failed to fetch authenticated thumbnail:', e); log.error('Failed to fetch authenticated thumbnail', { error: e });
return client.mxcUrlToHttp(mxcUrl, width, height, 'scale'); return client.mxcUrlToHttp(mxcUrl, width, height, 'scale');
} }
} }
@@ -936,7 +939,7 @@ export async function searchUsers(query: string, limit = 10): Promise<Array<{
avatarUrl: user.avatar_url ? client!.mxcUrlToHttp(user.avatar_url, 40, 40, 'crop') : null, avatarUrl: user.avatar_url ? client!.mxcUrlToHttp(user.avatar_url, 40, 40, 'crop') : null,
})); }));
} catch (e) { } catch (e) {
console.error('User search failed:', e); log.error('User search failed', { error: e });
return []; return [];
} }
} }
@@ -1080,7 +1083,7 @@ export async function setPresence(presence: 'online' | 'offline' | 'unavailable'
try { try {
await client.setPresence({ presence, status_msg: statusMsg }); await client.setPresence({ presence, status_msg: statusMsg });
} catch (e) { } catch (e) {
console.error('Failed to set presence:', e); log.error('Failed to set presence', { error: e });
} }
} }

View File

@@ -109,7 +109,7 @@ function loadTheme(): ThemeState {
return { ...defaultTheme, ...JSON.parse(stored) }; return { ...defaultTheme, ...JSON.parse(stored) };
} }
} catch (e) { } catch (e) {
console.warn('Failed to load theme:', e); // Theme load failure is non-critical, fall through to default
} }
return defaultTheme; return defaultTheme;
} }

View File

@@ -195,6 +195,19 @@ export function clearRecentLogs() {
* Format recent logs as a copyable string for bug reports. * Format recent logs as a copyable string for bug reports.
* User can paste this to you for debugging. * User can paste this to you for debugging.
*/ */
/**
* Safely extract an error message from an unknown caught value.
* Use in catch blocks: `catch (e: unknown) { toasts.error(getErrorMessage(e, 'fallback')) }`
*/
export function getErrorMessage(e: unknown, fallback = 'An unexpected error occurred'): string {
if (e instanceof Error) return e.message;
if (typeof e === 'string') return e;
if (e && typeof e === 'object' && 'message' in e && typeof (e as { message: unknown }).message === 'string') {
return (e as { message: string }).message;
}
return fallback;
}
export function dumpLogs(): string { export function dumpLogs(): string {
return recentLogs return recentLogs
.map((e) => { .map((e) => {

View File

@@ -45,6 +45,9 @@
import { clearBlobUrlCache } from "$lib/cache/mediaCache"; import { clearBlobUrlCache } from "$lib/cache/mediaCache";
import type { Message } from "$lib/matrix/types"; import type { Message } from "$lib/matrix/types";
import type { SupabaseClient } from "@supabase/supabase-js"; import type { SupabaseClient } from "@supabase/supabase-js";
import { createLogger, getErrorMessage } from "$lib/utils/logger";
const log = createLogger('chat:page');
const supabase = getContext<SupabaseClient>("supabase"); const supabase = getContext<SupabaseClient>("supabase");
let data = $derived(page.data); let data = $derived(page.data);
@@ -120,7 +123,7 @@
await initCache(); await initCache();
await cleanupCache(7 * 24 * 60 * 60 * 1000); await cleanupCache(7 * 24 * 60 * 60 * 1000);
} catch (e) { } catch (e) {
console.warn("Cache initialization failed:", e); log.warn('Cache initialization failed', { error: e });
} }
// Try to load credentials from Supabase // Try to load credentials from Supabase
@@ -141,7 +144,7 @@
isInitializing = false; isInitializing = false;
} }
} catch (e) { } catch (e) {
console.error("Failed to load Matrix credentials:", e); log.error('Failed to load Matrix credentials', { error: e });
showJoinScreen = true; showJoinScreen = true;
isInitializing = false; isInitializing = false;
} }
@@ -164,7 +167,7 @@
// Check if org has a Matrix Space, auto-create if not // Check if org has a Matrix Space, auto-create if not
await ensureOrgSpace(credentials); await ensureOrgSpace(credentials);
} catch (e: unknown) { } catch (e: unknown) {
console.error("Failed to init Matrix client:", e); log.error('Failed to init Matrix client', { error: e });
toasts.error(m.chat_join_error()); toasts.error(m.chat_join_error());
showJoinScreen = true; showJoinScreen = true;
} finally { } finally {
@@ -196,7 +199,7 @@
} }
} }
} catch (e) { } catch (e) {
console.warn("Failed to ensure org space:", e); log.warn('Failed to ensure org space', { error: e });
} }
} }
@@ -223,8 +226,8 @@
if (result.provisioned) { if (result.provisioned) {
toasts.success(m.chat_join_success()); toasts.success(m.chat_join_success());
} }
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || m.chat_join_error()); toasts.error(getErrorMessage(e, m.chat_join_error()));
} finally { } finally {
isProvisioning = false; isProvisioning = false;
} }
@@ -297,8 +300,8 @@
await editMessage($selectedRoomId, editingMsg.eventId, newContent); await editMessage($selectedRoomId, editingMsg.eventId, newContent);
editingMsg = null; editingMsg = null;
toasts.success("Message edited"); toasts.success("Message edited");
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to edit message"); toasts.error(getErrorMessage(e, 'Failed to edit message'));
} }
} }
@@ -312,8 +315,8 @@
try { try {
await deleteMessage($selectedRoomId, messageId); await deleteMessage($selectedRoomId, messageId);
toasts.success("Message deleted"); toasts.success("Message deleted");
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to delete message"); toasts.error(getErrorMessage(e, 'Failed to delete message'));
} }
} }
@@ -355,8 +358,8 @@
const contentUri = await uploadFile(file); const contentUri = await uploadFile(file);
await sendFileMessage($selectedRoomId, file, contentUri); await sendFileMessage($selectedRoomId, file, contentUri);
toasts.success("File sent!"); toasts.success("File sent!");
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to upload file"); toasts.error(getErrorMessage(e, 'Failed to upload file'));
} finally { } finally {
isUploadingDrop = false; isUploadingDrop = false;
} }
@@ -369,8 +372,8 @@
const result = await loadMoreMessages($selectedRoomId); const result = await loadMoreMessages($selectedRoomId);
loadRoomMessages($selectedRoomId); loadRoomMessages($selectedRoomId);
if (!result.hasMore) toasts.info("No more messages to load"); if (!result.hasMore) toasts.info("No more messages to load");
} catch (e: any) { } catch (e: unknown) {
console.error("Failed to load more messages:", e); log.error('Failed to load more messages', { error: e });
} finally { } finally {
isLoadingMore = false; isLoadingMore = false;
} }

View File

@@ -22,7 +22,7 @@ export const load: PageServerLoad = async ({ params, locals, url }) => {
try { try {
const events = await fetchEvents(locals.supabase, org.id, statusFilter); const events = await fetchEvents(locals.supabase, org.id, statusFilter);
return { events, statusFilter }; return { events, statusFilter };
} catch (e: any) { } catch (e: unknown) {
log.error('Failed to load events', { error: e, data: { orgId: org.id } }); log.error('Failed to load events', { error: e, data: { orgId: org.id } });
return { events: [], statusFilter }; return { events: [], statusFilter };
} }

View File

@@ -6,6 +6,7 @@
import type { SupabaseClient } from "@supabase/supabase-js"; import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "$lib/supabase/types"; import type { Database } from "$lib/supabase/types";
import { toasts } from "$lib/stores/ui"; import { toasts } from "$lib/stores/ui";
import { getErrorMessage } from "$lib/utils/logger";
import * as m from "$lib/paraglide/messages"; import * as m from "$lib/paraglide/messages";
interface EventItem { interface EventItem {
@@ -100,8 +101,8 @@
showCreateModal = false; showCreateModal = false;
resetForm(); resetForm();
goto(`/${data.org.slug}/events/${created.slug}`); goto(`/${data.org.slug}/events/${created.slug}`);
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to create event"); toasts.error(getErrorMessage(e, 'Failed to create event'));
} finally { } finally {
creating = false; creating = false;
} }

View File

@@ -23,8 +23,8 @@ export const load: LayoutServerLoad = async ({ params, locals, parent }) => {
]); ]);
return { event, eventMembers: members, eventRoles: roles, eventDepartments: departments }; return { event, eventMembers: members, eventRoles: roles, eventDepartments: departments };
} catch (e: any) { } catch (e: unknown) {
if (e?.status === 404) throw e; if (e && typeof e === 'object' && 'status' in e && (e as { status: number }).status === 404) throw e;
log.error('Failed to load event', { error: e, data: { orgId, eventSlug: params.eventSlug } }); log.error('Failed to load event', { error: e, data: { orgId, eventSlug: params.eventSlug } });
error(500, 'Failed to load event'); error(500, 'Failed to load event');
} }

View File

@@ -5,6 +5,7 @@
import type { SupabaseClient } from "@supabase/supabase-js"; import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "$lib/supabase/types"; import type { Database } from "$lib/supabase/types";
import { toasts } from "$lib/stores/ui"; import { toasts } from "$lib/stores/ui";
import { getErrorMessage } from "$lib/utils/logger";
import type { Event, EventMemberWithDetails, EventRole, EventDepartment } from "$lib/api/events"; import type { Event, EventMemberWithDetails, EventRole, EventDepartment } from "$lib/api/events";
import * as m from "$lib/paraglide/messages"; import * as m from "$lib/paraglide/messages";
@@ -173,8 +174,8 @@
goto(`/${data.org.slug}/events/${data.event.slug}`, { goto(`/${data.org.slug}/events/${data.event.slug}`, {
invalidateAll: true, invalidateAll: true,
}); });
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to update event"); toasts.error(getErrorMessage(e, 'Failed to update event'));
} finally { } finally {
saving = false; saving = false;
} }
@@ -192,8 +193,8 @@
toasts.success(m.events_deleted()); toasts.success(m.events_deleted());
goto(`/${data.org.slug}/events`); goto(`/${data.org.slug}/events`);
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to delete event"); toasts.error(getErrorMessage(e, 'Failed to delete event'));
} finally { } finally {
deleting = false; deleting = false;
} }

View File

@@ -54,7 +54,7 @@ export const load: PageServerLoad = async ({ params, locals, parent }) => {
sponsors, sponsors,
sponsorDeliverables, sponsorDeliverables,
}; };
} catch (e: any) { } catch (e: unknown) {
log.error('Failed to load department dashboard', { error: e, data: { deptId: params.deptId } }); log.error('Failed to load department dashboard', { error: e, data: { deptId: params.deptId } });
error(500, 'Failed to load department dashboard'); error(500, 'Failed to load department dashboard');
} }

View File

@@ -4,6 +4,7 @@
import type { SupabaseClient } from "@supabase/supabase-js"; import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "$lib/supabase/types"; import type { Database } from "$lib/supabase/types";
import { toasts } from "$lib/stores/ui"; import { toasts } from "$lib/stores/ui";
import { getErrorMessage } from "$lib/utils/logger";
import type { import type {
Event, Event,
EventMemberWithDetails, EventMemberWithDetails,
@@ -260,8 +261,8 @@
selectedRoleId = ""; selectedRoleId = "";
selectedDeptIds = []; selectedDeptIds = [];
addNotes = ""; addNotes = "";
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to add member"); toasts.error(getErrorMessage(e, 'Failed to add member'));
} finally { } finally {
adding = false; adding = false;
} }
@@ -327,8 +328,8 @@
toasts.success(m.team_updated()); toasts.success(m.team_updated());
editingMember = null; editingMember = null;
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to update member"); toasts.error(getErrorMessage(e, 'Failed to update member'));
} finally { } finally {
updatingMember = false; updatingMember = false;
} }
@@ -349,8 +350,8 @@
teamMembers = teamMembers.filter((tm) => tm.id !== memberToRemove!.id); teamMembers = teamMembers.filter((tm) => tm.id !== memberToRemove!.id);
toasts.success(m.team_removed({ name })); toasts.success(m.team_removed({ name }));
memberToRemove = null; memberToRemove = null;
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to remove member"); toasts.error(getErrorMessage(e, 'Failed to remove member'));
} finally { } finally {
removing = false; removing = false;
} }
@@ -398,8 +399,8 @@
toasts.success(m.team_dept_created()); toasts.success(m.team_dept_created());
} }
showDeptModal = false; showDeptModal = false;
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to save department"); toasts.error(getErrorMessage(e, 'Failed to save department'));
} finally { } finally {
savingDept = false; savingDept = false;
} }
@@ -419,8 +420,8 @@
departments: tm.departments.filter((d) => d.id !== dept.id), departments: tm.departments.filter((d) => d.id !== dept.id),
})); }));
toasts.success(m.team_dept_deleted()); toasts.success(m.team_dept_deleted());
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to delete department"); toasts.error(getErrorMessage(e, 'Failed to delete department'));
} }
} }
@@ -464,8 +465,8 @@
toasts.success(m.team_role_created()); toasts.success(m.team_role_created());
} }
showRoleModal = false; showRoleModal = false;
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to save role"); toasts.error(getErrorMessage(e, 'Failed to save role'));
} finally { } finally {
savingRole = false; savingRole = false;
} }
@@ -486,8 +487,8 @@
: tm, : tm,
); );
toasts.success(m.team_role_deleted()); toasts.success(m.team_role_deleted());
} catch (e: any) { } catch (e: unknown) {
toasts.error(e.message || "Failed to delete role"); toasts.error(getErrorMessage(e, 'Failed to delete role'));
} }
} }

View File

@@ -621,8 +621,9 @@
<Input label="Name" name="name" bind:value={editEventModal.name} /> <Input label="Name" name="name" bind:value={editEventModal.name} />
<Input label="Slug" name="slug" bind:value={editEventModal.slug} /> <Input label="Slug" name="slug" bind:value={editEventModal.slug} />
<div> <div>
<label class="block text-body-sm text-light/60 mb-1">Status</label> <label for="event-status" class="block text-body-sm text-light/60 mb-1">Status</label>
<select <select
id="event-status"
name="status" name="status"
bind:value={editEventModal.status} bind:value={editEventModal.status}
class="w-full bg-dark/50 border border-light/10 rounded-xl px-3 py-2 text-body-sm text-white focus:outline-none focus:border-primary/50" class="w-full bg-dark/50 border border-light/10 rounded-xl px-3 py-2 text-body-sm text-white focus:outline-none focus:border-primary/50"
@@ -634,8 +635,9 @@
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<div> <div>
<label class="block text-body-sm text-light/60 mb-1">Start Date</label> <label for="event-start-date" class="block text-body-sm text-light/60 mb-1">Start Date</label>
<input <input
id="event-start-date"
type="date" type="date"
name="start_date" name="start_date"
bind:value={editEventModal.start_date} bind:value={editEventModal.start_date}
@@ -643,8 +645,9 @@
/> />
</div> </div>
<div> <div>
<label class="block text-body-sm text-light/60 mb-1">End Date</label> <label for="event-end-date" class="block text-body-sm text-light/60 mb-1">End Date</label>
<input <input
id="event-end-date"
type="date" type="date"
name="end_date" name="end_date"
bind:value={editEventModal.end_date} bind:value={editEventModal.end_date}

View File

@@ -1,6 +1,9 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { createLogger } from '$lib/utils/logger';
const log = createLogger('api:matrix-provision');
/** /**
* POST /api/matrix-provision * POST /api/matrix-provision
@@ -83,7 +86,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
if (!registerRes.ok) { if (!registerRes.ok) {
const err = await registerRes.json().catch(() => ({})); const err = await registerRes.json().catch(() => ({}));
console.error('Matrix register failed:', registerRes.status, err); log.error('Matrix register failed', { data: { status: registerRes.status }, error: err });
return json({ error: 'Failed to create Matrix account' }, { status: 500 }); return json({ error: 'Failed to create Matrix account' }, { status: 500 });
} }
@@ -104,7 +107,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
if (!loginRes.ok) { if (!loginRes.ok) {
const err = await loginRes.json().catch(() => ({})); const err = await loginRes.json().catch(() => ({}));
console.error('Matrix login failed:', loginRes.status, err); log.error('Matrix login failed', { data: { status: loginRes.status }, error: err });
return json({ error: 'Failed to login to Matrix account' }, { status: 500 }); return json({ error: 'Failed to login to Matrix account' }, { status: 500 });
} }
@@ -118,7 +121,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
try { try {
await setMatrixAvatar(homeserverUrl, accessToken, profile.avatar_url); await setMatrixAvatar(homeserverUrl, accessToken, profile.avatar_url);
} catch (e) { } catch (e) {
console.warn('Failed to set Matrix avatar:', e); log.warn('Failed to set Matrix avatar', { error: e });
} }
} }
@@ -138,7 +141,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
); );
if (upsertError) { if (upsertError) {
console.error('Failed to store Matrix credentials:', upsertError); log.error('Failed to store Matrix credentials', { error: upsertError });
return json({ error: 'Failed to store credentials' }, { status: 500 }); return json({ error: 'Failed to store credentials' }, { status: 500 });
} }
@@ -152,7 +155,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
provisioned: true, provisioned: true,
}); });
} catch (e) { } catch (e) {
console.error('Matrix provisioning error:', e); log.error('Matrix provisioning error', { error: e });
return json({ error: 'Matrix provisioning failed' }, { status: 500 }); return json({ error: 'Matrix provisioning failed' }, { status: 500 });
} }
}; };

View File

@@ -1,5 +1,8 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { createLogger, getErrorMessage } from '$lib/utils/logger';
const log = createLogger('api:matrix-space');
/** /**
* GET: Retrieve the Matrix Space ID for an org * GET: Retrieve the Matrix Space ID for an org
@@ -140,9 +143,9 @@ export const POST: RequestHandler = async ({ request, locals }) => {
} }
return json({ spaceId, created: true }); return json({ spaceId, created: true });
} catch (e: any) { } catch (e: unknown) {
console.error('Failed to create Matrix Space:', e); log.error('Failed to create Matrix Space', { error: e });
return json({ error: e.message || 'Failed to create Matrix Space' }, { status: 500 }); return json({ error: getErrorMessage(e, 'Failed to create Matrix Space') }, { status: 500 });
} }
} }