diff --git a/frontend/website/src/components/AuthenticatedHeader.vue b/frontend/website/src/components/AuthenticatedHeader.vue new file mode 100644 index 0000000..eae4d57 --- /dev/null +++ b/frontend/website/src/components/AuthenticatedHeader.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/website/src/components/ElementTree.vue b/frontend/website/src/components/ElementTree.vue new file mode 100644 index 0000000..6cc1302 --- /dev/null +++ b/frontend/website/src/components/ElementTree.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/frontend/website/src/router/index.ts b/frontend/website/src/router/index.ts index c55b993..08f1ad0 100644 --- a/frontend/website/src/router/index.ts +++ b/frontend/website/src/router/index.ts @@ -63,6 +63,14 @@ const router = createRouter({ requiresAuth: true, }, }, + { + path: '/sets/:setId(\\d+)', + name: 'set-layout', + component: () => import('@/views/SetLayoutView.vue'), + meta: { + requiresAuth: true, + }, + }, ], }) diff --git a/frontend/website/src/stores/setLayout.ts b/frontend/website/src/stores/setLayout.ts new file mode 100644 index 0000000..919c108 --- /dev/null +++ b/frontend/website/src/stores/setLayout.ts @@ -0,0 +1,102 @@ +import { ref } from 'vue' +import { defineStore } from 'pinia' +import { z } from 'zod' + +import { API_BASE } from '@/utils/apiBase' + +export const setElementNodeSchema = z.object({ + id: z.number().int().positive(), + name: z.string().min(1), + kind: z.string().min(1), + get children() { + return z.array(setElementNodeSchema) + }, +}) + +export const setLayoutResponseSchema = z.object({ + set: z.object({ + id: z.number().int().positive(), + name: z.string().min(1), + }), + elements: z.array(setElementNodeSchema), +}) + +export type SetElementNode = z.infer +export type SetLayoutResponse = z.infer + +const LOAD_ERROR = "We couldn't load this set's layout." + +export const useSetLayoutStore = defineStore('set-layout', () => { + const layout = ref(null) + const loading = ref(false) + const error = ref(null) + const notFound = ref(false) + let activeRequestId = 0 + + async function fetchSetLayout(setId: number): Promise { + const requestId = ++activeRequestId + layout.value = null + loading.value = true + error.value = null + notFound.value = false + + try { + const response = await fetch(`${API_BASE}/api/sets/${setId}`, { + method: 'GET', + credentials: 'include', + headers: { + Accept: 'application/json', + }, + }) + + if (requestId !== activeRequestId) { + return false + } + + if (response.status === 404) { + notFound.value = true + + return false + } + + if (response.status !== 200) { + error.value = LOAD_ERROR + + return false + } + + const responseBody: unknown = await response.json() + if (requestId !== activeRequestId) { + return false + } + + const parsedLayout = setLayoutResponseSchema.parse(responseBody) + if (parsedLayout.set.id !== setId) { + throw new Error('set response did not match requested set') + } + + layout.value = parsedLayout + + return true + } catch { + if (requestId === activeRequestId) { + layout.value = null + error.value = LOAD_ERROR + } + + return false + } finally { + if (requestId === activeRequestId) { + loading.value = false + } + } + } + + return { + layout, + loading, + error, + notFound, + fetchSetLayout, + } +}) diff --git a/frontend/website/src/views/DashboardView.vue b/frontend/website/src/views/DashboardView.vue index 182def5..9c77bf4 100644 --- a/frontend/website/src/views/DashboardView.vue +++ b/frontend/website/src/views/DashboardView.vue @@ -1,33 +1,21 @@