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
+43
View File
@@ -0,0 +1,43 @@
import { createRouter, createWebHistory } from "vue-router";
import { useSessionStore } from "../stores/session";
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/",
name: "board",
component: () => import("../views/BoardView.vue"),
meta: { requiresAuth: true },
},
{
path: "/login",
name: "login",
component: () => import("../views/LoginView.vue"),
meta: { guestOnly: true },
},
{
path: "/register",
name: "register",
component: () => import("../views/RegisterView.vue"),
meta: { guestOnly: true },
},
],
});
router.beforeEach(async (to) => {
const session = useSessionStore();
// Resolve the current user once, lazily, before the first guarded navigation.
if (!session.loaded) {
await session.fetchMe();
}
if (to.meta.requiresAuth && !session.user) {
return { name: "login", query: to.fullPath !== "/" ? { redirect: to.fullPath } : undefined };
}
if (to.meta.guestOnly && session.user) {
return { name: "board" };
}
return true;
});
export default router;