Initial commit: React frontend (SLP Software) + AIDLC workflow docs

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-07-20 00:19:44 +02:00
co-authored by Junie
commit e299f1c745
73 changed files with 7275 additions and 0 deletions
@@ -0,0 +1,65 @@
# Business Logic Model — react-frontend-app
## Overview
This unit's business logic is small and UI-centric: rendering static marketing content and managing the theme (red default / purple alternate) selection and persistence. There is no backend business logic in this iteration.
## Process Flow: Page Load and Theme Resolution
```mermaid
graph TD
start_load["Visitor loads the site"]
read_storage["Read stored theme preference from localStorage"]
check_stored["Stored preference found?"]
use_stored["Use stored theme (red or purple)"]
use_default["Use default theme: red"]
apply_theme["Apply theme class/attribute to document root"]
load_content["Load static content module (nav, hero, packages, steps, about, contact)"]
init_query["Initialize QueryClientProvider and packages placeholder query"]
render_page["Render page sections via TanStack Router index route"]
start_load --> read_storage
read_storage --> check_stored
check_stored -->|"Yes"| use_stored
check_stored -->|"No"| use_default
use_stored --> apply_theme
use_default --> apply_theme
apply_theme --> load_content
load_content --> init_query
init_query --> render_page
classDef process fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef decision fill:#fbd38d,stroke:#92400e,stroke-width:1px,color:#000;
classDef terminal fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000;
class start_load,render_page terminal;
class read_storage,use_stored,use_default,apply_theme,load_content,init_query process;
class check_stored decision;
```
Text alternative: On load, the app reads a stored theme preference; if found it is used, otherwise red is used as default; the theme is applied to the document, static content is loaded, the query provider is initialized, then the page renders.
## Process Flow: Theme Switch Interaction
```mermaid
graph TD
click_toggle["Visitor clicks the theme toggle button in the nav"]
determine_next["Determine next theme (red to purple, or purple to red)"]
update_state["Update in-memory theme state (ThemeProvider context)"]
persist_storage["Persist chosen theme to localStorage"]
reapply_theme["Re-apply theme class/attribute to document root"]
update_toggle["Update toggle button visual state and aria-pressed"]
click_toggle --> determine_next
determine_next --> update_state
update_state --> persist_storage
persist_storage --> reapply_theme
reapply_theme --> update_toggle
classDef process fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef terminal fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000;
class click_toggle terminal;
class determine_next,update_state,persist_storage,reapply_theme,update_toggle process;
```
Text alternative: Clicking the toggle determines the other theme, updates in-memory state, persists it to localStorage, reapplies the theme to the document, and updates the toggle button's visual/accessible state.
@@ -0,0 +1,42 @@
# Business Rules — react-frontend-app
## BR-1: Default Theme Rule
The site MUST use the **red** theme when no stored theme preference exists (first visit, cleared storage, or unsupported storage).
## BR-2: Theme Persistence Rule
Whenever the visitor switches themes, the chosen theme MUST be written to `localStorage` immediately, so a page reload or new visit resolves to the same theme (BR-1 only applies when nothing is stored).
## BR-3: Valid Theme Values Rule
Only two theme values are valid: `red` and `purple`. If a stored value is anything else (corrupted/unexpected), the app MUST fall back to the default theme (`red`) rather than error.
## BR-4: Content Fidelity Rule
All rendered marketing copy, prices (€300 / €750 / "Op maat"), and the contact e-mail (`info@slpsoftware.nl`) MUST match the reference HTML designs exactly for this iteration (per requirements FR-1); no content may be altered, abbreviated, or replaced with placeholder text.
## BR-5: Reduced Motion Rule
When the visitor's OS/browser signals `prefers-reduced-motion: reduce`, both the hero caret blink animation AND the theme-switch color transition MUST be instant / non-animated.
## BR-6: Single Route Rule (current iteration)
For this iteration, all page sections (nav, hero, packages, process, about, contact, footer) are rendered under a single index route (`/`). No section requires its own route yet.
## Decision Flow: Theme Resolution on Load
```mermaid
graph TD
load_pref{"Stored theme value exists?"}
valid_check{"Stored value is 'red' or 'purple'?"}
use_stored_value["Use stored value as active theme"]
fallback_default["Fall back to default theme: red"]
load_pref -->|"No"| fallback_default
load_pref -->|"Yes"| valid_check
valid_check -->|"Yes"| use_stored_value
valid_check -->|"No (corrupted/unexpected)"| fallback_default
classDef decision fill:#fbd38d,stroke:#92400e,stroke-width:1px,color:#000;
classDef outcome fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
class load_pref,valid_check decision;
class use_stored_value,fallback_default outcome;
```
Text alternative: If no stored theme exists, or the stored value is not "red"/"purple", the app falls back to red; otherwise the valid stored value is used.
@@ -0,0 +1,97 @@
# Domain Entities — react-frontend-app
## Overview
This unit has no persisted backend entities. The "domain" here is the static content model and the theme model that drive rendering.
## Entity Relationships
```mermaid
graph TD
theme_pref["ThemePreference"]
theme_tokens["ThemeTokens"]
site_content["SiteContent"]
nav_link["NavLink"]
hero_content["HeroContent"]
package_card["PackageCard"]
process_step["ProcessStep"]
about_content["AboutContent"]
contact_info["ContactInfo"]
theme_pref -->|"selects"| theme_tokens
site_content -->|"has many"| nav_link
site_content -->|"has one"| hero_content
site_content -->|"has many"| package_card
site_content -->|"has many"| process_step
site_content -->|"has one"| about_content
site_content -->|"has one"| contact_info
classDef entity fill:#2196F3,stroke:#0d47a1,stroke-width:1px,color:#000;
classDef value fill:#FF9800,stroke:#e65100,stroke-width:1px,color:#000;
class theme_pref,site_content entity;
class theme_tokens,nav_link,hero_content,package_card,process_step,about_content,contact_info value;
```
Text alternative: A ThemePreference selects a set of ThemeTokens; SiteContent aggregates NavLinks, one HeroContent, many PackageCards, many ProcessSteps, one AboutContent, and one ContactInfo (blue = stateful entity, orange = static value objects).
## Entity Definitions
### ThemePreference
| Field | Type | Required | Description |
|---|---|---|---|
| `value` | `'red' \| 'purple'` | Yes | Currently active theme; defaults to `'red'` per BR-1 |
| `source` | `'stored' \| 'default'` | Yes | Whether the value came from `localStorage` or the default fallback |
### ThemeTokens
| Field | Type | Required | Description |
|---|---|---|---|
| `bg`, `surface`, `surfaceAlt`, `line`, `text`, `muted` | `string` (hex) | Yes | Neutral palette tokens, taken 1-to-1 from the reference CSS variables |
| `accent`, `accentSoft`, `accentLine` | `string` (hex/rgba) | Yes | Accent palette tokens (red or purple variant), taken 1-to-1 from the reference CSS variables |
### NavLink
| Field | Type | Required | Description |
|---|---|---|---|
| `label` | `string` | Yes | Link text (e.g. "Pakketten") |
| `href` | `string` | Yes | Anchor target (e.g. "#pakketten") |
| `isCta` | `boolean` | No | Marks the "Start project" call-to-action link |
### HeroContent
| Field | Type | Required | Description |
|---|---|---|---|
| `eyebrow` | `string` | Yes | Small label above the heading |
| `heading` | `string` | Yes | Main H1 text |
| `lead` | `string` | Yes | Lead paragraph text |
| `codeLine` | `string` | Yes | The animated code-line snippet text |
### PackageCard
| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | Yes | e.g. `pakket_01` |
| `title` | `string` | Yes | e.g. "Landingspagina" |
| `description` | `string` | Yes | Short description |
| `price` | `string` | Yes | e.g. "€ 300" or "Op maat" |
| `priceNote` | `string` | Yes | e.g. "eenmalig, excl. btw" |
| `features` | `string[]` | Yes | Bullet list of included features |
| `ctaLabel` | `string` | Yes | Button text |
| `featured` | `boolean` | No | Marks the "Meest gekozen" (most chosen) card |
### ProcessStep
| Field | Type | Required | Description |
|---|---|---|---|
| `label` | `string` | Yes | e.g. "stap 01 — intake" |
| `title` | `string` | Yes | e.g. "Kennismaken" |
| `description` | `string` | Yes | Step description |
### AboutContent
| Field | Type | Required | Description |
|---|---|---|---|
| `paragraphs` | `string[]` | Yes | About-section body paragraphs |
| `techStack` | `{ label: string; value: string }[]` | Yes | Tech-stack list items (Front-end, Back-end, API's, Focus) |
### ContactInfo
| Field | Type | Required | Description |
|---|---|---|---|
| `heading` | `string` | Yes | Contact section heading |
| `description` | `string` | Yes | Contact section body text |
| `email` | `string` | Yes | `info@slpsoftware.nl` |
| `mailSubject` | `string` | Yes | Prefilled mailto subject |
@@ -0,0 +1,77 @@
# Frontend Components — react-frontend-app
## Component Hierarchy
```mermaid
graph TD
root_route["__root.tsx\n(Root Route: ThemeProvider + QueryClientProvider)"]
layout["RootLayout\n(Nav + Footer wrapper)"]
index_route["index.tsx\n(/ Index Route)"]
nav["Nav"]
theme_toggle["ThemeToggle"]
hero["Hero"]
packages_section["PackagesSection"]
package_card["PackageCard (x3)"]
process_section["ProcessSection"]
process_step["ProcessStep (x3)"]
about_section["AboutSection"]
contact_section["ContactSection"]
footer["Footer"]
root_route --> layout
layout --> nav
nav --> theme_toggle
layout --> index_route
index_route --> hero
index_route --> packages_section
packages_section --> package_card
index_route --> process_section
process_section --> process_step
index_route --> about_section
index_route --> contact_section
layout --> footer
classDef root_node fill:#4CAF50,stroke:#2e7d32,stroke-width:2px,color:#000;
classDef layout_node fill:#2196F3,stroke:#0d47a1,stroke-width:1px,color:#000;
classDef page_node fill:#2196F3,stroke:#0d47a1,stroke-width:1px,color:#000;
classDef guard_node fill:#FF9800,stroke:#e65100,stroke-width:1px,color:#000;
class root_route root_node;
class layout,index_route layout_node;
class nav,hero,packages_section,package_card,process_section,process_step,about_section,contact_section,footer page_node;
class theme_toggle guard_node;
```
Text alternative: The root route provides ThemeProvider and QueryClientProvider and renders a RootLayout (Nav with ThemeToggle, plus Footer) wrapping the index route, which renders Hero, PackagesSection (three PackageCard instances), ProcessSection (three ProcessStep instances), AboutSection, and ContactSection.
## Components: Props and State
| Component | Props | State | Notes |
|---|---|---|---|
| `RootRoute` (`__root.tsx`) | — | — | Hosts `ThemeProvider` and `QueryClientProvider`; renders `<Outlet />` |
| `ThemeProvider` | `children: ReactNode` | `theme: 'red' \| 'purple'` (from context) | Reads/writes `localStorage`; exposes `theme` and `toggleTheme()` via context; applies `theme-red`/`theme-purple` class to `<html>` |
| `RootLayout` | `children: ReactNode` | — | Renders `Nav`, `children` (routed content), `Footer` |
| `Nav` | `links: NavLink[]` | — | Renders logo, `nav-links`, `ThemeToggle`, CTA link |
| `ThemeToggle` | — | — | Reads `theme`/`toggleTheme` from `ThemeProvider` context; `aria-label="Wissel kleurthema"`, `aria-pressed` reflects whether purple is active |
| `Hero` | `content: HeroContent` | — | Renders eyebrow, heading, lead, animated code line (caret respects `prefers-reduced-motion`), two CTA buttons |
| `PackagesSection` | `packages: PackageCard[]` | — | Renders section head + grid of `PackageCard` |
| `PackageCard` | `pkg: PackageCard` | — | Renders one pricing card; `featured` prop styling for "Meest gekozen" |
| `ProcessSection` | `steps: ProcessStep[]` | — | Renders section head + grid of `ProcessStep` |
| `ProcessStep` | `step: ProcessStep` | — | Renders one process step (label, title, description) |
| `AboutSection` | `content: AboutContent` | — | Renders paragraphs + tech-stack panel |
| `ContactSection` | `content: ContactInfo` | — | Renders contact box with mailto CTA |
| `Footer` | — | — | Renders copyright + mono tagline |
## User Interaction Flows
- **Theme toggle click**: `ThemeToggle` → calls `toggleTheme()` from `ThemeProvider` context → context updates `theme` state → writes new value to `localStorage` (BR-2) → root element's theme class is updated → all themed elements re-render with new token values → transition is instant if `prefers-reduced-motion: reduce` (BR-5), otherwise a short color transition plays.
- **In-page anchor navigation**: Clicking a `Nav` link or hero CTA scrolls smoothly to the target section (`scroll-behavior: smooth`), respecting `prefers-reduced-motion` (falls back to instant jump).
- **Hover / focus-visible states**: Preserved 1-to-1 from the reference design on nav links, buttons, and cards (border/color changes on `:hover`/`:focus-visible`).
- **Mailto CTA**: Clicking the contact CTA or the inline mail link opens the visitor's mail client via a `mailto:` link with a prefilled subject.
## Form Validation Rules
None — this iteration has no forms; the only interactive control is the theme toggle and standard anchor/mailto links.
## API Integration Points (Forward-Looking)
- `usePackagesQuery` (TanStack Query hook, placeholder): `queryFn` currently resolves the static `PackageCard[]` data from the content module wrapped in `Promise.resolve(...)`, consumed via `useQuery` in `PackagesSection`. This keeps the component's data-access pattern identical to what it will be once a real backend endpoint exists — only the `queryFn` implementation will need to change in a future iteration (per FR-5).
- No other components call `useQuery` in this iteration; `Nav`, `Hero`, `ProcessSection`, `AboutSection`, and `ContactSection` read directly from the static content module for now.