From f1cba6cf746ddeb8e7a0dad501cb3fd15d6b6aab Mon Sep 17 00:00:00 2001 From: hamid Date: Fri, 17 Jul 2026 14:00:22 +0330 Subject: [PATCH] ui phase 0 --- .claude/skills/frontend-designer/SKILL.md | 100 +++++++-- client/CLAUDE.md | 73 ++++-- client/public/img/favicon/16x16.png | Bin 479 -> 426 bytes client/public/img/favicon/180x180.png | Bin 9127 -> 4346 bytes client/public/img/favicon/192x192.png | Bin 9147 -> 4781 bytes client/public/img/favicon/32x32.png | Bin 923 -> 764 bytes client/public/img/favicon/48x48.png | Bin 0 -> 1062 bytes client/public/img/favicon/512x512.png | Bin 32040 -> 17253 bytes client/public/img/logo.svg | 10 +- client/public/site.webmanifest | 23 +- .../(customer)/addresses/page.tsx | 2 +- .../(customer)/bookings/[id]/cancel/page.tsx | 8 +- .../(customer)/bookings/[id]/invoice/page.tsx | 14 +- .../(customer)/bookings/[id]/page.tsx | 4 +- .../bookings/[id]/refund_status/page.tsx | 2 +- .../(customer)/bookings/[id]/review/page.tsx | 7 +- .../checkout/bnpl/EligibilityStep.tsx | 9 +- .../bookings/checkout/bnpl/MethodStep.tsx | 1 - .../bookings/checkout/bnpl/PlanStep.tsx | 5 +- .../bookings/checkout/bnpl/ScheduleStep.tsx | 6 +- .../bookings/checkout/bnpl/gateway/page.tsx | 4 +- .../bookings/checkout/bnpl/page.tsx | 2 +- .../bookings/checkout/bnpl/return/page.tsx | 7 +- .../bookings/checkout/confirmation/page.tsx | 4 +- .../(customer)/bookings/checkout/page.tsx | 5 +- .../bookings/checkout/return/page.tsx | 7 +- .../(customer)/bookings/page.tsx | 1 - .../(customer)/bookings/request/[id]/page.tsx | 6 +- .../(customer)/bookings/request/page.tsx | 8 +- .../(customer)/onboarding/page.tsx | 1 - .../(private-routes)/(customer)/page.tsx | 4 +- .../(customer)/patients/[id]/record/page.tsx | 16 +- .../(customer)/patients/page.tsx | 2 +- .../(customer)/profile/page.tsx | 4 +- .../search/nurse/[nurseId]/page.tsx | 7 +- .../(customer)/search/page.tsx | 2 +- .../(customer)/search/results/page.tsx | 6 +- .../(customer)/wallet/WalletInstallments.tsx | 4 +- .../(private-routes)/admin/audit/page.tsx | 4 +- .../(private-routes)/admin/config/page.tsx | 8 +- .../(private-routes)/admin/holidays/page.tsx | 8 +- .../admin/partners/[id]/page.tsx | 11 +- .../(private-routes)/admin/partners/page.tsx | 6 +- .../admin/payouts/[batchId]/page.tsx | 10 +- .../(private-routes)/admin/payouts/page.tsx | 12 +- .../(private-routes)/admin/reviews/page.tsx | 6 +- .../(private-routes)/admin/roles/page.tsx | 8 +- .../admin/tickets/[id]/page.tsx | 5 +- .../(private-routes)/admin/tickets/page.tsx | 4 +- .../admin/verification/[nurseId]/page.tsx | 11 +- .../admin/verification/page.tsx | 2 +- .../(private-routes)/nurse/bank/page.tsx | 4 +- .../(private-routes)/nurse/coverage/page.tsx | 4 +- .../(private-routes)/nurse/earnings/page.tsx | 6 +- .../nurse/earnings/payouts/[id]/page.tsx | 5 +- .../nurse/earnings/payouts/page.tsx | 8 +- .../(private-routes)/nurse/profile/page.tsx | 6 +- .../nurse/requests/[id]/page.tsx | 16 +- .../(private-routes)/nurse/requests/page.tsx | 4 +- .../nurse/services/MyServicesList.tsx | 2 +- .../nurse/services/PublishGate.tsx | 2 - .../nurse/services/VariantBuilder.tsx | 18 +- .../verification/VerificationChecklist.tsx | 2 +- .../nurse/verification/credentials/page.tsx | 11 +- .../nurse/verification/identity/page.tsx | 3 +- .../nurse/verification/page.tsx | 10 +- .../nurse/verification/review/page.tsx | 4 +- .../visits/[id]/NurseVisitNotesPanel.tsx | 2 +- .../(private-routes)/nurse/visits/page.tsx | 2 +- .../(private-routes)/partner/page.tsx | 4 +- .../partner/settlement/page.tsx | 2 +- client/src/app/[locale]/layout.tsx | 23 +- client/src/app/favicon.ico | Bin 15406 -> 2306 bytes client/src/app/globals.css | 27 ++- .../BankStatusPanel/BankStatusPanel.tsx | 4 +- .../BookingRequestSummaryCard.tsx | 8 +- .../CancellationPolicyDisclosure.tsx | 2 +- .../CountdownTimer/CountdownTimer.tsx | 4 +- .../DocumentUpload/DocumentUpload.tsx | 12 +- .../EarningsBalanceHeader.tsx | 6 +- .../components/EarningsRow/EarningsRow.tsx | 2 - .../components/GenderToggle/GenderToggle.tsx | 2 +- .../components/PatientForm/PatientForm.tsx | 3 +- .../PatientHeader/PatientHeader.tsx | 2 +- .../PayoutHistoryRow/PayoutHistoryRow.tsx | 6 +- .../PriceBreakdown/PriceBreakdown.tsx | 2 +- .../components/PriceDisplay/PriceDisplay.tsx | 4 +- .../RefundEtaBanner/RefundEtaBanner.tsx | 2 +- .../RefundStatusCard/RefundStatusCard.tsx | 4 +- .../RelationSelect/RelationSelect.tsx | 2 +- .../ServicePriceRow/ServicePriceRow.tsx | 2 +- .../src/components/StatusChip/StatusChip.tsx | 2 +- .../components/VariantCard/VariantCard.tsx | 3 +- .../src/components/admin/AdminErrorState.tsx | 2 +- client/src/components/admin/AdminPager.tsx | 4 +- client/src/components/admin/AuditLogRow.tsx | 2 +- client/src/components/admin/ConfigRow.tsx | 4 +- client/src/components/admin/ConfirmDialog.tsx | 3 +- .../src/components/admin/DocumentViewer.tsx | 8 +- .../components/admin/PartnerSettlementRow.tsx | 4 +- client/src/components/admin/RefundPanel.tsx | 6 +- .../src/components/admin/SupportAlertCard.tsx | 4 +- client/src/components/auth/BrandMark.tsx | 5 +- client/src/components/auth/OtpStep.tsx | 1 - client/src/components/auth/PhoneStep.tsx | 1 - client/src/components/auth/SelectRole.tsx | 3 +- .../BookingDetailView/BookingDetailView.tsx | 2 +- .../BookingStatusTimeline.tsx | 2 +- .../booking/SessionCard/SessionCard.tsx | 8 +- .../common/AppButton/AppButton.test.tsx | 17 +- .../components/common/AppButton/AppButton.tsx | 20 +- .../common/AppIcon/AppIcon.test.tsx | 8 +- .../src/components/common/AppIcon/AppIcon.tsx | 28 ++- .../src/components/common/AppIcon/config.ts | 210 +++++++++--------- .../common/AppIcon/icons/LogoLockup.tsx | 30 +++ .../common/AppIcon/icons/LogoMark.tsx | 28 +++ .../common/AppIcon/icons/PencilIcon.tsx | 29 --- .../AppIconButton/AppIconButton.test.tsx | 3 +- .../common/AppImage/AppImage.test.tsx | 55 ----- .../components/common/AppImage/AppImage.tsx | 23 -- .../src/components/common/AppImage/index.tsx | 3 - client/src/components/common/index.tsx | 3 +- client/src/components/config.ts | 15 +- .../src/components/geography/AddressCard.tsx | 2 +- .../src/components/geography/AddressForm.tsx | 4 +- .../messaging/BookingSupportEntry.tsx | 1 - .../messaging/TicketInboxScreen.tsx | 1 - .../messaging/TicketThreadScreen.tsx | 2 +- .../notifications/NotificationCenter.tsx | 1 - client/src/lib/cookies/server.ts | 23 +- client/src/theme/ThemeProvider.tsx | 8 +- client/src/theme/colors.ts | 22 ++ client/src/theme/dark.ts | 11 - client/src/theme/index.ts | 9 +- client/src/theme/light.ts | 11 - client/src/theme/theme.ts | 165 +++++++++++++- client/src/theme/tokens.css | 138 +++++++++++- client/src/theme/typography.ts | 97 ++++++-- .../reports/ui-phase-0-report.md | 197 ++++++++++++++++ 139 files changed, 1242 insertions(+), 667 deletions(-) create mode 100644 client/public/img/favicon/48x48.png create mode 100644 client/src/components/common/AppIcon/icons/LogoLockup.tsx create mode 100644 client/src/components/common/AppIcon/icons/LogoMark.tsx delete mode 100644 client/src/components/common/AppIcon/icons/PencilIcon.tsx delete mode 100644 client/src/components/common/AppImage/AppImage.test.tsx delete mode 100644 client/src/components/common/AppImage/AppImage.tsx delete mode 100644 client/src/components/common/AppImage/index.tsx delete mode 100644 client/src/theme/dark.ts delete mode 100644 client/src/theme/light.ts create mode 100644 dev/shared-working-context/reports/ui-phase-0-report.md diff --git a/.claude/skills/frontend-designer/SKILL.md b/.claude/skills/frontend-designer/SKILL.md index 2c02d3a..2a20b12 100644 --- a/.claude/skills/frontend-designer/SKILL.md +++ b/.claude/skills/frontend-designer/SKILL.md @@ -28,10 +28,24 @@ Balinyaar is a **trust-first home-nursing marketplace in Iran**. The visual tone calm, warm, clinical-but-human — not a cold medical dashboard. Default audience is Persian (RTL); English is secondary. -**Logo mark** (`product/balinyaar.html` seed deck): deep-teal square, lowercase -display glyph in cream, a single terracotta dot. That trio — **teal ground, cream -text, terracotta accent** — is the whole identity. Use terracotta sparingly as the -single accent; teal carries everything else. +**Logo mark** — this skill is the construction source of truth (the original +`product/balinyaar.html` seed deck no longer exists in the repo): a deep-teal +rounded-square ground (`var(--bal-primary)`), a cream lowercase "b" glyph built +from a stem + a ring bowl (`var(--bal-primary-contrast)`), and a single +terracotta dot accent (`var(--bal-secondary)`). That trio — **teal ground, cream +glyph, terracotta accent** — is the whole identity. Use terracotta sparingly as +the single accent; teal carries everything else. Implemented as two SVGs under +`components/common/AppIcon/icons/`: +- `LogoMark.tsx` — a monochrome `currentColor` version of just the glyph (no + ground square), registered as `ICONS.logo`. Use via `` + anywhere an inline, recolorable brand glyph is needed. +- `LogoLockup.tsx` — the full-color mark (ground + glyph + dot, token-driven so + it tracks the color scheme) for `BrandMark` (auth splash). The wordmark next + to it stays real, translated `` — never bake locale text into an SVG. +- The favicon (`src/app/favicon.ico`) and `public/img/favicon/*.png` are + rasterized from the same construction (fixed brand hex, not CSS vars — static + binary assets are the one place a literal hex is correct). Regenerate with a + `sharp`-based script if the mark ever changes; don't hand-edit the PNGs/ICO. | Role | Light | Dark | |------|-------|------| @@ -67,21 +81,54 @@ Colors exist in **two mirrored places** that must stay in sync. Pick the right o - Adding/changing a color means editing `tokens.css` **and** `colors.ts` together (the file headers call out the sync requirement). +**Beyond color** — `tokens.css` also defines non-palette tokens (`colors.ts` never needs +these; they're define-only in CSS): +- **Radius** — `--bal-radius-sm` (4px, controls: buttons/inputs), `--bal-radius-md` + (10px = `theme.shape.borderRadius`, the house default: cards/paper), `--bal-radius-lg` + (16px: dialogs). Reference the token/constant, never invent a new radius. +- **Elevation** — `--bal-shadow-1/2/3`, teal-tinted (black-teal in dark mode) shadow + steps that back `theme.ts`'s `shadows` array — every MUI elevation (Paper, Dialog, + Menu, Popover, AppBar) resolves through these, never MUI's default grey stack. +- **Motion** — `--bal-motion-fast/base/slow` (120/200/300ms) + `--bal-easing-standard`. + Consumed by the phase-12 app-wide motion pass; use them for any transition you add now. +- **Focus** — `--bal-focus-ring`, the 2px ring `MuiCssBaseline`'s global `:focus-visible` + override uses. Don't hand-roll a focus style; it's already uniform everywhere. +- **Rating** — `--bal-rating` / `--bal-rating-empty` (filled/empty star colors) — + `RatingInput` uses these, not `--bal-warning`. +- **Trust** — `--bal-trust` / `--bal-trust-soft`, a distinct identity (not + primary/success) for verified marks — `TrustBadge` and any future verification UI. +- **Money emphasis** — `--bal-money-emphasis`, an AA-contrast-safe color for emphasized + money text. `--bal-secondary` (terracotta) fails AA contrast at small sizes on light + backgrounds — never use it for money text, use this token instead. + --- ## 3. Typography & fonts -- `shape.borderRadius: 10` (set in `src/theme/theme.ts`) — the house corner radius. - Don't override per-component unless deliberate; prefer multiples that read as related. -- Buttons: `textTransform: 'none'`, weight 600 (set globally in `typography.ts`). Never +- `shape.borderRadius: 10` (set in `src/theme/theme.ts`) — the house corner radius + (= `--bal-radius-md`). Don't override per-component unless deliberate; the radius + *scale* is `--bal-radius-sm` (4, controls) / `-md` (10, cards) / `-lg` (16, dialogs). +- **Weight system — never write `fontWeight: 600`.** Mikhak and Space Grotesk both load + only 400/500/700 (no 600 face), so a requested 600 silently renders full Bold. Use + **700** for headings (`h1`–`h6`) and buttons/strong emphasis, **500** for lighter + in-text emphasis (subtitles, row labels, chip text). This is enforced globally in + `typography.ts`; match it in any new `sx` you write. +- Buttons: `textTransform: 'none'`, weight 700 (set globally in `typography.ts`). Never re-uppercase button text. -- Headings (`h1`–`h6`) use the display font; `h6` is weight 600, the rest 700. +- Persian type scale (`TYPOGRAPHY_RTL`): `letterSpacing: 0` on every variant (Persian is + a joined script — tracking breaks glyph connections), body line-height ≥1.7, heading + line-height ~1.4–1.5 (room for ascenders/descenders), and `responsiveFontSizes()` + wraps both themes (`theme.ts`) so heading sizes scale down on small viewports — + don't hand-roll per-breakpoint `fontSize` overrides. - **Fonts are loaded per-locale in `src/app/[locale]/layout.tsx` only** — Mikhak - (`--font-mikhak`) for `fa`, system stack for `en` (Space Grotesk `--font-space-grotesk` - is declared but not yet wired). **Never load a font in a component or page.** + (`--font-mikhak`) for `fa`, **Space Grotesk** (`--font-space-grotesk`, via + `next/font/google`, self-hosted at build time) for `en`. Both `preload: false` with a + conditional `.variable` className so neither ships to the other locale. + **Never load a font in a component or page.** - Use `` for text — it inherits the correct direction-aware family - (`TYPOGRAPHY_RTL` = Mikhak everywhere for full Persian glyph coverage; `TYPOGRAPHY_LTR`). - Import neither directly in components; let the theme apply them. + (`TYPOGRAPHY_RTL` = Mikhak everywhere for full Persian glyph coverage; `TYPOGRAPHY_LTR` + = Space Grotesk headings + system-stack body). Import neither directly in components; + let the theme apply them. --- @@ -141,13 +188,30 @@ Icons are a **name registry**, not free imports. `src/components/common/AppIcon/ maps lowercase names → MUI/SVG components. Render with `` or pass the name to `AppButton`/`AppIconButton` (`icon="search"`). -Currently registered: `default, logo, close, menu, settings, visibilityon, -visibilityoff, daynight, night, day, search, info, home, account, signup, login, -logout, notifications, error`. +**One visual family: MUI `*Rounded`.** Every registered icon is the `Rounded` variant of +`@mui/icons-material` (warmer, softer strokes than the old filled/outlined mix — fits +"clinical-but-human"). When adding an icon, import the `*Rounded` version; don't mix in a +Filled/Outlined/Sharp/TwoTone icon next to it. ~90 names are registered today, spanning +navigation, catalog, verification, booking, payments, admin, and messaging — read +`AppIcon/config.ts` directly for the full list rather than duplicating it here (it drifts +too fast for a skill doc to track reliably); the two structural rules below don't. -**Need a new icon:** import it into `config.ts`, add a **lowercase** key to `ICONS`, then -reference by that name. Custom SVGs go in `AppIcon/icons/`. An unregistered name logs a -warning and falls back to `default` — never pass a raw MUI icon where a name is expected. +**`size` actually resizes now.** `AppIcon` drives size via `style.fontSize` (the basis for +MUI SvgIcon's internal `1em` sizing) instead of `width`/`height` attributes, which MUI's +own CSS used to beat. `` renders 48px — no more +silent 24px flattening. + +**Directional icons mirror automatically.** Icons authored for LTR that must flip under +RTL (`back`, `chevron_start`) are registered in `AppIcon/config.ts`'s `DIRECTIONAL_ICONS` +set. `AppIcon` stamps `data-icon-directional` on those, and one CSS rule +(`app/globals.css`) does `[dir='rtl'] [data-icon-directional] { transform: scaleX(-1); }`. +Adding a new directional icon is a one-line registry addition — never hand-roll a +per-component flip. + +**Need a new icon:** import the `*Rounded` version into `config.ts`, add a **lowercase** +key to `ICONS`, then reference by that name. Custom SVGs (the brand mark) go in +`AppIcon/icons/`. An unregistered name logs a dev-only warning and falls back to +`default` — never pass a raw MUI icon where a name is expected. --- diff --git a/client/CLAUDE.md b/client/CLAUDE.md index f04908b..9ed8951 100644 --- a/client/CLAUDE.md +++ b/client/CLAUDE.md @@ -319,13 +319,12 @@ client/ │ └── auth/ # AuthContext — AuthProvider (server-seeded) + reducer + useAuth ├── theme/ │ ├── ThemeProvider.tsx # MuiThemeProvider wrapper (RTL cache) + ColorSchemeCookieSync - │ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE - │ ├── light.ts / dark.ts # LIGHT_THEME / DARK_THEME ThemeOptions (consumed by theme.ts) + │ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE (incl. success/error/warning/info) │ ├── direction.ts # getDirection(locale) → 'ltr' | 'rtl' - │ ├── theme.ts # APP_THEME_LTR / APP_THEME_RTL (static, created once) - │ ├── tokens.css # CSS custom properties — [data-mui-color-scheme] selectors - │ ├── typography.ts # TYPOGRAPHY_LTR (Space Grotesk) / TYPOGRAPHY_RTL (Mikhak) - │ └── index.ts # Public re-exports (ThemeProvider, getDirection, APP_THEME_*) — note: no ColorSchemeScript is exported/rendered today (doc drift below) + │ ├── theme.ts # APP_THEME_LTR / APP_THEME_RTL (static, created once) — the `components` brand pass + teal-tinted `shadows` array + responsiveFontSizes() + │ ├── tokens.css # CSS custom properties — [data-mui-color-scheme] selectors + the dark @media fallback (no-flash boot, no script — see "Theme System" below) + │ ├── typography.ts # TYPOGRAPHY_LTR (Space Grotesk) / TYPOGRAPHY_RTL (Mikhak) — shared size scale, 500/700 weight system + │ └── index.ts # Public re-exports (ThemeProvider, getDirection, APP_THEME_*) ├── constants/ # App-wide constants (routes.ts w/ actor paths, roles.ts, headers.ts) ├── hooks/ # incl. auth.ts → useIsAuthenticated / useActorRole (role-aware chrome) ├── utils/ # incl. money.ts (IRR/Toman, integer-safe) + date.ts (Shamsi display) + toEnglishDigits @@ -458,15 +457,42 @@ Rules: ## Theme System -### How it works (end-to-end, no-flash) +### How it works (end-to-end, no-flash — CSS only, no boot script) -1. **Request arrives** → `getThemeMode()` reads `'color-scheme'` cookie → returns `{ colorScheme, defaultMode }` -2. **Root layout** sets `data-mui-color-scheme={colorScheme}` on `` server-side -3. **``** in `` runs before any paint: - - Reads the same cookie, sets `data-mui-color-scheme` (handles edge cases where server attr might differ) - - Patches `Storage.prototype` — routes MUI's `localStorage` writes for key `'mode'` to our cookie; reads return `null` so MUI always trusts the `defaultMode` prop -4. **``** mounts — uses the server-derived mode, not localStorage -5. **`ColorSchemeCookieSync`** in ThemeProvider writes the cookie via `useColorScheme().colorScheme` on mount (safety net for first-visit system mode) +The no-flash mechanism is **pure CSS**, matching how every other color decision in this +app is made — no inline `