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/api/version.ts b/src/api/version.ts new file mode 100644 index 0000000..af779f0 --- /dev/null +++ b/src/api/version.ts @@ -0,0 +1,10 @@ +import { API_BASE_URL, extractErrorMessage } from '@/api/http' +import type { VersionInfo } from '@/types/version' + +export async function getVersionApi(): Promise { + const response = await fetch(`${API_BASE_URL}/version`) + if (!response.ok) { + throw new Error(await extractErrorMessage(response, 'Failed to load API version')) + } + return response.json() +} 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; +}