diff --git a/web/src/auth/login-page.tsx b/web/src/auth/login-page.tsx index 6009685..0193a70 100644 --- a/web/src/auth/login-page.tsx +++ b/web/src/auth/login-page.tsx @@ -1,4 +1,4 @@ -import { useState, type FormEvent } from "react"; +import { useEffect, useState, type FormEvent } from "react"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; @@ -14,6 +14,10 @@ export function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + useEffect(() => { + document.title = t("app.name"); + }, [t]); + const onSubmit = (event: FormEvent) => { event.preventDefault(); login.mutate( diff --git a/web/src/objects/object-detail.test.tsx b/web/src/objects/object-detail.test.tsx index 228ae36..ab05685 100644 --- a/web/src/objects/object-detail.test.tsx +++ b/web/src/objects/object-detail.test.tsx @@ -1,5 +1,5 @@ import { expect, test } from "vitest"; -import { screen, within } from "@testing-library/react"; +import { screen, waitFor, within } from "@testing-library/react"; import { http, HttpResponse } from "msw"; import { Routes, Route } from "react-router-dom"; import { server } from "../test/server"; @@ -96,6 +96,14 @@ test("shows a not-found state for a missing object", async () => { expect(await screen.findByText(/object not found/i)).toBeInTheDocument(); }); +test("sets the tab title to the object number and reverts on unmount", async () => { + document.title = "Base"; + const { unmount } = renderDetail(); + await waitFor(() => expect(document.title).toMatch(/^LM-0042 \| /)); + unmount(); + expect(document.title).toBe("Base"); +}); + test("detail shows the publish control with the current visibility stepper", async () => { // default GET /api/admin/objects/:id handler returns amphora (visibility "public") renderApp(tree(), { route: "/objects/11111111-1111-1111-1111-111111111111" }); diff --git a/web/src/objects/object-detail.tsx b/web/src/objects/object-detail.tsx index b879ae3..fc6e2ac 100644 --- a/web/src/objects/object-detail.tsx +++ b/web/src/objects/object-detail.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import type { components } from "../api/schema"; import { useObject, useFieldDefinitions } from "../api/queries"; import { formatDate } from "../lib/format-date"; +import { useDocumentTitle } from "../lib/use-document-title"; import { DeleteObjectDialog } from "./delete-object-dialog"; import { FlexibleFieldValue } from "./flexible-field-value"; import { PublishControl } from "./publish-control"; @@ -13,6 +14,7 @@ import { buttonVariants } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; type FieldDefinitionView = components["schemas"]["FieldDefinitionView"]; +type AdminObjectView = components["schemas"]["AdminObjectView"]; function Field({ label, value }: { label: string; value: ReactNode }) { const empty = value === null || value === undefined || value === ""; @@ -26,10 +28,9 @@ function Field({ label, value }: { label: string; value: ReactNode }) { } export function ObjectDetail() { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const { id } = useParams(); const { data: object, isLoading, isError } = useObject(id!); - const { data: definitions } = useFieldDefinitions(); if (isLoading) { return ( @@ -43,6 +44,15 @@ export function ObjectDetail() { if (!object) return

{t("objects.notFound")}

; + return ; +} + +function ObjectDetailLoaded({ object }: { object: AdminObjectView }) { + const { t, i18n } = useTranslation(); + const { data: definitions } = useFieldDefinitions(); + + useDocumentTitle(object.object_number); + // Prefer the active locale's label, then English, then the raw key. const lang = i18n.language.startsWith("sv") ? "sv" : "en"; const labelFor = (key: string) => { diff --git a/web/src/vocab/vocabulary-terms.tsx b/web/src/vocab/vocabulary-terms.tsx index 3900f70..4a786a2 100644 --- a/web/src/vocab/vocabulary-terms.tsx +++ b/web/src/vocab/vocabulary-terms.tsx @@ -49,9 +49,9 @@ export function VocabularyTerms() { return (
-

+
{t("vocab.terms")} -

+