From 16405ec5f7e31f2b49d8e1dc58b7f7bfba9b5369 Mon Sep 17 00:00:00 2001 From: Robin Dittmar Date: Tue, 1 Sep 2026 16:34:13 +0200 Subject: [PATCH 1/2] feat: use new invite status endpoint to display different invite type counts --- src/api/__tests__/invites.spec.ts | 29 ++++- src/api/invites.ts | 10 +- src/components/InvitesPanel.vue | 60 ++++++++- src/components/__tests__/InvitesPanel.spec.ts | 116 ++++++++++++++++++ src/types/invite.ts | 6 + 5 files changed, 213 insertions(+), 8 deletions(-) diff --git a/src/api/__tests__/invites.spec.ts b/src/api/__tests__/invites.spec.ts index c351cbd..9146901 100644 --- a/src/api/__tests__/invites.spec.ts +++ b/src/api/__tests__/invites.spec.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' -import { getInvitesApi, createInviteApi, deleteInviteApi } from '../invites' +import { getInvitesApi, getInviteStatusApi, createInviteApi, deleteInviteApi } from '../invites' import { useAuthStore } from '@/stores/auth' import { API_BASE_URL } from '@/api/http' @@ -65,6 +65,33 @@ describe('invites API', () => { await expect(getInvitesApi()).rejects.toThrow('Failed') }) + it('getInviteStatusApi sends GET to /user/invites/status and returns the counts', async () => { + const mockCounts = { active: 12, expired: 2, used: 8 } + const fetchMock = vi.fn().mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => mockCounts, + } as unknown as Response) + global.fetch = fetchMock + + const result = await getInviteStatusApi() + + expect(fetchMock).toHaveBeenCalledWith( + `${API_BASE_URL}/user/invites/status`, + expect.objectContaining({ method: 'GET' }), + ) + expect(result).toEqual(mockCounts) + }) + + it('getInviteStatusApi throws on failure', async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: false, + json: async () => ({ message: 'Failed to load invite counts' }), + } as unknown as Response) + + await expect(getInviteStatusApi()).rejects.toThrow('Failed to load invite counts') + }) + it('createInviteApi sends POST to /user/invites and returns the created invite', async () => { const mockInvite = { id: 'invite-1', code: 'ABC123' } const fetchMock = vi.fn().mockResolvedValueOnce({ diff --git a/src/api/invites.ts b/src/api/invites.ts index 891521b..b6a58a6 100644 --- a/src/api/invites.ts +++ b/src/api/invites.ts @@ -1,6 +1,6 @@ import { apiClient } from '@/api/client' import { extractErrorMessage } from '@/api/http' -import type { Invite, PaginatedInvites } from '@/types/invite' +import type { Invite, InviteStatusCounts, PaginatedInvites } from '@/types/invite' export interface GetInvitesParams { page?: number @@ -20,6 +20,14 @@ export async function getInvitesApi(params: GetInvitesParams = {}): Promise { + const response = await apiClient.get('/user/invites/status') + if (!response.ok) { + throw new Error(await extractErrorMessage(response, 'Failed to load invite counts')) + } + return response.json() +} + export async function createInviteApi(): Promise { const response = await apiClient.post('/user/invites') if (!response.ok) { diff --git a/src/components/InvitesPanel.vue b/src/components/InvitesPanel.vue index 395797c..3153b24 100644 --- a/src/components/InvitesPanel.vue +++ b/src/components/InvitesPanel.vue @@ -1,7 +1,7 @@ @@ -107,4 +147,10 @@ onMounted(() => { margin-top: 0.6rem; padding: 0.5rem 1rem; } + +.loading-text { + font-size: 0.85rem; + color: var(--c-text-soft); + margin: 0; +} -- 2.54.0