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 @@
@@ -51,6 +70,27 @@ onMounted(() => {
+ API information
+
+
+ Loading…
+
+
+ Version
+ {{ versionInfo.version }}
+
+
+ Commit
+ {{ versionInfo.commit }}
+
+
+ Build time
+ {{ versionInfo.buildTime }}
+
+
+ {{ versionError }}
+
+
@@ -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