diff --git a/frontend/src/routes/notifications/+page.server.ts b/frontend/src/routes/notifications/+page.server.ts deleted file mode 100644 index 42485660..00000000 --- a/frontend/src/routes/notifications/+page.server.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { error, redirect } from '@sveltejs/kit'; -import { createApiClient } from '$lib/api.server'; -import { getErrorMessage } from '$lib/errors'; -import type { PageServerLoad, Actions } from './$types'; - -export const load: PageServerLoad = async ({ fetch, url }) => { - const api = createApiClient(fetch); - - const type = url.searchParams.get('type') ?? undefined; - const readParam = url.searchParams.get('read'); - const read = readParam !== null ? readParam === 'true' : undefined; - - const result = await api.GET('/api/notifications', { - params: { query: { type: type as 'MENTION' | 'REPLY' | undefined, read, page: 0, size: 20 } } - }); - - if (!result.response.ok) { - const code = (result.error as unknown as { code?: string })?.code; - throw error(result.response.status, getErrorMessage(code)); - } - - const page = result.data!; - const notifications = page.content ?? []; - const unreadCount = notifications.filter((n) => !n.read).length; - - return { notifications, unreadCount, totalPages: page.totalPages ?? 1 }; -}; - -export const actions: Actions = { - 'mark-all': async ({ fetch }) => { - const api = createApiClient(fetch); - await api.POST('/api/notifications/read-all'); - redirect(303, '/notifications'); - } -}; diff --git a/frontend/src/routes/notifications/+page.svelte b/frontend/src/routes/notifications/+page.svelte deleted file mode 100644 index 42a35391..00000000 --- a/frontend/src/routes/notifications/+page.svelte +++ /dev/null @@ -1,279 +0,0 @@ - - - - {m.notification_history_heading()} - - -
-
- - - - {m.btn_back_to_overview()} - - - -
-

- {m.notification_history_heading()} -

- {#if data.unreadCount > 0} -
- -
- {/if} -
- - -
- - - - - - - - - - - -
- - - {#if allNotifications.length === 0} -
- -

- {m.notification_empty_history()} -

-

- {m.notification_empty_history_body()} -

-
- {:else} - - {/if} - - - {#if hasMore} - - {/if} -
-
diff --git a/frontend/src/routes/notifications/page.server.spec.ts b/frontend/src/routes/notifications/page.server.spec.ts deleted file mode 100644 index 05d4fb2a..00000000 --- a/frontend/src/routes/notifications/page.server.spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest'; - -vi.mock('$lib/api.server', () => ({ createApiClient: vi.fn() })); - -import { load, actions } from './+page.server'; -import { createApiClient } from '$lib/api.server'; - -beforeEach(() => vi.clearAllMocks()); - -function makeUrl(params: Record = {}) { - const url = new URL('http://localhost/notifications'); - for (const [key, value] of Object.entries(params)) { - url.searchParams.set(key, value); - } - return url; -} - -// ─── load ───────────────────────────────────────────────────────────────────── - -describe('notifications page load', () => { - it('returns notifications and unreadCount from API response', async () => { - const mockGet = vi.fn().mockResolvedValueOnce({ - response: { ok: true }, - data: { - content: [ - { id: 'n1', read: false }, - { id: 'n2', read: true }, - { id: 'n3', read: false } - ], - totalElements: 3, - totalPages: 1, - number: 0 - } - }); - vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< - typeof createApiClient - >); - - const result = await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); - - expect(result.notifications).toHaveLength(3); - expect(result.unreadCount).toBe(2); - }); - - it('passes type param to API when ?type=MENTION is in URL', async () => { - const mockGet = vi.fn().mockResolvedValueOnce({ - response: { ok: true }, - data: { content: [], totalElements: 0, totalPages: 0, number: 0 } - }); - vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< - typeof createApiClient - >); - - await load({ url: makeUrl({ type: 'MENTION' }), fetch: vi.fn() as unknown as typeof fetch }); - - const queryParams = mockGet.mock.calls[0][1].params.query; - expect(queryParams.type).toBe('MENTION'); - }); - - it('passes read=false to API when ?read=false is in URL', async () => { - const mockGet = vi.fn().mockResolvedValueOnce({ - response: { ok: true }, - data: { content: [], totalElements: 0, totalPages: 0, number: 0 } - }); - vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< - typeof createApiClient - >); - - await load({ url: makeUrl({ read: 'false' }), fetch: vi.fn() as unknown as typeof fetch }); - - const queryParams = mockGet.mock.calls[0][1].params.query; - expect(queryParams.read).toBe(false); - }); - - it('passes no filter params when no search params present', async () => { - const mockGet = vi.fn().mockResolvedValueOnce({ - response: { ok: true }, - data: { content: [], totalElements: 0, totalPages: 0, number: 0 } - }); - vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< - typeof createApiClient - >); - - await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); - - const queryParams = mockGet.mock.calls[0][1].params.query; - expect(queryParams.type).toBeUndefined(); - expect(queryParams.read).toBeUndefined(); - }); - - it('calls the API exactly once — no separate round-trip for unreadCount', async () => { - const mockGet = vi.fn().mockResolvedValueOnce({ - response: { ok: true }, - data: { content: [], totalElements: 0, totalPages: 0, number: 0 } - }); - vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< - typeof createApiClient - >); - - await load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }); - - expect(mockGet).toHaveBeenCalledTimes(1); - }); - - it('throws 401 error when API returns 401', async () => { - const mockGet = vi.fn().mockResolvedValueOnce({ - response: { ok: false, status: 401 }, - data: null - }); - vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType< - typeof createApiClient - >); - - await expect( - load({ url: makeUrl(), fetch: vi.fn() as unknown as typeof fetch }) - ).rejects.toMatchObject({ status: 401 }); - }); -}); - -// ─── mark-all action ────────────────────────────────────────────────────────── - -describe('notifications mark-all action', () => { - it('calls POST /api/notifications/read-all and redirects', async () => { - const mockPost = vi.fn().mockResolvedValueOnce({ response: { ok: true } }); - vi.mocked(createApiClient).mockReturnValue({ POST: mockPost } as ReturnType< - typeof createApiClient - >); - - const markAll = actions['mark-all'] as (ctx: { fetch: typeof fetch }) => Promise; - await expect(markAll({ fetch: vi.fn() as unknown as typeof fetch })).rejects.toMatchObject({ - location: '/notifications' - }); - - expect(mockPost).toHaveBeenCalledTimes(1); - }); -}); diff --git a/frontend/src/routes/profile/+page.svelte b/frontend/src/routes/profile/+page.svelte index 39de19a7..24d25c12 100644 --- a/frontend/src/routes/profile/+page.svelte +++ b/frontend/src/routes/profile/+page.svelte @@ -102,10 +102,7 @@ const hasEmail = $derived(!!data.user?.email);