Files
starr-philproperties/README.md
T
kennethobsequio 17326b2c2e testing 101
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-30 19:32:00 +08:00

178 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```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>
```