M0: Vue 3 + TypeScript frontend — auth views + authed board shell

- Vite + Vue 3.5 + Pinia + vue-router + Tailwind (brand accent #F5C518),
  dark-mode aware, deterministic package-lock.json for `npm ci`.
- Session store (fetchMe/login/register/logout) over a credentials:'include'
  fetch client; router guards (requiresAuth / guestOnly) with lazy /me resolve.
- BaseButton + BaseInput primitives (focus rings, loading, error states).
- LoginView, RegisterView, and an authed BoardView shell with an empty state
  for the M1 masonry board — all at v1 polish (rule 24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-19 13:17:38 -04:00
co-authored by Claude Opus 4.8
parent 04e3ab20cf
commit bf3d403648
20 changed files with 3173 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
export interface ApiError {
error: string;
status: number;
}
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
const resp = await fetch(path, {
method,
credentials: "include", // send/receive the signed session cookie
headers: body !== undefined ? { "Content-Type": "application/json" } : undefined,
body: body !== undefined ? JSON.stringify(body) : undefined,
});
const text = await resp.text();
const data: unknown = text ? JSON.parse(text) : {};
if (!resp.ok) {
const message =
typeof data === "object" && data !== null && "error" in data
? String((data as { error: unknown }).error)
: "Request failed.";
const err: ApiError = { error: message, status: resp.status };
throw err;
}
return data as T;
}
export const api = {
get: <T>(path: string) => request<T>("GET", path),
post: <T>(path: string, body?: unknown) => request<T>("POST", path, body),
};