Toon leesbare slug in plaats van GUID-ID op pakketkaarten #14
@@ -17,8 +17,8 @@ reference designs in `References/` (`slp-software.html` = purple accent,
|
|||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
pnpm install
|
||||||
npm run dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The app runs at `http://localhost:5173/` by default.
|
The app runs at `http://localhost:5173/` by default.
|
||||||
@@ -27,13 +27,13 @@ The app runs at `http://localhost:5173/` by default.
|
|||||||
|
|
||||||
| Script | Description |
|
| Script | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `npm run dev` | Start the Vite dev server |
|
| `pnpm dev` | Start the Vite dev server |
|
||||||
| `npm run build` | Type-check and build a production bundle to `dist/` |
|
| `pnpm build` | Type-check and build a production bundle to `dist/` |
|
||||||
| `npm run preview` | Preview the production build locally |
|
| `pnpm preview` | Preview the production build locally |
|
||||||
| `npm run test` | Run the test suite once (Vitest) |
|
| `pnpm test` | Run the test suite once (Vitest) |
|
||||||
| `npm run test:watch` | Run the test suite in watch mode |
|
| `pnpm test:watch` | Run the test suite in watch mode |
|
||||||
| `npm run lint` | Run ESLint |
|
| `pnpm lint` | Run ESLint |
|
||||||
| `npm run format` | Format the codebase with Prettier |
|
| `pnpm format` | Format the codebase with Prettier |
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ for the full theme business rules.
|
|||||||
|
|
||||||
## Deployment Notes
|
## Deployment Notes
|
||||||
|
|
||||||
This app builds to a plain static bundle (`npm run build` → `dist/`), intended for
|
This app builds to a plain static bundle (`pnpm build` → `dist/`), intended for
|
||||||
upload to a traditional web host via FTP. TanStack Router uses hash-based history
|
upload to a traditional web host via FTP. TanStack Router uses hash-based history
|
||||||
(`/#/route`) so that client-side routing keeps working even on hosts without
|
(`/#/route`) so that client-side routing keeps working even on hosts without
|
||||||
server-side rewrite rules configured. See the feature's NFR Requirements/Design
|
server-side rewrite rules configured. See the feature's NFR Requirements/Design
|
||||||
|
|||||||
+2
-1
@@ -66,7 +66,8 @@ Text alternative: A ThemePreference selects a set of ThemeTokens; SiteContent ag
|
|||||||
### PackageCard
|
### PackageCard
|
||||||
| Field | Type | Required | Description |
|
| Field | Type | Required | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `id` | `string` | Yes | e.g. `pakket_01` |
|
| `id` | `string` | Yes | Backend record identifier (GUID). Not shown on the page. |
|
||||||
|
| `slug` | `string` | Yes | Readable, URL-safe identifier derived from `title`, e.g. `landingspagina`. Shown on the card instead of `id`. |
|
||||||
| `title` | `string` | Yes | e.g. "Landingspagina" |
|
| `title` | `string` | Yes | e.g. "Landingspagina" |
|
||||||
| `description` | `string` | Yes | Short description |
|
| `description` | `string` | Yes | Short description |
|
||||||
| `price` | `string` | Yes | e.g. "€ 300" or "Op maat" |
|
| `price` | `string` | Yes | e.g. "€ 300" or "Op maat" |
|
||||||
|
|||||||
+3
-1
@@ -49,6 +49,7 @@ client-side sorting):
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "pakket_01",
|
"id": "pakket_01",
|
||||||
|
"slug": "landingspagina",
|
||||||
"title": "Landingspagina",
|
"title": "Landingspagina",
|
||||||
"description": "Eén overtuigende pagina die je product of dienst helder neerzet.",
|
"description": "Eén overtuigende pagina die je product of dienst helder neerzet.",
|
||||||
"price": "€ 300",
|
"price": "€ 300",
|
||||||
@@ -73,7 +74,8 @@ for a structured error body.
|
|||||||
|
|
||||||
| Field | Type | Required | Content type / description | Used for |
|
| Field | Type | Required | Content type / description | Used for |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `id` | `string` | Yes | Stable slug-like identifier, e.g. `pakket_01`. Currently just an ordinal slug, not a business meaning — reuse as the CMS entry's own key/slug. | React list key + `data-testid` on the card, and referenced in tests. Must be unique and stable across edits (don't regenerate on every content change, or it'll be treated as a different card). |
|
| `id` | `string` | Yes | Stable record identifier (the CMS entry's own key, a GUID in practice). | React list key + `data-testid` on the card, and referenced in tests. Must be unique and stable across edits (don't regenerate on every content change, or it'll be treated as a different card). Not rendered on the page. |
|
||||||
|
| `slug` | `string` | Yes | Readable, URL-safe identifier derived from `title` (lowercase, hyphen-separated), e.g. `landingspagina`. Unique among offerings; regenerate it when `title` changes, keep it otherwise. | Displayed on the card in place of the raw `id`, so visitors see readable text instead of a GUID. |
|
||||||
| `title` | `string` | Yes | Short package name (e.g. "Landingspagina"). Plain text, no markup. | Card heading. |
|
| `title` | `string` | Yes | Short package name (e.g. "Landingspagina"). Plain text, no markup. | Card heading. |
|
||||||
| `description` | `string` | Yes | One-sentence pitch, plain text. | Subheading under the title. |
|
| `description` | `string` | Yes | One-sentence pitch, plain text. | Subheading under the title. |
|
||||||
| `price` | `string` | Yes | Pre-formatted price display string — e.g. `"€ 300"` or `"Op maat"` for the quote-based package. **Not a number**: it's rendered verbatim, so formatting (currency symbol, spacing, "Op maat") is the CMS editor's responsibility, not computed client-side. | Large price figure on the card. |
|
| `price` | `string` | Yes | Pre-formatted price display string — e.g. `"€ 300"` or `"Op maat"` for the quote-based package. **Not a number**: it's rendered verbatim, so formatting (currency symbol, spacing, "Op maat") is the CMS editor's responsibility, not computed client-side. | Large price figure on the card. |
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function PackageCard({ pkg }: { pkg: PackageCardData }) {
|
|||||||
Meest gekozen
|
Meest gekozen
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="font-mono mb-3.5 text-[0.78rem] text-accent">{pkg.id}</span>
|
<span className="font-mono mb-3.5 text-[0.78rem] text-accent">{pkg.slug}</span>
|
||||||
<h3 className="mb-2.5 text-xl font-bold">{pkg.title}</h3>
|
<h3 className="mb-2.5 text-xl font-bold">{pkg.title}</h3>
|
||||||
<p className="mb-6 text-[0.95rem] text-muted">{pkg.description}</p>
|
<p className="mb-6 text-[0.95rem] text-muted">{pkg.description}</p>
|
||||||
<div className="font-mono mb-1 text-3xl font-bold">
|
<div className="font-mono mb-1 text-3xl font-bold">
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface HeroContent {
|
|||||||
|
|
||||||
export interface PackageCardData {
|
export interface PackageCardData {
|
||||||
id: string;
|
id: string;
|
||||||
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
price: string;
|
price: string;
|
||||||
@@ -76,6 +77,7 @@ export const heroContent: HeroContent = {
|
|||||||
export const packages: PackageCardData[] = [
|
export const packages: PackageCardData[] = [
|
||||||
{
|
{
|
||||||
id: 'pakket_01',
|
id: 'pakket_01',
|
||||||
|
slug: 'landingspagina',
|
||||||
title: 'Landingspagina',
|
title: 'Landingspagina',
|
||||||
description: 'Eén overtuigende pagina die je product of dienst helder neerzet.',
|
description: 'Eén overtuigende pagina die je product of dienst helder neerzet.',
|
||||||
price: '€ 300',
|
price: '€ 300',
|
||||||
@@ -90,6 +92,7 @@ export const packages: PackageCardData[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'pakket_02',
|
id: 'pakket_02',
|
||||||
|
slug: 'website',
|
||||||
title: 'Website',
|
title: 'Website',
|
||||||
description: "Een complete website met meerdere pagina's, klaar om te groeien.",
|
description: "Een complete website met meerdere pagina's, klaar om te groeien.",
|
||||||
price: '€ 750',
|
price: '€ 750',
|
||||||
@@ -105,6 +108,7 @@ export const packages: PackageCardData[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'pakket_03',
|
id: 'pakket_03',
|
||||||
|
slug: 'maatwerk',
|
||||||
title: 'Maatwerk',
|
title: 'Maatwerk',
|
||||||
description: 'Grotere websites, een eigen back-end of andere .NET-projecten.',
|
description: 'Grotere websites, een eigen back-end of andere .NET-projecten.',
|
||||||
price: 'Op maat',
|
price: 'Op maat',
|
||||||
|
|||||||
Reference in New Issue
Block a user