2026-06-22 10:07:20 +08:00
2026-06-22 10:07:20 +08:00
2026-07-03 16:21:27 +08:00
2026-06-22 10:07:20 +08:00
2026-07-14 17:09:42 +08:00
2026-06-22 10:07:20 +08:00
2026-06-22 10:07:20 +08:00
2026-05-05 23:16:22 +08:00
2026-07-29 03:25:31 +08:00
2026-05-05 23:16:22 +08:00
2026-06-30 19:32:00 +08:00
2026-06-22 10:07:20 +08:00
2026-07-20 22:06:18 +08:00
2026-07-20 22:06:18 +08:00
2026-06-22 10:07:20 +08:00
2026-06-30 19:32:00 +08:00
2026-07-13 21:47:31 +08:00
2026-06-22 10:07:20 +08:00

new_starr_app — Frontend

React + Vite CSR frontend for the STARR LMS platform.


Page Metadata

Metadata (<title>, 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

// 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.

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.

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.

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.
// 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.

// 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>
S
Description
A course & training platform currently live in staging test deployment.
https://starr.philproperties.orij.space Readme Apache-2.0
5.5 MiB
Languages
JavaScript 99.4%
CSS 0.5%