# new_starr_app — Frontend React + Vite CSR frontend for the STARR LMS platform. --- ## Page Metadata Metadata (``, Open Graph, Twitter Card) is managed with **`react-helmet-async`**. The system has two layers: 1. **Global fallback** — a `<Helmet>` in `App.jsx` always renders default tags. Any page that does not supply its own metadata falls back to this. 2. **Per-page override** — each page renders a `<PageMeta />` component anywhere in its JSX tree. react-helmet-async lets the deepest/last `<Helmet>` win, so the page always overrides the fallback. This approach fixes the `useNavigate()` stale-title bug: when you navigate away from a page, its `<PageMeta>` unmounts and the global fallback immediately takes over. --- ### The component ```jsx // src/contexts/MetadataContext.jsx import { PageMeta } from '@/contexts/MetadataContext' ``` **Props** | Prop | Type | Default | |---|---|---| | `title` | `string \| undefined` | `'STARR \| Philproperties'` | | `description` | `string \| undefined` | app default description | | `keywords` | `string \| undefined` | app default keywords | | `ogImage` | `string \| undefined` | app default OG image URL | | `ogType` | `string` | `'website'` | Every prop is optional. Any omitted prop falls back to the app-level default. Passing `title={undefined}` explicitly is the same as omitting it — the fallback title renders. --- ### Static pages Pages with no data dependency just pass a hardcoded string. ```jsx import { PageMeta } from '@/contexts/MetadataContext' export default function CourseList() { return ( <section className="bg-muted h-full"> <PageMeta title="Courses - STARR" description="Browse and manage your training courses." /> {/* rest of page */} </section> ) } ``` --- ### Dynamic pages — data from context When the page title comes from an API response, pass the derived string. Use `undefined` (not a hardcoded fallback string) while the data is loading — this lets the global default render during the loading state and then automatically updates when data arrives. ```jsx import { PageMeta } from '@/contexts/MetadataContext' import { useCourses } from '@/contexts/AdminCoursesContext' export default function ViewCourse() { const { course } = useCourses() return ( <section className="bg-muted/60 min-h-full"> {/* undefined while course is null → global default renders */} <PageMeta title={course ? `${course.title} - STARR` : undefined} description={course?.description} /> {/* rest of page */} </section> ) } ``` --- ### Dynamic pages — data in local state Same pattern when the title comes from local `useState` instead of context. ```jsx import { PageMeta } from '@/contexts/MetadataContext' export default function EditUnit() { const [unitTitle, setUnitTitle] = useState('') // ... fetch and setUnitTitle on load return ( <section className="bg-muted h-full"> <PageMeta title={unitTitle ? `Edit: ${unitTitle} - STARR` : undefined} /> {/* rest of page */} </section> ) } ``` --- ### Placement rules - Drop `<PageMeta>` as the **first child** of the root element in the page's main `return`. - Do **not** add it to early/loading returns (spinner-only returns). The global fallback covers those. - Do **not** wrap the entire page in `<PageMeta>` — it is an inline element, not a wrapper. ```jsx // CORRECT — inline inside root element return ( <section> <PageMeta title="..." /> <div>...</div> </section> ) // WRONG — do not use as a wrapper return ( <PageMeta title="..."> <section>...</section> </PageMeta> ) ``` --- ### Title convention | Page type | Pattern | Example | |---|---|---| | List | `{Entity} - STARR` | `Courses - STARR` | | Archived list | `Archived {Entity} - STARR` | `Archived Courses - STARR` | | Create | `Add {Entity} - STARR` | `Add Course - STARR` | | View (dynamic) | `{name} - STARR` | `Intro to Sales - STARR` | | Edit (dynamic) | `Edit: {name} - STARR` | `Edit: Intro to Sales - STARR` | | Sub-list | `{SubEntity} – {parentName} - STARR` | `Units – Intro to Sales - STARR` | | Nested action | `{Action} – {parentName} - STARR` | `Page Builder – Lesson 1 - STARR` | --- ### Adding metadata to a new page — checklist 1. Import `PageMeta` from `@/contexts/MetadataContext`. 2. Identify what data (if any) drives the title — context state, local state, or nothing. 3. Place `<PageMeta title="..." />` as the first child of the root element in the main `return`. 4. Use the title convention above. 5. Pass `description` if the page has meaningful content to describe (e.g. a course description). 6. Pass `title={undefined}` (or omit `title`) while data is still loading. --- ### MetadataProvider (legacy) `Login.jsx` and `Register.jsx` still use the old wrapper pattern via `MetadataProvider`. This is a backward-compat shim that internally renders `<PageMeta {...value} />` and then its children. It works but should not be used for new pages — use the inline `<PageMeta />` pattern instead. ```jsx // Old pattern (Login / Register only — do not copy for new pages) <MetadataProvider value={{ title: 'Login - Philproperties', description: '...' }}> <LoginContent /> </MetadataProvider> // New pattern (use this for all new pages) <section> <PageMeta title="Login - Philproperties" description="..." /> <LoginContent /> </section> ```