# PRP-012 - Light / Dark Mode Options

- **Status:** Done
- **Plan:** Lite
- **Depends on:** PRP-007 Dashboard, PRP-008 i18n/RTL, latest UI shell/accounting/tracking work
- **Estimated effort:** 0.5-1 day
- **Pro KB ref:** N/A - product polish for Lite dashboard usability

> **Audience:** junior developer. This PRP adds user-selectable light/dark appearance to the Shiprex Lite
> dashboard and public pages. Keep the feature simple, predictable, and fully client-side.

---

## 1. Goal / Why
Shiprex Lite is used for operations work across the day. Some users prefer a bright dashboard for office
use, while others prefer dark mode for night operations. Add a global theme switcher so the product feels
more mature and comfortable without changing the backend or the Lite/Pro feature model.

## 2. Scope
**In:**
- Add Light, Dark, and System theme options.
- Persist the selected theme in `localStorage`.
- Apply the theme to the whole React app, including:
  - authenticated dashboard shell/sidebar,
  - Admin,
  - Dashboard,
  - Orders,
  - Accounting,
  - Zones,
  - Settings,
  - Order detail / shipping policy preview,
  - public `/tracking`,
  - auth pages: login/register/forgot/reset/activate.
- Preserve RTL support.
- Preserve print styles for shipping policy.

**Out:**
- No database changes.
- No API endpoints.
- No per-user server-side preference.
- No brand customization / white-label themes.
- No Pro gating.

## 3. Functional Requirements
- **FR-1:** The user can choose `Light`, `Dark`, or `System` from the UI.
- **FR-2:** The choice persists across refreshes using `localStorage`.
- **FR-3:** `System` follows `prefers-color-scheme: dark`.
- **FR-4:** Theme changes apply immediately without reload.
- **FR-5:** The logout button remains visible in the sidebar after the theme switcher is added.
- **FR-6:** All text remains readable in both themes.
- **FR-7:** Tables, cards, forms, badges, charts, locked Pro cards, and public tracking surfaces are themed.
- **FR-8:** Printed shipping policy remains black-on-white and is not affected by dark mode.

## 4. Data Model
No database changes.

Client-side storage only:

```ts
localStorage["shiprex_theme"] = "light" | "dark" | "system"
```

## 5. API Surface
No new API endpoints.

Do not touch Postman for this PRP.

## 6. Frontend Implementation Plan

### Step 1 - Add a Theme Provider
Create **`client/src/theme.tsx`**.

Responsibilities:
- Store `themePreference`: `'light' | 'dark' | 'system'`.
- Compute the resolved theme: `'light' | 'dark'`.
- Apply the resolved theme to `<html data-theme="light|dark">`.
- Persist the preference in `localStorage`.
- Listen for `prefers-color-scheme` changes when preference is `system`.

Suggested shape:

```tsx
import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react';

type ThemePreference = 'light' | 'dark' | 'system';
type ResolvedTheme = 'light' | 'dark';

type ThemeCtx = {
  preference: ThemePreference;
  resolvedTheme: ResolvedTheme;
  setPreference: (next: ThemePreference) => void;
};

const STORAGE_KEY = 'shiprex_theme';
const Ctx = createContext<ThemeCtx>(null as any);

function getSystemTheme(): ResolvedTheme {
  return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [preference, setPreferenceState] = useState<ThemePreference>(() => {
    const stored = localStorage.getItem(STORAGE_KEY);
    return stored === 'light' || stored === 'dark' || stored === 'system' ? stored : 'system';
  });
  const [systemTheme, setSystemTheme] = useState<ResolvedTheme>(() => getSystemTheme());

  useEffect(() => {
    const mq = window.matchMedia?.('(prefers-color-scheme: dark)');
    if (!mq) return;
    const onChange = () => setSystemTheme(getSystemTheme());
    mq.addEventListener('change', onChange);
    return () => mq.removeEventListener('change', onChange);
  }, []);

  const resolvedTheme = preference === 'system' ? systemTheme : preference;

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', resolvedTheme);
  }, [resolvedTheme]);

  function setPreference(next: ThemePreference) {
    localStorage.setItem(STORAGE_KEY, next);
    setPreferenceState(next);
  }

  const value = useMemo(() => ({ preference, resolvedTheme, setPreference }), [preference, resolvedTheme]);
  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}

export const useTheme = () => useContext(Ctx);
```

### Step 2 - Wrap the App
Edit **`client/src/App.tsx`**:

```tsx
import { ThemeProvider } from './theme';
```

Wrap the existing app:

```tsx
<AuthProvider>
  <ThemeProvider>
    <Routes>...</Routes>
  </ThemeProvider>
</AuthProvider>
```

Keep `AuthProvider` as the outer provider unless you find a concrete reason to change it.

### Step 3 - Add a Theme Switcher Component
Create **`client/src/components/ThemeSwitcher.tsx`**.

UI:
- Use a compact segmented control or native `<select>`.
- Include icons if possible using the existing local `Icon` component.
- Options:
  - Light
  - Dark
  - System

Recommended for junior dev simplicity:

```tsx
import Icon from './Icon';
import { useTheme } from '../theme';

export default function ThemeSwitcher() {
  const { preference, setPreference } = useTheme();
  return (
    <label className="theme-switcher">
      <Icon name="settings" size={15} />
      <select value={preference} onChange={(e) => setPreference(e.target.value as any)} aria-label="Theme">
        <option value="system">System</option>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
    </label>
  );
}
```

### Step 4 - Put ThemeSwitcher in the UI
Edit **`client/src/App.tsx`**:
- In the sidebar, place `<ThemeSwitcher />` near `<LanguageSwitcher />`.
- Keep logout inside `.sidebar-footer`.
- Do not make the sidebar taller than the viewport. It already scrolls; do not remove that behavior.

Also add `<ThemeSwitcher />` to public/auth screens that do not use the sidebar:
- `client/src/pages/Track.tsx` public header.
- `client/src/pages/Login.tsx`
- `client/src/pages/Register.tsx`
- `client/src/pages/ForgotPassword.tsx`
- `client/src/pages/ResetPassword.tsx`
- `client/src/pages/Activate.tsx`

If time is tight, prioritize:
1. Sidebar pages,
2. `/tracking`,
3. auth pages.

### Step 5 - Convert CSS Variables to Theme-Aware Tokens
Edit **`client/src/styles.css`**.

Current CSS already uses variables like:
- `--bg`
- `--panel`
- `--panel-soft`
- `--muted`
- `--text`
- `--heading`
- `--accent`
- `--border`
- `--shadow`
- `--sidebar-bg`
- `--sidebar-line`

Keep those variable names. Add a dark theme override:

```css
:root,
html[data-theme="light"] {
  /* existing light values */
}

html[data-theme="dark"] {
  --bg: #0f172a;
  --bg-soft: #111827;
  --sidebar-bg: #111827;
  --sidebar-line: #263244;
  --panel: #162033;
  --panel-soft: #111827;
  --muted: #9ca3af;
  --text: #e5e7eb;
  --heading: #f8fafc;
  --accent: #60a5fa;
  --accent-strong: #93c5fd;
  --accent-soft: #172554;
  --success: #34d399;
  --success-soft: #064e3b;
  --warning: #fbbf24;
  --warning-soft: #451a03;
  --border: #334155;
  --border-strong: #475569;
  --danger: #f87171;
  --danger-soft: #450a0a;
  --shadow: 0 18px 40px rgba(0, 0, 0, 0.24);
}
```

Then audit any hard-coded light colors in `styles.css`:
- `#fff`
- `#f8fbff`
- `#e2e8f0`
- `#111827`
- `#0f172a`

Replace them with variables where they affect app UI. Leave print-only black/white as-is.

Important:
- Keep `.shipping-policy` black-on-white because labels must print clearly.
- Keep `@media print` untouched except if required to preserve label output.

### Step 6 - Theme the Chart and Form Pieces
Make sure these classes look good in both themes:
- `.bar-track`
- `.bar-fill`
- `.data-item`
- `.feature-tile`
- `.empty-state`
- `.upgrade`
- `.badge`
- `.cap-control`
- `.tracking-shell`
- `.public-header`

Do not introduce a new CSS framework.

### Step 7 - RTL Check
Arabic RTL currently relies on:

```css
html[dir="rtl"] ...
```

Do not replace this. Theme must coexist with:

```html
<html dir="rtl" data-theme="dark">
```

## 7. Pro-Teaser / Gating
No Pro gating changes.

Theme switching is a Lite feature and should not be locked behind Pro.

## 8. Acceptance Criteria
- [x] Sidebar contains a visible theme switcher.
- [x] Public `/tracking` contains a visible theme switcher.
- [x] User can select Light, Dark, and System.
- [x] Selected preference persists after refresh.
- [x] `html[data-theme]` changes correctly.
- [x] Dashboard cards, sidebar, tables, forms, badges, charts, and Pro teaser cards are readable in dark mode.
- [x] Logout remains visible in the sidebar.
- [x] Arabic RTL still works.
- [x] Shipping policy print remains black-on-white.
- [x] Build passes.

## 9. Test Plan

### Static checks
Run:

```bash
npm.cmd run build:client
git diff --check
```

### Manual browser checks
Use `http://localhost:3000`.

1. Login as company manager.
2. On Dashboard:
   - switch Light -> Dark -> System,
   - refresh page,
   - confirm the preference persists.
3. Visit:
   - `/orders`
   - `/accounting`
   - `/zones`
   - `/settings`
   - an order detail page
   Confirm tables/forms/cards/charts are readable.
4. Visit `/tracking` while logged out:
   - switch theme,
   - refresh,
   - confirm public page remains themed.
5. Switch language to Arabic:
   - confirm RTL + dark theme both apply.
6. Open order detail and use browser print preview:
   - shipping policy label remains white background with black text/barcode.

### Suggested DOM checks
In browser dev tools:

```js
document.documentElement.getAttribute('data-theme')
localStorage.getItem('shiprex_theme')
```

Expected:
- `data-theme` is `light` or `dark`.
- `localStorage` is `light`, `dark`, or `system`.

## 10. Git Commits

**Commit 1 - theme provider and switcher**

```bash
git add client/src/theme.tsx client/src/components/ThemeSwitcher.tsx client/src/App.tsx
git commit -m "feat(web): PRP-012 add theme provider and switcher"
```

**Commit 2 - dark theme CSS**

```bash
git add client/src/styles.css
git commit -m "feat(web): PRP-012 add dark mode design tokens"
```

**Commit 3 - public/auth theme switcher coverage**

```bash
git add client/src/pages
git commit -m "feat(web): PRP-012 expose theme switcher on public and auth pages"
```

**Commit 4 - docs**

```bash
git add PRPs/PRP-012-theme-light-dark-mode.md PRPs/README.md
git commit -m "docs(prp): add PRP-012 light and dark mode"
```

## 11. Notes / Open Questions
- Do not store theme preference on the backend in this PRP.
- Keep theme labels in English for this PRP unless the implementer has time to add i18n keys.
- If adding i18n keys, add:
  - `settings.theme`
  - `settings.theme_light`
  - `settings.theme_dark`
  - `settings.theme_system`
- If the team later wants per-user theme preferences, that should be a separate PRP with a user settings API.

