From 321c1b7bb01f0c1123b14e128d6e528b34e18371 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Mon, 3 Aug 2026 20:31:59 +0300 Subject: [PATCH] show sets on dashboard --- .../website/cypress/e2e/session-auth.cy.ts | 9 +- .../website/cypress/e2e/sets-dashboard.cy.ts | 2 +- frontend/website/src/stores/sets.ts | 65 +++++++ frontend/website/src/views/DashboardView.vue | 166 ++++++++++++++++-- 4 files changed, 221 insertions(+), 21 deletions(-) create mode 100644 frontend/website/src/stores/sets.ts diff --git a/frontend/website/cypress/e2e/session-auth.cy.ts b/frontend/website/cypress/e2e/session-auth.cy.ts index cd35dfd..defb942 100644 --- a/frontend/website/cypress/e2e/session-auth.cy.ts +++ b/frontend/website/cypress/e2e/session-auth.cy.ts @@ -38,6 +38,13 @@ function visitDashboardAndLogout(): void { } describe('session authentication', () => { + beforeEach(() => { + cy.intercept('GET', '**/api/sets', { + statusCode: 200, + body: { sets: [] }, + }) + }) + it('restores an authenticated session on a protected route', () => { cy.intercept('GET', '**/api/me', { statusCode: 200, @@ -48,7 +55,7 @@ describe('session authentication', () => { cy.wait('@me') cy.location('pathname').should('equal', '/dashboard') - cy.get('h1').should('have.text', 'Your next step starts here.') + cy.get('h1').should('have.text', 'Available sets') }) it('redirects an unauthenticated protected route to login', () => { diff --git a/frontend/website/cypress/e2e/sets-dashboard.cy.ts b/frontend/website/cypress/e2e/sets-dashboard.cy.ts index 7e3bbca..3872b10 100644 --- a/frontend/website/cypress/e2e/sets-dashboard.cy.ts +++ b/frontend/website/cypress/e2e/sets-dashboard.cy.ts @@ -63,7 +63,7 @@ describe('sets dashboard', () => { cy.wait('@sets') cy.get('[role="status"]').should( - 'have.text', + 'contain.text', 'No sets are available yet.', ) }) diff --git a/frontend/website/src/stores/sets.ts b/frontend/website/src/stores/sets.ts new file mode 100644 index 0000000..57b0bcc --- /dev/null +++ b/frontend/website/src/stores/sets.ts @@ -0,0 +1,65 @@ +import { ref } from 'vue' +import { defineStore } from 'pinia' +import { z } from 'zod' + +import { API_BASE } from '@/utils/apiBase' + +export const setSummarySchema = z.object({ + id: z.number().int().positive(), + name: z.string().min(1), +}) + +const setsResponseSchema = z.object({ + sets: z.array(setSummarySchema), +}) + +export type SetSummary = z.infer + +const LOAD_ERROR = "We couldn't load the available sets." + +export const useSetsStore = defineStore('sets', () => { + const sets = ref([]) + const loading = ref(false) + const error = ref(null) + + async function fetchSets(): Promise { + loading.value = true + error.value = null + + try { + const response = await fetch(`${API_BASE}/api/sets`, { + method: 'GET', + credentials: 'include', + headers: { + Accept: 'application/json', + }, + }) + + if (response.status !== 200) { + sets.value = [] + error.value = LOAD_ERROR + + return false + } + + const responseBody: unknown = await response.json() + sets.value = setsResponseSchema.parse(responseBody).sets + + return true + } catch { + sets.value = [] + error.value = LOAD_ERROR + + return false + } finally { + loading.value = false + } + } + + return { + sets, + loading, + error, + fetchSets, + } +}) diff --git a/frontend/website/src/views/DashboardView.vue b/frontend/website/src/views/DashboardView.vue index c61783f..182def5 100644 --- a/frontend/website/src/views/DashboardView.vue +++ b/frontend/website/src/views/DashboardView.vue @@ -1,12 +1,21 @@