mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -1,16 +1,177 @@
|
||||
# React + Vite
|
||||
# new_starr_app — Frontend
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
React + Vite CSR frontend for the STARR LMS platform.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
---
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
## Page Metadata
|
||||
|
||||
## React Compiler
|
||||
Metadata (`<title>`, Open Graph, Twitter Card) is managed with **`react-helmet-async`**.
|
||||
The system has two layers:
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
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.
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
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.
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
---
|
||||
|
||||
### 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/60 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/60 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>
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user