diff --git a/apps/journal/app/root.tsx b/apps/journal/app/root.tsx index 51db198..fb07974 100644 --- a/apps/journal/app/root.tsx +++ b/apps/journal/app/root.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from "react-router"; +import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse, useLocation, Form, Link } from "react-router"; import type { LinksFunction } from "react-router"; import type { Route } from "./+types/root"; import * as Sentry from "@sentry/react"; @@ -35,6 +35,75 @@ export async function loader({ request }: Route.LoaderArgs) { return { user: user ? { id: user.id, username: user.username } : null }; } +function NavBar({ user }: { user: { id: string; username: string } | null }) { + const { t } = useTranslation("journal"); + const location = useLocation(); + + const isActive = (path: string) => + location.pathname === path || location.pathname.startsWith(path + "/"); + + const linkClass = (path: string) => + `text-sm font-medium ${ + isActive(path) + ? "text-blue-600" + : "text-gray-600 hover:text-gray-900" + }`; + + return ( + + ); +} + export default function App({ loaderData }: Route.ComponentProps) { const user = loaderData?.user; useEffect(() => { @@ -45,7 +114,12 @@ export default function App({ loaderData }: Route.ComponentProps) { } }, [user]); - return ; + return ( + <> + + + + ); } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { diff --git a/apps/planner/app/components/SessionView.tsx b/apps/planner/app/components/SessionView.tsx index bf56abc..615d043 100644 --- a/apps/planner/app/components/SessionView.tsx +++ b/apps/planner/app/components/SessionView.tsx @@ -1,5 +1,6 @@ import { Suspense, lazy, useState, useCallback, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { Link } from "react-router"; import * as Sentry from "@sentry/react"; import { useYjs } from "~/lib/use-yjs"; import { useRouting } from "~/lib/use-routing"; @@ -49,7 +50,9 @@ export function SessionView({ sessionId, callbackUrl, callbackToken, returnUrl, <>
-

{t("title")}

+ + {t("title")} +
diff --git a/e2e/journal.test.ts b/e2e/journal.test.ts index b2863fa..ad94506 100644 --- a/e2e/journal.test.ts +++ b/e2e/journal.test.ts @@ -9,8 +9,9 @@ test.describe("Journal", () => { test("shows register and sign in when logged out", async ({ page }) => { await page.goto("/"); - await expect(page.getByRole("link", { name: "Register" })).toBeVisible(); - await expect(page.getByRole("link", { name: "Sign in" })).toBeVisible(); + // Nav bar has Register and Sign In links + await expect(page.getByRole("navigation").getByRole("link", { name: "Register" })).toBeVisible(); + await expect(page.getByRole("navigation").getByRole("link", { name: "Sign In" })).toBeVisible(); }); test("registration page renders correctly", async ({ page }) => { @@ -29,6 +30,17 @@ test.describe("Journal", () => { await expect(page.getByText(/magic link/i)).toBeVisible(); }); + test("nav bar shows on all pages", async ({ page }) => { + await page.goto("/"); + const nav = page.getByRole("navigation"); + await expect(nav).toBeVisible(); + // Logo/title links home + await expect(nav.getByRole("link", { name: "trails.cool" })).toBeVisible(); + // Logged-out nav does not show Routes or Activities + await expect(nav.getByRole("link", { name: "Routes" })).not.toBeVisible(); + await expect(nav.getByRole("link", { name: "Activities" })).not.toBeVisible(); + }); + test("routes page redirects to login when not authenticated", async ({ page }) => { await page.goto("/routes"); await expect(page).toHaveURL(/auth\/login/); diff --git a/e2e/planner.test.ts b/e2e/planner.test.ts index 67d6c97..316988b 100644 --- a/e2e/planner.test.ts +++ b/e2e/planner.test.ts @@ -20,7 +20,8 @@ test.describe("Planner", () => { const { url } = await response.json(); await page.goto(url); - await expect(page.getByText("trails.cool Planner")).toBeVisible(); + // Title is now a home link + await expect(page.getByRole("link", { name: "trails.cool Planner" })).toBeVisible(); await expect(page.locator(".leaflet-container")).toBeVisible({ timeout: 10000 }); }); diff --git a/openspec/changes/app-navigation/tasks.md b/openspec/changes/app-navigation/tasks.md index dbbc1d7..b4eb4f2 100644 --- a/openspec/changes/app-navigation/tasks.md +++ b/openspec/changes/app-navigation/tasks.md @@ -1,18 +1,18 @@ ## 1. Journal Navigation Bar -- [ ] 1.1 Add nav bar to Journal root Layout — show "Routes", "Activities" links when logged in; "Login", "Register" when logged out -- [ ] 1.2 Add user menu to nav bar — username/display name linking to profile, logout button -- [ ] 1.3 Highlight active nav item based on current route -- [ ] 1.4 Add i18n keys for nav labels (en + de): routes, activities, profile, logout +- [x] 1.1 Add nav bar to Journal root Layout — show "Routes", "Activities" links when logged in; "Login", "Register" when logged out +- [x] 1.2 Add user menu to nav bar — username/display name linking to profile, logout button +- [x] 1.3 Highlight active nav item based on current route +- [x] 1.4 Add i18n keys for nav labels (en + de): routes, activities, profile, logout ## 2. Planner Navigation -- [ ] 2.1 Add "Start Planning" CTA button on Planner home page linking to `/new` -- [ ] 2.2 Add home link (logo or text) to SessionView header -- [ ] 2.3 Add i18n keys for planner nav (en + de): startPlanning, home +- [x] 2.1 Add "Start Planning" CTA button on Planner home page linking to `/new` (already done in planner-landing-page change) +- [x] 2.2 Add home link (logo or text) to SessionView header +- [x] 2.3 Add i18n keys for planner nav (en + de): startPlanning, home ## 3. Verify -- [ ] 3.1 Verify all Journal routes are reachable via navigation (routes, activities, profile, auth, privacy) -- [ ] 3.2 Verify Planner home → new session → back to home works -- [ ] 3.3 Update E2E tests if navigation changes affect existing selectors +- [x] 3.1 Verify all Journal routes are reachable via navigation (routes, activities, profile, auth, privacy) +- [x] 3.2 Verify Planner home → new session → back to home works +- [x] 3.3 Update E2E tests if navigation changes affect existing selectors diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index dea861c..2fb244c 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -14,6 +14,7 @@ export default { planner: { title: "trails.cool Planer", subtitle: "Gemeinsame Routenplanung", + home: "Startseite", newSession: "Neue Sitzung", saveRoute: "Route speichern", exportGpx: "GPX exportieren", @@ -65,6 +66,14 @@ export default { journal: { title: "trails.cool", subtitle: "Dein Outdoor-Aktivitäten-Tagebuch", + nav: { + routes: "Routen", + activities: "Aktivitäten", + login: "Anmelden", + register: "Registrieren", + profile: "Profil", + logout: "Abmelden", + }, welcome: "Willkommen,", addPasskeyPrompt: "Füge einen Passkey hinzu, um dich schneller auf diesem Gerät anzumelden.", addPasskey: "Passkey hinzufügen", diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index 3842de0..74b3288 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -14,6 +14,7 @@ export default { planner: { title: "trails.cool Planner", subtitle: "Collaborative route planning", + home: "Home", newSession: "New Session", saveRoute: "Save Route", exportGpx: "Export GPX", @@ -65,6 +66,14 @@ export default { journal: { title: "trails.cool", subtitle: "Your outdoor activity journal", + nav: { + routes: "Routes", + activities: "Activities", + login: "Sign In", + register: "Register", + profile: "Profile", + logout: "Log Out", + }, welcome: "Welcome,", addPasskeyPrompt: "Add a passkey for faster sign-in on this device.", addPasskey: "Add Passkey",