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
+45
View File
@@ -0,0 +1,45 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { api } from "../api/client";
export interface User {
id: string;
email: string;
display_name: string;
email_verified: boolean;
}
export const useSessionStore = defineStore("session", () => {
const user = ref<User | null>(null);
// Whether we've resolved the initial /me check yet (guards against redirect flashes).
const loaded = ref(false);
async function fetchMe(): Promise<void> {
try {
user.value = await api.get<User>("/api/auth/me");
} catch {
user.value = null;
} finally {
loaded.value = true;
}
}
async function login(email: string, password: string): Promise<void> {
user.value = await api.post<User>("/api/auth/login", { email, password });
}
async function register(email: string, password: string, displayName: string): Promise<void> {
user.value = await api.post<User>("/api/auth/register", {
email,
password,
display_name: displayName,
});
}
async function logout(): Promise<void> {
await api.post("/api/auth/logout");
user.value = null;
}
return { user, loaded, fetchMe, login, register, logout };
});