From 367a98308a8bf38adba3760ed7de89eddb83d992 Mon Sep 17 00:00:00 2001 From: Aneurin Barker Snook Date: Sat, 5 Sep 2026 01:25:37 +0100 Subject: [PATCH] Extract a shared ManageMenu; add notFoundOr for the repeated 404 pattern ManageMenu.vue holds what ProjectManageMenu and CardManageMenu had copy-pasted between them almost verbatim: the dropdown, the confirm modal, both useDialog wirings, menuOpen/confirmingDelete/deleting state. Each is now a thin wrapper supplying only what's genuinely entity-specific -- the Configure route, the delete labels, and (as confirmDelete) what deleting actually does, since that differs more than the UI around it (which stores to refresh, where to navigate). The confirmation body text comes through the default slot, since a project's names its card count and a card's doesn't. notFoundOr(e, notFoundMessage, fallbackMessage) in lib/api.ts replaces the identical "404 -> fixed message, else -> the error's own message, else -> a fallback" block that ProjectView, ProjectConfigureView, CardView, and CardConfigureView had each written out by hand. Co-Authored-By: Claude Sonnet 5 --- web/src/components/CardManageMenu.vue | 124 ++++------------------- web/src/components/ManageMenu.vue | 114 +++++++++++++++++++++ web/src/components/ProjectManageMenu.vue | 119 ++++------------------ web/src/lib/api.ts | 11 ++ web/src/views/CardConfigureView.vue | 8 +- web/src/views/CardView.vue | 8 +- web/src/views/ProjectConfigureView.vue | 8 +- web/src/views/ProjectView.vue | 8 +- 8 files changed, 174 insertions(+), 226 deletions(-) create mode 100644 web/src/components/ManageMenu.vue diff --git a/web/src/components/CardManageMenu.vue b/web/src/components/CardManageMenu.vue index 2d7d7d2..51cf96d 100644 --- a/web/src/components/CardManageMenu.vue +++ b/web/src/components/CardManageMenu.vue @@ -1,124 +1,44 @@ diff --git a/web/src/components/ManageMenu.vue b/web/src/components/ManageMenu.vue new file mode 100644 index 0000000..4f70e26 --- /dev/null +++ b/web/src/components/ManageMenu.vue @@ -0,0 +1,114 @@ + + + diff --git a/web/src/components/ProjectManageMenu.vue b/web/src/components/ProjectManageMenu.vue index cb26afd..2368e66 100644 --- a/web/src/components/ProjectManageMenu.vue +++ b/web/src/components/ProjectManageMenu.vue @@ -1,121 +1,40 @@ diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index d4d4e96..7c2ca99 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -38,6 +38,17 @@ export class ApiError extends Error { } } +/** + * A message for a caught error: a fixed one for "not found" (404), the + * server's own message for any other API error, and a fallback for + * anything else (e.g. a network failure) -- the shape every view's initial + * load uses to turn a failed fetch into what it shows the user. + */ +export function notFoundOr(e: unknown, notFoundMessage: string, fallbackMessage: string): string { + if (e instanceof ApiError && e.status === 404) return notFoundMessage + return e instanceof ApiError ? e.message : fallbackMessage +} + interface RequestOptions { method?: string body?: unknown diff --git a/web/src/views/CardConfigureView.vue b/web/src/views/CardConfigureView.vue index 400d400..9d976c5 100644 --- a/web/src/views/CardConfigureView.vue +++ b/web/src/views/CardConfigureView.vue @@ -2,7 +2,7 @@ import { onMounted, ref } from 'vue' import { useRoute } from 'vue-router' import CardManageMenu from '../components/CardManageMenu.vue' -import { ApiError, apiRequest } from '../lib/api' +import { ApiError, apiRequest, notFoundOr } from '../lib/api' import { useCardsStore } from '../stores/cards' import { useInboxStore } from '../stores/inbox' import type { Card } from '../types' @@ -24,11 +24,7 @@ async function load() { card.value = fetched textDraft.value = fetched.text } catch (e) { - if (e instanceof ApiError && e.status === 404) { - loadError.value = 'That card does not exist.' - } else { - loadError.value = e instanceof ApiError ? e.message : 'Could not load the card.' - } + loadError.value = notFoundOr(e, 'That card does not exist.', 'Could not load the card.') } } diff --git a/web/src/views/CardView.vue b/web/src/views/CardView.vue index a01e825..a3f923b 100644 --- a/web/src/views/CardView.vue +++ b/web/src/views/CardView.vue @@ -2,7 +2,7 @@ import { computed, onMounted, ref } from 'vue' import { useRoute, type RouteLocationRaw } from 'vue-router' import CardManageMenu from '../components/CardManageMenu.vue' -import { ApiError, apiRequest } from '../lib/api' +import { apiRequest, notFoundOr } from '../lib/api' import type { Card } from '../types' const route = useRoute() @@ -19,11 +19,7 @@ async function load() { const { card: fetched } = await apiRequest<{ card: Card }>(`/cards/${cardId}`, { auth: true }) card.value = fetched } catch (e) { - if (e instanceof ApiError && e.status === 404) { - loadError.value = 'That card does not exist.' - } else { - loadError.value = e instanceof ApiError ? e.message : 'Could not load the card.' - } + loadError.value = notFoundOr(e, 'That card does not exist.', 'Could not load the card.') } } diff --git a/web/src/views/ProjectConfigureView.vue b/web/src/views/ProjectConfigureView.vue index 2463030..607d59d 100644 --- a/web/src/views/ProjectConfigureView.vue +++ b/web/src/views/ProjectConfigureView.vue @@ -3,7 +3,7 @@ import { onMounted, ref } from 'vue' import { useRoute } from 'vue-router' import ProjectManageMenu from '../components/ProjectManageMenu.vue' import StatusManager from '../components/StatusManager.vue' -import { ApiError, apiRequest } from '../lib/api' +import { ApiError, apiRequest, notFoundOr } from '../lib/api' import { useProjectsStore } from '../stores/projects' import type { Project } from '../types' @@ -23,11 +23,7 @@ async function load() { project.value = fetched nameDraft.value = fetched.title } catch (e) { - if (e instanceof ApiError && e.status === 404) { - loadError.value = 'That project does not exist.' - } else { - loadError.value = e instanceof ApiError ? e.message : 'Could not load the project.' - } + loadError.value = notFoundOr(e, 'That project does not exist.', 'Could not load the project.') } } diff --git a/web/src/views/ProjectView.vue b/web/src/views/ProjectView.vue index 8058d6a..e3a5b61 100644 --- a/web/src/views/ProjectView.vue +++ b/web/src/views/ProjectView.vue @@ -2,7 +2,7 @@ import { onMounted, ref } from 'vue' import { useRoute } from 'vue-router' import ProjectManageMenu from '../components/ProjectManageMenu.vue' -import { ApiError, apiRequest } from '../lib/api' +import { apiRequest, notFoundOr } from '../lib/api' import { useCardsStore } from '../stores/cards' import type { Project } from '../types' @@ -30,11 +30,7 @@ async function load() { cards.load(projectId), ]) } catch (e) { - if (e instanceof ApiError && e.status === 404) { - loadError.value = 'That project does not exist.' - } else { - loadError.value = e instanceof ApiError ? e.message : 'Could not load the project.' - } + loadError.value = notFoundOr(e, 'That project does not exist.', 'Could not load the project.') } }