ui phase 0
@@ -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
|
calm, warm, clinical-but-human — not a cold medical dashboard. Default audience is
|
||||||
Persian (RTL); English is secondary.
|
Persian (RTL); English is secondary.
|
||||||
|
|
||||||
**Logo mark** (`product/balinyaar.html` seed deck): deep-teal square, lowercase
|
**Logo mark** — this skill is the construction source of truth (the original
|
||||||
display glyph in cream, a single terracotta dot. That trio — **teal ground, cream
|
`product/balinyaar.html` seed deck no longer exists in the repo): a deep-teal
|
||||||
text, terracotta accent** — is the whole identity. Use terracotta sparingly as the
|
rounded-square ground (`var(--bal-primary)`), a cream lowercase "b" glyph built
|
||||||
single accent; teal carries everything else.
|
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 `<AppIcon icon="logo">`
|
||||||
|
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 `<Typography>` — 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 |
|
| 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
|
- Adding/changing a color means editing `tokens.css` **and** `colors.ts` together (the
|
||||||
file headers call out the sync requirement).
|
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
|
## 3. Typography & fonts
|
||||||
|
|
||||||
- `shape.borderRadius: 10` (set in `src/theme/theme.ts`) — the house corner radius.
|
- `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.
|
(= `--bal-radius-md`). Don't override per-component unless deliberate; the radius
|
||||||
- Buttons: `textTransform: 'none'`, weight 600 (set globally in `typography.ts`). Never
|
*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.
|
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
|
- **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`
|
(`--font-mikhak`) for `fa`, **Space Grotesk** (`--font-space-grotesk`, via
|
||||||
is declared but not yet wired). **Never load a font in a component or page.**
|
`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 `<Typography variant=…>` for text — it inherits the correct direction-aware family
|
- Use `<Typography variant=…>` for text — it inherits the correct direction-aware family
|
||||||
(`TYPOGRAPHY_RTL` = Mikhak everywhere for full Persian glyph coverage; `TYPOGRAPHY_LTR`).
|
(`TYPOGRAPHY_RTL` = Mikhak everywhere for full Persian glyph coverage; `TYPOGRAPHY_LTR`
|
||||||
Import neither directly in components; let the theme apply them.
|
= 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 `<AppIcon icon="home" />` or pass
|
maps lowercase names → MUI/SVG components. Render with `<AppIcon icon="home" />` or pass
|
||||||
the name to `AppButton`/`AppIconButton` (`icon="search"`).
|
the name to `AppButton`/`AppIconButton` (`icon="search"`).
|
||||||
|
|
||||||
Currently registered: `default, logo, close, menu, settings, visibilityon,
|
**One visual family: MUI `*Rounded`.** Every registered icon is the `Rounded` variant of
|
||||||
visibilityoff, daynight, night, day, search, info, home, account, signup, login,
|
`@mui/icons-material` (warmer, softer strokes than the old filled/outlined mix — fits
|
||||||
logout, notifications, error`.
|
"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
|
**`size` actually resizes now.** `AppIcon` drives size via `style.fontSize` (the basis for
|
||||||
reference by that name. Custom SVGs go in `AppIcon/icons/`. An unregistered name logs a
|
MUI SvgIcon's internal `1em` sizing) instead of `width`/`height` attributes, which MUI's
|
||||||
warning and falls back to `default` — never pass a raw MUI icon where a name is expected.
|
own CSS used to beat. `<AppIcon icon="verified" size={48} />` 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -319,13 +319,12 @@ client/
|
|||||||
│ └── auth/ # AuthContext — AuthProvider (server-seeded) + reducer + useAuth
|
│ └── auth/ # AuthContext — AuthProvider (server-seeded) + reducer + useAuth
|
||||||
├── theme/
|
├── theme/
|
||||||
│ ├── ThemeProvider.tsx # MuiThemeProvider wrapper (RTL cache) + ColorSchemeCookieSync
|
│ ├── ThemeProvider.tsx # MuiThemeProvider wrapper (RTL cache) + ColorSchemeCookieSync
|
||||||
│ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE
|
│ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE (incl. success/error/warning/info)
|
||||||
│ ├── light.ts / dark.ts # LIGHT_THEME / DARK_THEME ThemeOptions (consumed by theme.ts)
|
|
||||||
│ ├── direction.ts # getDirection(locale) → 'ltr' | 'rtl'
|
│ ├── direction.ts # getDirection(locale) → 'ltr' | 'rtl'
|
||||||
│ ├── theme.ts # APP_THEME_LTR / APP_THEME_RTL (static, created once)
|
│ ├── 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
|
│ ├── 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)
|
│ ├── 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_*) — note: no ColorSchemeScript is exported/rendered today (doc drift below)
|
│ └── index.ts # Public re-exports (ThemeProvider, getDirection, APP_THEME_*)
|
||||||
├── constants/ # App-wide constants (routes.ts w/ actor paths, roles.ts, headers.ts)
|
├── constants/ # App-wide constants (routes.ts w/ actor paths, roles.ts, headers.ts)
|
||||||
├── hooks/ # incl. auth.ts → useIsAuthenticated / useActorRole (role-aware chrome)
|
├── hooks/ # incl. auth.ts → useIsAuthenticated / useActorRole (role-aware chrome)
|
||||||
├── utils/ # incl. money.ts (IRR/Toman, integer-safe) + date.ts (Shamsi display) + toEnglishDigits
|
├── utils/ # incl. money.ts (IRR/Toman, integer-safe) + date.ts (Shamsi display) + toEnglishDigits
|
||||||
@@ -458,15 +457,42 @@ Rules:
|
|||||||
|
|
||||||
## Theme System
|
## 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 }`
|
The no-flash mechanism is **pure CSS**, matching how every other color decision in this
|
||||||
2. **Root layout** sets `data-mui-color-scheme={colorScheme}` on `<html>` server-side
|
app is made — no inline `<script>`, no `Storage.prototype` patching. Two visitor cases:
|
||||||
3. **`<ColorSchemeScript />`** in `<head>` runs before any paint:
|
|
||||||
- Reads the same cookie, sets `data-mui-color-scheme` (handles edge cases where server attr might differ)
|
**Returning visitor (cookie present):**
|
||||||
- Patches `Storage.prototype` — routes MUI's `localStorage` writes for key `'mode'` to our cookie; reads return `null` so MUI always trusts the `defaultMode` prop
|
1. `getThemeMode()` (`lib/cookies/server.ts`) reads the `'color-scheme'` cookie → returns
|
||||||
4. **`<MuiThemeProvider defaultMode={defaultMode}>`** mounts — uses the server-derived mode, not localStorage
|
`{ colorScheme: 'light'|'dark', defaultMode: colorScheme }`.
|
||||||
5. **`ColorSchemeCookieSync`** in ThemeProvider writes the cookie via `useColorScheme().colorScheme` on mount (safety net for first-visit system mode)
|
2. Root layout sets `data-mui-color-scheme={colorScheme}` on `<html>` server-side.
|
||||||
|
3. `tokens.css`'s explicit `[data-mui-color-scheme='light'|'dark']` blocks match
|
||||||
|
immediately — correct on the very first paint, before any JS runs.
|
||||||
|
|
||||||
|
**First-ever visitor (no cookie yet):**
|
||||||
|
1. `getThemeMode()` returns `{ colorScheme: undefined, defaultMode: 'system' }`.
|
||||||
|
2. Root layout renders `<html>` **without** the `data-mui-color-scheme` attribute at all
|
||||||
|
(`data-mui-color-scheme={undefined}` — React omits it).
|
||||||
|
3. `tokens.css` has a `@media (prefers-color-scheme: dark)` block scoped to
|
||||||
|
`:root:not([data-mui-color-scheme])` — it only applies while the attribute is absent,
|
||||||
|
and paints the OS-preferred scheme immediately, with zero JS.
|
||||||
|
4. Once React hydrates, `<MuiThemeProvider defaultMode="system">` resolves the *same*
|
||||||
|
media query and stamps the attribute itself. The CSS values already match what was
|
||||||
|
painted, so there is nothing to visibly flip.
|
||||||
|
5. `ColorSchemeCookieSync` in `ThemeProvider.tsx` writes the cookie via
|
||||||
|
`useColorScheme().colorScheme` on mount, so the next visit is a "returning visitor".
|
||||||
|
|
||||||
|
**Trade-off, by design:** this covers the dominant visual surface — every `--bal-*` token
|
||||||
|
(page/paper background, text, dividers, all brand colors) — because that's what
|
||||||
|
`tokens.css`'s media-query fallback drives. MUI's own generated `--mui-palette-*`
|
||||||
|
variables (consumed by a bare `color="primary"` fill, e.g. a contained Button, or the
|
||||||
|
default `MuiTabs` indicator) do **not** get the same free fallback — MUI's
|
||||||
|
`colorSchemeSelector` supports either attribute-based *or* `'media'`-based generation,
|
||||||
|
not both at once — so on a cookie-less first visit with OS dark on, a raw MUI-primary
|
||||||
|
fill can very briefly show the light value until hydration (self-corrects same frame;
|
||||||
|
`disableTransitionOnChange` means it snaps, never animates). Prefer sourcing colors from
|
||||||
|
`var(--bal-*)` over `theme.vars.palette.*` in new `styleOverrides` — most of `theme.ts`'s
|
||||||
|
`components` block already does — to keep this gap as small as possible.
|
||||||
|
|
||||||
### Critical MUI v9 rules
|
### Critical MUI v9 rules
|
||||||
|
|
||||||
@@ -484,7 +510,10 @@ The shorthand `'data'` in MUI v9 generates `[data-%s]` → `data-dark=""` / `dat
|
|||||||
In MUI v9's `localStorageManager`, the check is `if (!storageWindow && typeof window !== 'undefined')` — `null` is falsy, so it silently overrides to `window`. This prop is a no-op in browsers. The `Storage.prototype` patch in `ColorSchemeScript` is the correct intercept.
|
In MUI v9's `localStorageManager`, the check is `if (!storageWindow && typeof window !== 'undefined')` — `null` is falsy, so it silently overrides to `window`. This prop is a no-op in browsers. The `Storage.prototype` patch in `ColorSchemeScript` is the correct intercept.
|
||||||
|
|
||||||
**Never use MUI's `InitColorSchemeScript`:**
|
**Never use MUI's `InitColorSchemeScript`:**
|
||||||
It reads from localStorage which diverges from our cookie (especially in 'system' mode). Use `ColorSchemeScript` from `@/theme` instead.
|
It reads from localStorage, which diverges from our cookie (especially in 'system' mode),
|
||||||
|
and it's a script — this app's no-flash boot is CSS-only (see "How it works" above). Don't
|
||||||
|
add any pre-paint script for color scheme; extend the `tokens.css` media-query fallback
|
||||||
|
instead if a new token needs the same first-visit treatment.
|
||||||
|
|
||||||
**MUI v9 localStorage key defaults (different from v5/v6):**
|
**MUI v9 localStorage key defaults (different from v5/v6):**
|
||||||
- Mode key: `'mode'` (was `'mui-mode'`)
|
- Mode key: `'mode'` (was `'mui-mode'`)
|
||||||
@@ -536,18 +565,22 @@ Fonts are loaded **per locale** — the Persian face is never shipped to English
|
|||||||
| Locale | Font | CSS variable | Source | Loaded when |
|
| Locale | Font | CSS variable | Source | Loaded when |
|
||||||
|--------|------|--------------|--------|-------------|
|
|--------|------|--------------|--------|-------------|
|
||||||
| `fa` (RTL) | **Mikhak** | `--font-mikhak` | `next/font/local` — woff2 files in `src/app/fonts/` | only on `fa` routes |
|
| `fa` (RTL) | **Mikhak** | `--font-mikhak` | `next/font/local` — woff2 files in `src/app/fonts/` | only on `fa` routes |
|
||||||
| `en` (LTR) | **Space Grotesk** | `--font-space-grotesk` | (not currently wired — falls back to the system stack) | — |
|
| `en` (LTR) | **Space Grotesk** | `--font-space-grotesk` | `next/font/google` — self-hosted at build time, `preload: false` | only on `en` routes |
|
||||||
|
|
||||||
**Typography exports:**
|
**Typography exports:**
|
||||||
- `TYPOGRAPHY_LTR` — Space Grotesk headings, system font body (used by `APP_THEME_LTR`)
|
- `TYPOGRAPHY_LTR` — Space Grotesk headings, system font body (used by `APP_THEME_LTR`)
|
||||||
- `TYPOGRAPHY_RTL` — Mikhak for all text including body (used by `APP_THEME_RTL`, ensures full Persian glyph coverage)
|
- `TYPOGRAPHY_RTL` — Mikhak for all text including body (used by `APP_THEME_RTL`, ensures full Persian glyph coverage)
|
||||||
- `TYPOGRAPHY` — alias for `TYPOGRAPHY_LTR` (deprecated, prefer the explicit exports)
|
- Both share one size/line-height scale (`SIZE_SCALE` in `typography.ts`), wrapped in
|
||||||
|
`responsiveFontSizes()` (`theme.ts`) for per-breakpoint heading scaling. Weight system:
|
||||||
|
**700** for headings + buttons, **500** for in-text emphasis (subtitles, labels), **400**
|
||||||
|
body — never `600`, neither font loads that weight (see `typography.ts`'s header comment).
|
||||||
|
- There is no `TYPOGRAPHY` alias anymore — import `TYPOGRAPHY_LTR`/`TYPOGRAPHY_RTL` explicitly.
|
||||||
|
|
||||||
**Rules:**
|
**Rules:**
|
||||||
- Mikhak is declared with `preload: false`, and its `.variable` class is attached to `<html>` **only when `locale === 'fa'`**. Both are required: a `next/font` loader called in the root layout would otherwise preload on every route (including `/en`), and `preload: false` ensures the woff2 only downloads when Persian text actually renders.
|
- Both fonts are declared with `preload: false`, and each `.variable` class is attached to `<html>` **only for its own locale** (Mikhak on `fa`, Space Grotesk on `en`) — never both, never neither. A `next/font` loader called in the root layout would otherwise preload on every route, and `preload: false` ensures the font file only downloads when its locale actually renders.
|
||||||
- Font files live in `src/app/fonts/` (not `public/`). next/font/local resolves paths relative to the calling file (`src/app/[locale]/layout.tsx`) at build time.
|
- Mikhak's woff2 files live in `src/app/fonts/` (not `public/`) — next/font/local resolves paths relative to the calling file (`src/app/[locale]/layout.tsx`) at build time. Space Grotesk needs no local files — next/font/google fetches + self-hosts it at build time.
|
||||||
- Never load fonts inside components — all font loading lives in `src/app/[locale]/layout.tsx`.
|
- Never load fonts inside components — all font loading lives in `src/app/[locale]/layout.tsx`.
|
||||||
- To add a new font, add woff2 files to `src/app/fonts/`, declare via `localFont`/`localFont`-equivalent in `src/app/[locale]/layout.tsx`, attach its `.variable` class conditionally on the matching locale, and update `BRAND_FONT_VARIABLE_*` constants in `typography.ts`.
|
- To add a new local font, add woff2 files to `src/app/fonts/`, declare via `localFont` in `src/app/[locale]/layout.tsx`, attach its `.variable` class conditionally on the matching locale, and update the `BRAND_FONT_VARIABLE_*` constants in `typography.ts`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 479 B After Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 923 B After Width: | Height: | Size: 764 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,2 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z"/><path fill="#EA596E" d="M13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4.167 0L1.141 9.475c-1.15-1.151-1.15-3.016 0-4.167l4.167-4.167c1.15-1.151 3.016-1.151 4.167 0l4.168 4.167z"/><path fill="#FFCC4D" d="M31.353 23.018l-4.17 4.17-4.163 4.165L7.392 15.726l8.335-8.334 15.626 15.626z"/><path fill="#292F33" d="M32.078 34.763s2.709 1.489 3.441.757c.732-.732-.765-3.435-.765-3.435s-2.566.048-2.676 2.678z"/><path fill="#CCD6DD" d="M2.183 10.517l8.335-8.335 5.208 5.209-8.334 8.335z"/><path fill="#99AAB5" d="M3.225 11.558l8.334-8.334 1.042 1.042L4.267 12.6zm2.083 2.086l8.335-8.335 1.042 1.042-8.335 8.334z"/></svg>
|
<rect width="40" height="40" rx="10" fill="#1d4a40" />
|
||||||
|
<path fill="#f3efe9" fill-rule="evenodd" clip-rule="evenodd"
|
||||||
|
d="M21,17 A7.5,7.5 0 1,0 21,32 A7.5,7.5 0 1,0 21,17 Z
|
||||||
|
M21,20.8 A3.7,3.7 0 1,0 21,28.2 A3.7,3.7 0 1,0 21,20.8 Z" />
|
||||||
|
<rect x="12.9" y="9" width="4.8" height="22" rx="2.4" fill="#f3efe9" />
|
||||||
|
<circle cx="28.5" cy="10.5" r="2.6" fill="#d98c6a" />
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 444 B |
@@ -1,22 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "Balinyaar",
|
"name": "Balinyaar",
|
||||||
"short_name": "Balinyaar",
|
"short_name": "Balinyaar",
|
||||||
"description": "Balinyaar web application",
|
"description": "Balinyaar — trust-first home nursing marketplace",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#1d4a40",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#faf9f5",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{ "src": "favicon.ico?v=2", "sizes": "48x48 32x32 16x16", "type": "image/x-icon" },
|
||||||
"src": "favicon.ico?v=1.0",
|
{ "src": "img/favicon/16x16.png?v=2", "sizes": "16x16", "type": "image/png" },
|
||||||
"sizes": "48x48 32x32 16x16",
|
{ "src": "img/favicon/32x32.png?v=2", "sizes": "32x32", "type": "image/png" },
|
||||||
"type": "image/x-icon"
|
{ "src": "img/favicon/48x48.png?v=2", "sizes": "48x48", "type": "image/png" },
|
||||||
},
|
{ "src": "img/favicon/180x180.png?v=2", "sizes": "180x180", "type": "image/png" },
|
||||||
{ "src": "img/favicon/16x16.png?v=1.0", "sizes": "16x16", "type": "image/png" },
|
{ "src": "img/favicon/192x192.png?v=2", "sizes": "192x192", "type": "image/png" },
|
||||||
{ "src": "img/favicon/32x32.png?v=1.0", "sizes": "32x32", "type": "image/png" },
|
{ "src": "img/favicon/512x512.png?v=2", "sizes": "512x512", "type": "image/png" }
|
||||||
{ "src": "img/favicon/180x180.png?v=1.0", "sizes": "180x180", "type": "image/png" },
|
|
||||||
{ "src": "img/favicon/192x192.png?v=1.0", "sizes": "192x192", "type": "image/png" },
|
|
||||||
{ "src": "img/favicon/512x512.png?v=1.0", "sizes": "512x512", "type": "image/png" }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function AddressesPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{!isEmpty ? (
|
{!isEmpty ? (
|
||||||
<AppButton color="primary" variant="contained" startIcon="add" onClick={openAdd} sx={{ m: 0, flexShrink: 0 }}>
|
<AppButton color="primary" variant="contained" startIcon="add" onClick={openAdd} sx={{ flexShrink: 0 }}>
|
||||||
{t('add')}
|
{t('add')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -93,11 +93,10 @@ export default function CancelBookingPage() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => router.push(`/${locale}${bookingRefundStatusPath(bookingId)}`)}
|
onClick={() => router.push(`/${locale}${bookingRefundStatusPath(bookingId)}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('view_refund_status')}
|
{t('view_refund_status')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="inherit" onClick={() => router.push(bookingHref)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={() => router.push(bookingHref)}>
|
||||||
{t('back_to_booking')}
|
{t('back_to_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -157,7 +156,7 @@ export default function CancelBookingPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack direction="row" sx={{ gap: 1, justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
<Stack direction="row" sx={{ gap: 1, justifyContent: 'space-between', flexWrap: 'wrap' }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={() => router.push(bookingHref)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={() => router.push(bookingHref)}>
|
||||||
{t('back_to_booking')}
|
{t('back_to_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
@@ -165,7 +164,6 @@ export default function CancelBookingPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
disabled={!acknowledged}
|
disabled={!acknowledged}
|
||||||
onClick={() => setStep(1)}
|
onClick={() => setStep(1)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('continue_cta')}
|
{t('continue_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -192,7 +190,6 @@ export default function CancelBookingPage() {
|
|||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={() => setStep(0)}
|
onClick={() => setStep(0)}
|
||||||
disabled={cancel.isPending}
|
disabled={cancel.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{tc('back')}
|
{tc('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -201,7 +198,6 @@ export default function CancelBookingPage() {
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={cancel.isPending}
|
disabled={cancel.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{cancel.isPending ? t('submitting') : t('confirm_cta')}
|
{cancel.isPending ? t('submitting') : t('confirm_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default function BookingInvoicePage() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('invalid_link_body')}
|
{t('invalid_link_body')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="contained" onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}`)} sx={{ m: 0 }}>
|
<AppButton variant="contained" onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}`)}>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -82,11 +82,11 @@ export default function BookingInvoicePage() {
|
|||||||
{notIssued ? t('invoice_not_issued_body') : t('error_body')}
|
{notIssued ? t('invoice_not_issued_body') : t('error_body')}
|
||||||
</Typography>
|
</Typography>
|
||||||
{notIssued ? (
|
{notIssued ? (
|
||||||
<AppButton variant="contained" onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${bookingId}`)} sx={{ m: 0 }}>
|
<AppButton variant="contained" onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${bookingId}`)}>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<AppButton variant="contained" onClick={() => refetch()} sx={{ m: 0 }}>
|
<AppButton variant="contained" onClick={() => refetch()}>
|
||||||
{tc('retry')}
|
{tc('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
)}
|
)}
|
||||||
@@ -185,15 +185,15 @@ export default function BookingInvoicePage() {
|
|||||||
|
|
||||||
<Stack sx={{ gap: 1 }}>
|
<Stack sx={{ gap: 1 }}>
|
||||||
{invoice.pdfUrl ? (
|
{invoice.pdfUrl ? (
|
||||||
<AppButton color="primary" variant="contained" startIcon="document" href={invoice.pdfUrl} openInNewTab sx={{ m: 0 }}>
|
<AppButton color="primary" variant="contained" startIcon="document" href={invoice.pdfUrl} openInNewTab>
|
||||||
{t('download_invoice')}
|
{t('download_invoice')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<AppButton color="primary" variant="contained" startIcon="document" onClick={handlePrint} sx={{ m: 0 }}>
|
<AppButton color="primary" variant="contained" startIcon="document" onClick={handlePrint}>
|
||||||
{t('print_invoice')}
|
{t('print_invoice')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
)}
|
)}
|
||||||
<AppButton variant="text" onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${bookingId}`)} sx={{ m: 0 }}>
|
<AppButton variant="text" onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${bookingId}`)}>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -207,7 +207,7 @@ function MetaRow({ label, value, ltr }: { label: string; value: string; ltr?: bo
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }} dir={ltr ? 'ltr' : undefined}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }} dir={ltr ? 'ltr' : undefined}>
|
||||||
{value}
|
{value}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ function LeaveReviewCta({ bookingId }: { bookingId: number }) {
|
|||||||
color="primary"
|
color="primary"
|
||||||
startIcon="star"
|
startIcon="star"
|
||||||
onClick={() => router.push(`/${locale}${bookingReviewPath(bookingId)}`)}
|
onClick={() => router.push(`/${locale}${bookingReviewPath(bookingId)}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{alreadyReviewed ? (underReview ? t('cta_under_review') : t('cta_view_review')) : t('cta_leave')}
|
{alreadyReviewed ? (underReview ? t('cta_under_review') : t('cta_view_review')) : t('cta_leave')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -95,7 +94,6 @@ function CustomerBookingActions({ bookingId }: { bookingId: number }) {
|
|||||||
color="error"
|
color="error"
|
||||||
startIcon="rejected"
|
startIcon="rejected"
|
||||||
onClick={() => router.push(`/${locale}${bookingCancelPath(bookingId)}`)}
|
onClick={() => router.push(`/${locale}${bookingCancelPath(bookingId)}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('cancel_booking_cta')}
|
{t('cancel_booking_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -114,7 +112,7 @@ function CustomerBookingActions({ bookingId }: { bookingId: number }) {
|
|||||||
variant="text"
|
variant="text"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => router.push(`/${locale}${bookingRefundStatusPath(bookingId)}`)}
|
onClick={() => router.push(`/${locale}${bookingRefundStatusPath(bookingId)}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('view_refund_status')}
|
{t('view_refund_status')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function RefundStatusPage() {
|
|||||||
variant="text"
|
variant="text"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${bookingId}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${bookingId}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back_to_booking')}
|
{t('back_to_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export default function LeaveReviewPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => router.back()} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton variant="outlined" color="primary" onClick={() => router.back()} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{tc('back')}
|
{tc('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -124,7 +124,7 @@ export default function LeaveReviewPage() {
|
|||||||
{t(`reason_${reason}`)}
|
{t(`reason_${reason}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => router.back()} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton variant="outlined" color="primary" onClick={() => router.back()} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{tc('back')}
|
{tc('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -167,7 +167,7 @@ export default function LeaveReviewPage() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
||||||
<AppButton variant="text" color="primary" onClick={() => router.back()} sx={{ m: 0 }} disabled={createReview.isPending}>
|
<AppButton variant="text" color="primary" onClick={() => router.back()} disabled={createReview.isPending}>
|
||||||
{tc('cancel')}
|
{tc('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
@@ -176,7 +176,6 @@ export default function LeaveReviewPage() {
|
|||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={rating < 1 || createReview.isPending}
|
disabled={rating < 1 || createReview.isPending}
|
||||||
startIcon="star"
|
startIcon="star"
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{createReview.isPending ? t('submitting') : t('submit')}
|
{createReview.isPending ? t('submitting') : t('submit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const EligibilityStep: FunctionComponent<EligibilityStepProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
<AppButton color="secondary" variant="contained" size="large" onClick={() => onApproved(result)} sx={{ m: 0 }}>
|
<AppButton color="secondary" variant="contained" size="large" onClick={() => onApproved(result)}>
|
||||||
{t('approve_continue')}
|
{t('approve_continue')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -162,11 +162,10 @@ const EligibilityStep: FunctionComponent<EligibilityStepProps> = ({
|
|||||||
size="large"
|
size="large"
|
||||||
disabled={!consent || check.isPending}
|
disabled={!consent || check.isPending}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('check_eligibility')}
|
{t('check_eligibility')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="primary" onClick={onPayWithCard} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onPayWithCard}>
|
||||||
{t('pay_with_card')}
|
{t('pay_with_card')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -207,11 +206,11 @@ function DeclinedPanel({
|
|||||||
</Paper>
|
</Paper>
|
||||||
<Stack sx={{ gap: 1 }}>
|
<Stack sx={{ gap: 1 }}>
|
||||||
{onRetry && retryLabel ? (
|
{onRetry && retryLabel ? (
|
||||||
<AppButton variant="outlined" color="secondary" onClick={onRetry} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="secondary" onClick={onRetry}>
|
||||||
{retryLabel}
|
{retryLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
<AppButton color="primary" variant="contained" size="large" onClick={onPayWithCard} sx={{ m: 0 }}>
|
<AppButton color="primary" variant="contained" size="large" onClick={onPayWithCard}>
|
||||||
{cardLabel}
|
{cardLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ const MethodStep: FunctionComponent<MethodStepProps> = ({
|
|||||||
size="large"
|
size="large"
|
||||||
disabled={selectedProvider == null}
|
disabled={selectedProvider == null}
|
||||||
onClick={onContinue}
|
onClick={onContinue}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{selectedProvider
|
{selectedProvider
|
||||||
? t('continue_with', { provider: t(`provider_${selectedProvider}`) })
|
? t('continue_with', { provider: t(`provider_${selectedProvider}`) })
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const PlanStep: FunctionComponent<PlanStepProps> = ({
|
|||||||
{t('no_plans_body')}
|
{t('no_plans_body')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
<AppButton variant="outlined" color="primary" onClick={onBack} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={onBack}>
|
||||||
{t('back_to_providers')}
|
{t('back_to_providers')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -93,11 +93,10 @@ const PlanStep: FunctionComponent<PlanStepProps> = ({
|
|||||||
size="large"
|
size="large"
|
||||||
disabled={selectedPlanId == null}
|
disabled={selectedPlanId == null}
|
||||||
onClick={onContinue}
|
onClick={onContinue}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('continue')}
|
{t('continue')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="primary" onClick={onBack} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onBack}>
|
||||||
{tc('back')}
|
{tc('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const ScheduleStep: FunctionComponent<ScheduleStepProps> = ({
|
|||||||
<AppAlert severity="error" variant="outlined" sx={{ marginY: 0 }}>
|
<AppAlert severity="error" variant="outlined" sx={{ marginY: 0 }}>
|
||||||
{t('error_body')}
|
{t('error_body')}
|
||||||
</AppAlert>
|
</AppAlert>
|
||||||
<AppButton variant="outlined" color="secondary" onClick={() => refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="secondary" onClick={() => refetch()}>
|
||||||
{tc('retry')}
|
{tc('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -146,11 +146,11 @@ const ScheduleStep: FunctionComponent<ScheduleStepProps> = ({
|
|||||||
size="large"
|
size="large"
|
||||||
disabled={!accepted}
|
disabled={!accepted}
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
sx={{ m: 0, py: 1.25 }}
|
sx={{ py: 1.25 }}
|
||||||
>
|
>
|
||||||
{t('pay_down_payment')}
|
{t('pay_down_payment')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="primary" onClick={onBack} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onBack}>
|
||||||
{tc('back')}
|
{tc('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -61,10 +61,10 @@ function BnplGatewayScreen() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('handoff_body', { provider: providerName })}
|
{t('handoff_body', { provider: providerName })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton color="secondary" variant="contained" size="large" onClick={() => returnWith('success')} sx={{ m: 0 }}>
|
<AppButton color="secondary" variant="contained" size="large" onClick={() => returnWith('success')}>
|
||||||
{t('handoff_pay_success')}
|
{t('handoff_pay_success')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="error" onClick={() => returnWith('failure')} sx={{ m: 0 }}>
|
<AppButton variant="text" color="error" onClick={() => returnWith('failure')}>
|
||||||
{t('handoff_pay_fail')}
|
{t('handoff_pay_fail')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ function MessageCard({
|
|||||||
{body}
|
{body}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
<AppButton variant="contained" color="primary" onClick={onCta} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onCta}>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function BnplReturnScreen() {
|
|||||||
if (!validId) {
|
if (!validId) {
|
||||||
return (
|
return (
|
||||||
<StateCard icon="error" tone="var(--bal-error)" title={t('error_title')}>
|
<StateCard icon="error" tone="var(--bal-error)" title={t('error_title')}>
|
||||||
<AppButton variant="contained" onClick={() => router.replace(`/${locale}${ROUTES.BOOKINGS}`)} sx={{ m: 0 }}>
|
<AppButton variant="contained" onClick={() => router.replace(`/${locale}${ROUTES.BOOKINGS}`)}>
|
||||||
{t('pay_with_card')}
|
{t('pay_with_card')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</StateCard>
|
</StateCard>
|
||||||
@@ -113,7 +113,6 @@ function BnplReturnScreen() {
|
|||||||
<AppButton
|
<AppButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => router.replace(`/${locale}${ROUTES.BOOKING_REQUEST_STATUS}/${requestId}`)}
|
onClick={() => router.replace(`/${locale}${ROUTES.BOOKING_REQUEST_STATUS}/${requestId}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{tp('back_to_request')}
|
{tp('back_to_request')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -129,14 +128,12 @@ function BnplReturnScreen() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => router.replace(`/${locale}${ROUTES.CHECKOUT_BNPL}?${BNPL_QUERY_REQUEST_ID}=${requestId}`)}
|
onClick={() => router.replace(`/${locale}${ROUTES.CHECKOUT_BNPL}?${BNPL_QUERY_REQUEST_ID}=${requestId}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('retry_installments')}
|
{t('retry_installments')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={() => router.replace(`/${locale}${ROUTES.CHECKOUT}?${CHECKOUT_QUERY_REQUEST_ID}=${requestId}`)}
|
onClick={() => router.replace(`/${locale}${ROUTES.CHECKOUT}?${CHECKOUT_QUERY_REQUEST_ID}=${requestId}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('pay_with_card')}
|
{t('pay_with_card')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -148,7 +145,7 @@ function BnplReturnScreen() {
|
|||||||
return (
|
return (
|
||||||
<StateCard icon="installments" tone="var(--bal-secondary)" title={t('settling_title')} body={t('settling_body')}>
|
<StateCard icon="installments" tone="var(--bal-secondary)" title={t('settling_title')} body={t('settling_body')}>
|
||||||
<CircularProgress color="secondary" size="2.5rem" />
|
<CircularProgress color="secondary" size="2.5rem" />
|
||||||
<AppButton variant="text" disabled={orderQuery.isFetching} onClick={() => orderQuery.refetch()} sx={{ m: 0 }}>
|
<AppButton variant="text" disabled={orderQuery.isFetching} onClick={() => orderQuery.refetch()}>
|
||||||
{t('check_again')}
|
{t('check_again')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</StateCard>
|
</StateCard>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ function ConfirmationScreen() {
|
|||||||
{summary.variantLabel} · {summary.nurseName}
|
{summary.variantLabel} · {summary.nurseName}
|
||||||
</Typography>
|
</Typography>
|
||||||
{isBnpl ? (
|
{isBnpl ? (
|
||||||
<Typography variant="caption" sx={{ color: 'var(--bal-secondary)', fontWeight: 600, mt: 0.5 }}>
|
<Typography variant="caption" sx={{ color: 'var(--bal-secondary)', fontWeight: 500, mt: 0.5 }}>
|
||||||
{tBnpl('paid_via_installments', {
|
{tBnpl('paid_via_installments', {
|
||||||
provider: bnplProvider ? tBnpl(`provider_${bnplProvider}`) : tBnpl('installments_heading'),
|
provider: bnplProvider ? tBnpl(`provider_${bnplProvider}`) : tBnpl('installments_heading'),
|
||||||
})}
|
})}
|
||||||
@@ -94,7 +94,6 @@ function ConfirmationScreen() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(`/${locale}${bookingId != null ? `${ROUTES.BOOKINGS}/${bookingId}` : ROUTES.BOOKINGS}`)
|
router.push(`/${locale}${bookingId != null ? `${ROUTES.BOOKINGS}/${bookingId}` : ROUTES.BOOKINGS}`)
|
||||||
}
|
}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -104,7 +103,6 @@ function ConfirmationScreen() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
startIcon="document"
|
startIcon="document"
|
||||||
onClick={() => router.push(`/${locale}${bookingInvoicePath(bookingId)}`)}
|
onClick={() => router.push(`/${locale}${bookingInvoicePath(bookingId)}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('download_invoice')}
|
{t('download_invoice')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ function CheckoutScreen() {
|
|||||||
size="large"
|
size="large"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={handlePay}
|
onClick={handlePay}
|
||||||
sx={{ m: 0, py: 1.25 }}
|
sx={{ py: 1.25 }}
|
||||||
>
|
>
|
||||||
{initiate.isPending ? t('state_initiating') : initiate.isSuccess ? t('state_redirecting') : t('cta_pay')}
|
{initiate.isPending ? t('state_initiating') : initiate.isSuccess ? t('state_redirecting') : t('cta_pay')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -217,7 +217,6 @@ function CheckoutScreen() {
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
startIcon="installments"
|
startIcon="installments"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.CHECKOUT_BNPL}?${CHECKOUT_QUERY_REQUEST_ID}=${requestId}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.CHECKOUT_BNPL}?${CHECKOUT_QUERY_REQUEST_ID}=${requestId}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('bnpl_option')}
|
{t('bnpl_option')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -278,7 +277,7 @@ function MessageCard({
|
|||||||
{body}
|
{body}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
<AppButton variant="contained" color="primary" onClick={onCta} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onCta}>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ function ReturnScreen() {
|
|||||||
if (!validId) {
|
if (!validId) {
|
||||||
return (
|
return (
|
||||||
<StateCard icon="error" tone="var(--bal-error)" title={t('error_title')}>
|
<StateCard icon="error" tone="var(--bal-error)" title={t('error_title')}>
|
||||||
<AppButton variant="contained" onClick={() => router.replace(`/${locale}${ROUTES.BOOKINGS}`)} sx={{ m: 0 }}>
|
<AppButton variant="contained" onClick={() => router.replace(`/${locale}${ROUTES.BOOKINGS}`)}>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</StateCard>
|
</StateCard>
|
||||||
@@ -101,7 +101,6 @@ function ReturnScreen() {
|
|||||||
<AppButton
|
<AppButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => router.replace(`/${locale}${ROUTES.BOOKING_REQUEST_STATUS}/${requestId}`)}
|
onClick={() => router.replace(`/${locale}${ROUTES.BOOKING_REQUEST_STATUS}/${requestId}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('back_to_request')}
|
{t('back_to_request')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -120,14 +119,12 @@ function ReturnScreen() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.replace(`/${locale}${ROUTES.CHECKOUT}?${CHECKOUT_QUERY_REQUEST_ID}=${requestId}`)
|
router.replace(`/${locale}${ROUTES.CHECKOUT}?${CHECKOUT_QUERY_REQUEST_ID}=${requestId}`)
|
||||||
}
|
}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('retry_payment')}
|
{t('retry_payment')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
variant="text"
|
variant="text"
|
||||||
onClick={() => router.replace(`/${locale}${ROUTES.BOOKING_REQUEST_STATUS}/${requestId}`)}
|
onClick={() => router.replace(`/${locale}${ROUTES.BOOKING_REQUEST_STATUS}/${requestId}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('back_to_request')}
|
{t('back_to_request')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -141,7 +138,7 @@ function ReturnScreen() {
|
|||||||
<CircularProgress color="primary" size="2.5rem" />
|
<CircularProgress color="primary" size="2.5rem" />
|
||||||
<PaymentStatusBadge status="pending" />
|
<PaymentStatusBadge status="pending" />
|
||||||
{/* Manual re-check — covers the bounded poll giving up on a very slow callback. */}
|
{/* Manual re-check — covers the bounded poll giving up on a very slow callback. */}
|
||||||
<AppButton variant="text" disabled={outcomeQuery.isFetching} onClick={() => outcomeQuery.refetch()} sx={{ m: 0 }}>
|
<AppButton variant="text" disabled={outcomeQuery.isFetching} onClick={() => outcomeQuery.refetch()}>
|
||||||
{t('check_again')}
|
{t('check_again')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</StateCard>
|
</StateCard>
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ function BookingRow({ item }: { item: BookingListItemDto }) {
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="bookings"
|
endIcon="bookings"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${item.id}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${item.id}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export default function BookingRequestStatusPage() {
|
|||||||
size="large"
|
size="large"
|
||||||
endIcon="payment"
|
endIcon="payment"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.CHECKOUT}?request_id=${request.id}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.CHECKOUT}?request_id=${request.id}`)}
|
||||||
sx={{ m: 0, py: 1.25 }}
|
sx={{ py: 1.25 }}
|
||||||
>
|
>
|
||||||
{t('continue_payment')}
|
{t('continue_payment')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -207,7 +207,7 @@ export default function BookingRequestStatusPage() {
|
|||||||
color="error"
|
color="error"
|
||||||
disabled={cancelRequest.isPending}
|
disabled={cancelRequest.isPending}
|
||||||
onClick={() => setConfirmCancel(true)}
|
onClick={() => setConfirmCancel(true)}
|
||||||
sx={{ m: 0, alignSelf: 'center' }}
|
sx={{ alignSelf: 'center' }}
|
||||||
>
|
>
|
||||||
{cancelRequest.isPending ? t('cancelling') : t('cancel_request')}
|
{cancelRequest.isPending ? t('cancelling') : t('cancel_request')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -273,7 +273,7 @@ function TerminalCard({
|
|||||||
{body}
|
{body}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
<AppButton variant="contained" color="primary" onClick={onCta} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onCta}>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ function BookingRequestForm() {
|
|||||||
'& .MuiToggleButton-root': {
|
'& .MuiToggleButton-root': {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
py: 1.25,
|
py: 1.25,
|
||||||
fontWeight: 600,
|
fontWeight: 700,
|
||||||
borderColor: attempted && gender === '' ? 'var(--bal-error)' : undefined,
|
borderColor: attempted && gender === '' ? 'var(--bal-error)' : undefined,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -438,7 +438,7 @@ function BookingRequestForm() {
|
|||||||
startIcon="requests"
|
startIcon="requests"
|
||||||
disabled={!requiredChosen || genderMismatch || createRequest.isPending}
|
disabled={!requiredChosen || genderMismatch || createRequest.isPending}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
sx={{ m: 0, py: 1.5 }}
|
sx={{ py: 1.5 }}
|
||||||
>
|
>
|
||||||
{createRequest.isPending ? t('submitting') : t('submit')}
|
{createRequest.isPending ? t('submitting') : t('submit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -494,7 +494,7 @@ function FieldEmpty({
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{message}
|
{message}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="add" onClick={onCta} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="add" onClick={onCta}>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -525,7 +525,7 @@ function EmptyState({
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
|
||||||
{body}
|
{body}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="contained" color="primary" onClick={onCta} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onCta}>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export default function OnboardingPage() {
|
|||||||
fullWidth
|
fullWidth
|
||||||
disabled={!relation}
|
disabled={!relation}
|
||||||
onClick={() => setStep(1)}
|
onClick={() => setStep(1)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('continue')}
|
{t('continue')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const NudgeCard: FunctionComponent<NudgeCardProps> = ({ icon, title, body, ctaLa
|
|||||||
{body}
|
{body}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<AppButton color="primary" variant="outlined" to={to} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton color="primary" variant="outlined" to={to} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -180,7 +180,7 @@ const CategoryGrid: FunctionComponent<{ onSelect: (categoryId: number) => void }
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1 }}>
|
||||||
{t('categories_error')}
|
{t('categories_error')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={() => refetch()}>
|
||||||
{tc('retry')}
|
{tc('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function PatientRecordPage() {
|
|||||||
sx={{ p: 2, borderRadius: 2, backgroundColor: 'var(--bal-primary-soft)', display: 'flex', gap: 1, alignItems: 'center' }}
|
sx={{ p: 2, borderRadius: 2, backgroundColor: 'var(--bal-primary-soft)', display: 'flex', gap: 1, alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
<AppIcon icon="family" size={22} color="var(--bal-primary)" />
|
<AppIcon icon="family" size={22} color="var(--bal-primary)" />
|
||||||
<Typography variant="body2" sx={{ color: 'var(--bal-primary)', fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ color: 'var(--bal-primary)', fontWeight: 500 }}>
|
||||||
{t('ownership_banner')}
|
{t('ownership_banner')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -435,13 +435,13 @@ function HistoryTab({ patientId }: { patientId: number }) {
|
|||||||
))}
|
))}
|
||||||
{totalPages > 1 ? (
|
{totalPages > 1 ? (
|
||||||
<Stack direction="row" sx={{ gap: 1, justifyContent: 'center', alignItems: 'center' }}>
|
<Stack direction="row" sx={{ gap: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
<AppButton variant="text" color="primary" disabled={page <= 1 || history.isFetching} onClick={() => setPage((n) => Math.max(1, n - 1))} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" disabled={page <= 1 || history.isFetching} onClick={() => setPage((n) => Math.max(1, n - 1))}>
|
||||||
{t('prev')}
|
{t('prev')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
||||||
{t('page_of', { page, total: totalPages })}
|
{t('page_of', { page, total: totalPages })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="text" color="primary" disabled={page >= totalPages || history.isFetching} onClick={() => setPage((n) => n + 1)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" disabled={page >= totalPages || history.isFetching} onClick={() => setPage((n) => n + 1)}>
|
||||||
{t('next')}
|
{t('next')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -468,7 +468,7 @@ function SectionShell({
|
|||||||
return (
|
return (
|
||||||
<Stack sx={{ gap: 1.5 }}>
|
<Stack sx={{ gap: 1.5 }}>
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<AppButton variant="text" color="primary" startIcon="edit" onClick={onEdit} sx={{ m: 0, alignSelf: 'flex-end' }}>
|
<AppButton variant="text" color="primary" startIcon="edit" onClick={onEdit} sx={{ alignSelf: 'flex-end' }}>
|
||||||
{tc('edit')}
|
{tc('edit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -500,14 +500,14 @@ function EditActions({
|
|||||||
const tc = useTranslations('common');
|
const tc = useTranslations('common');
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ gap: 1 }}>
|
<Stack sx={{ gap: 1 }}>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="add" onClick={onAdd} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton variant="outlined" color="primary" startIcon="add" onClick={onAdd} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{t('add_item')}
|
{t('add_item')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
||||||
<AppButton variant="text" color="primary" onClick={onCancel} disabled={saving} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onCancel} disabled={saving}>
|
||||||
{tc('cancel')}
|
{tc('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="contained" color="primary" onClick={onSave} disabled={saving || !canSave} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onSave} disabled={saving || !canSave}>
|
||||||
{saving ? tc('saving') : tc('save')}
|
{saving ? tc('saving') : tc('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -524,7 +524,7 @@ function BackToPatients() {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.PATIENTS}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.PATIENTS}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back_to_patients')}
|
{t('back_to_patients')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export default function PatientsPage() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{!isEmpty ? (
|
{!isEmpty ? (
|
||||||
<AppButton color="primary" variant="contained" startIcon="add" onClick={openAdd} sx={{ m: 0, flexShrink: 0 }}>
|
<AppButton color="primary" variant="contained" startIcon="add" onClick={openAdd} sx={{ flexShrink: 0 }}>
|
||||||
{t('add')}
|
{t('add')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ const CustomerProfileForm: FunctionComponent<{
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={upsert.isPending}
|
disabled={upsert.isPending}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{upsert.isPending ? tc('saving') : t('save')}
|
{upsert.isPending ? tc('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -156,7 +156,7 @@ const CustomerProfileForm: FunctionComponent<{
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon="location"
|
startIcon="location"
|
||||||
to={`/${locale}${ROUTES.ADDRESSES}`}
|
to={`/${locale}${ROUTES.ADDRESSES}`}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{ta('manage_cta')}
|
{ta('manage_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export default function NurseProfilePage() {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => (notFound ? router.push(`/${locale}${ROUTES.SEARCH}`) : refetch())}
|
onClick={() => (notFound ? router.push(`/${locale}${ROUTES.SEARCH}`) : refetch())}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{notFound ? t('profile_not_found_cta') : t('retry')}
|
{notFound ? t('profile_not_found_cta') : t('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -95,7 +94,7 @@ export default function NurseProfilePage() {
|
|||||||
size="large"
|
size="large"
|
||||||
onClick={requestBooking}
|
onClick={requestBooking}
|
||||||
startIcon="bookings"
|
startIcon="bookings"
|
||||||
sx={{ m: 0, py: 1.5 }}
|
sx={{ py: 1.5 }}
|
||||||
>
|
>
|
||||||
{t('request_booking')}
|
{t('request_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -230,7 +229,7 @@ function ReviewsPanel({ nurseId }: { nurseId: number }) {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
||||||
{t('load_error')}
|
{t('load_error')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={() => refetch()}>
|
||||||
{t('retry')}
|
{t('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -278,7 +277,7 @@ function ReviewsPanel({ nurseId }: { nurseId: number }) {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => fetchNextPage()}
|
onClick={() => fetchNextPage()}
|
||||||
disabled={isFetchingNextPage}
|
disabled={isFetchingNextPage}
|
||||||
sx={{ m: 0, alignSelf: 'center' }}
|
sx={{ alignSelf: 'center' }}
|
||||||
>
|
>
|
||||||
{isFetchingNextPage ? t('loading') : t('load_more')}
|
{isFetchingNextPage ? t('loading') : t('load_more')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ function SearchFilterScreen() {
|
|||||||
disabled={!controller.isReady}
|
disabled={!controller.isReady}
|
||||||
onClick={goToResults}
|
onClick={goToResults}
|
||||||
startIcon="search"
|
startIcon="search"
|
||||||
sx={{ m: 0, py: 1.5 }}
|
sx={{ py: 1.5 }}
|
||||||
>
|
>
|
||||||
{ctaLabel}
|
{ctaLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ function ResultsScreen() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
|
||||||
{t('results_error')}
|
{t('results_error')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={() => refetch()}>
|
||||||
{t('retry')}
|
{t('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -97,7 +97,7 @@ function ResultsScreen() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setPageSize((size) => size + SEARCH_PAGE_SIZE)}
|
onClick={() => setPageSize((size) => size + SEARCH_PAGE_SIZE)}
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
sx={{ m: 0, alignSelf: 'center' }}
|
sx={{ alignSelf: 'center' }}
|
||||||
>
|
>
|
||||||
{t('load_more')}
|
{t('load_more')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -128,7 +128,7 @@ function EmptyState({ onRelax }: { onRelax: () => void }) {
|
|||||||
{t('empty_suggest_city')}
|
{t('empty_suggest_city')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<AppButton variant="contained" color="primary" onClick={onRelax} startIcon="tune" sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onRelax} startIcon="tune">
|
||||||
{t('empty_cta')}
|
{t('empty_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const WalletInstallments: FunctionComponent = () => {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('wallet_error_body')}
|
{t('wallet_error_body')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="secondary" onClick={() => refetch()} sx={{ m: 0, mt: 1 }}>
|
<AppButton variant="outlined" color="secondary" onClick={() => refetch()} sx={{ mt: 1 }}>
|
||||||
{tc('retry')}
|
{tc('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -106,7 +106,7 @@ function InstallmentPlanSection({ plan }: { plan: WalletInstallmentPlan }) {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleEarlyPay}
|
onClick={handleEarlyPay}
|
||||||
sx={{ m: 0, flex: 'none' }}
|
sx={{ flex: 'none' }}
|
||||||
>
|
>
|
||||||
{t('early_pay')}
|
{t('early_pay')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ export default function AdminAuditPage() {
|
|||||||
onChange={(e) => setDraft((d) => ({ ...d, to: e.target.value || undefined }))}
|
onChange={(e) => setDraft((d) => ({ ...d, to: e.target.value || undefined }))}
|
||||||
slotProps={{ inputLabel: { shrink: true } }}
|
slotProps={{ inputLabel: { shrink: true } }}
|
||||||
/>
|
/>
|
||||||
<AppButton variant="contained" color="primary" onClick={apply} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={apply}>
|
||||||
{t('apply')}
|
{t('apply')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="inherit" onClick={clear} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={clear}>
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -159,15 +159,15 @@ function ConfigEditDialog({ config, onClose }: { config: PlatformConfig; onClose
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DialogContentText sx={{ mt: 2, color: 'var(--bal-warning)', fontWeight: 600 }}>
|
<DialogContentText sx={{ mt: 2, color: 'var(--bal-warning)', fontWeight: 500 }}>
|
||||||
{t('cfg_save_confirm_body')}
|
{t('cfg_save_confirm_body')}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} disabled={update.isPending} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} disabled={update.isPending}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="contained" color="primary" onClick={onSave} disabled={!!errorKey || update.isPending} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onSave} disabled={!!errorKey || update.isPending}>
|
||||||
{update.isPending ? t('saving') : t('save')}
|
{update.isPending ? t('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
@@ -187,7 +187,7 @@ function ConfigHistoryDrawer({ configKey, onClose }: { configKey: string | null;
|
|||||||
<Drawer anchor={anchor} open={configKey != null} onClose={onClose} slotProps={{ paper: { sx: { width: { xs: '100%', sm: 420 }, p: 3 } } }}>
|
<Drawer anchor={anchor} open={configKey != null} onClose={onClose} slotProps={{ paper: { sx: { width: { xs: '100%', sm: 420 }, p: 3 } } }}>
|
||||||
<Stack direction="row" sx={{ justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Stack direction="row" sx={{ justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="h6">{configKey ? t('cfg_history_title', { key: configKey }) : ''}</Typography>
|
<Typography variant="h6">{configKey ? t('cfg_history_title', { key: configKey }) : ''}</Typography>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} sx={{ m: 0, minWidth: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} sx={{ minWidth: 0 }}>
|
||||||
<AppIcon icon="close" />
|
<AppIcon icon="close" />
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function AdminHolidaysPage() {
|
|||||||
key: 'actions',
|
key: 'actions',
|
||||||
header: '',
|
header: '',
|
||||||
render: (h: Holiday) => (
|
render: (h: Holiday) => (
|
||||||
<AppButton variant="text" color="primary" startIcon="edit" onClick={() => setEditing(h)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" startIcon="edit" onClick={() => setEditing(h)}>
|
||||||
{t('cfg_edit')}
|
{t('cfg_edit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
),
|
),
|
||||||
@@ -79,7 +79,7 @@ export default function AdminHolidaysPage() {
|
|||||||
subtitle={t('hol_subtitle')}
|
subtitle={t('hol_subtitle')}
|
||||||
actions={
|
actions={
|
||||||
caps.canConfig ? (
|
caps.canConfig ? (
|
||||||
<AppButton variant="contained" color="primary" startIcon="add" onClick={() => setEditing('new')} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" startIcon="add" onClick={() => setEditing('new')}>
|
||||||
{t('hol_add')}
|
{t('hol_add')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : undefined
|
) : undefined
|
||||||
@@ -168,10 +168,10 @@ function HolidayDialog({ holiday, onClose }: { holiday: Holiday | null; onClose:
|
|||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} disabled={upsert.isPending} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} disabled={upsert.isPending}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="contained" color="primary" onClick={onSave} disabled={!valid || upsert.isPending} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onSave} disabled={!valid || upsert.isPending}>
|
||||||
{upsert.isPending ? t('saving') : t('save')}
|
{upsert.isPending ? t('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export default function AdminPartnerCenterDetailPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
startIcon="partners"
|
startIcon="partners"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.ADMIN_PARTNERS}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.ADMIN_PARTNERS}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back')}
|
{t('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -162,7 +162,6 @@ export default function AdminPartnerCenterDetailPage() {
|
|||||||
startIcon="verified"
|
startIcon="verified"
|
||||||
onClick={() => setConfirmVerify(true)}
|
onClick={() => setConfirmVerify(true)}
|
||||||
disabled={verify.isPending}
|
disabled={verify.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('partner_verify')}
|
{t('partner_verify')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -172,11 +171,10 @@ export default function AdminPartnerCenterDetailPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setActive.mutate(!data.isActive)}
|
onClick={() => setActive.mutate(!data.isActive)}
|
||||||
disabled={setActive.isPending}
|
disabled={setActive.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t(data.isActive ? 'partner_suspend' : 'partner_activate')}
|
{t(data.isActive ? 'partner_suspend' : 'partner_activate')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="outlined" color="inherit" startIcon="edit" onClick={() => setEditing(true)} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="inherit" startIcon="edit" onClick={() => setEditing(true)}>
|
||||||
{t('partner_edit')}
|
{t('partner_edit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -211,7 +209,6 @@ export default function AdminPartnerCenterDetailPage() {
|
|||||||
startIcon="delete"
|
startIcon="delete"
|
||||||
onClick={() => onRemove(nurse.nurseProfileId)}
|
onClick={() => onRemove(nurse.nurseProfileId)}
|
||||||
disabled={assignNurse.isPending}
|
disabled={assignNurse.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('partner_unlink_nurse')}
|
{t('partner_unlink_nurse')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -239,7 +236,7 @@ export default function AdminPartnerCenterDetailPage() {
|
|||||||
startIcon="assign"
|
startIcon="assign"
|
||||||
onClick={onAssign}
|
onClick={onAssign}
|
||||||
disabled={assignNurse.isPending || Number(assignId) <= 0}
|
disabled={assignNurse.isPending || Number(assignId) <= 0}
|
||||||
sx={{ m: 0, mt: 0.25 }}
|
sx={{ mt: 0.25 }}
|
||||||
>
|
>
|
||||||
{t('partner_assign_nurse')}
|
{t('partner_assign_nurse')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -270,7 +267,7 @@ function DetailRow({ label, children }: { label: string; children: ReactNode })
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" component="div" sx={{ fontWeight: 600, textAlign: 'end' }}>
|
<Typography variant="body2" component="div" sx={{ fontWeight: 500, textAlign: 'end' }}>
|
||||||
{children}
|
{children}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default function AdminPartnersPage() {
|
|||||||
subtitle={t('partner_subtitle')}
|
subtitle={t('partner_subtitle')}
|
||||||
actions={
|
actions={
|
||||||
caps.canManagePartners ? (
|
caps.canManagePartners ? (
|
||||||
<AppButton variant="contained" color="primary" startIcon="add" onClick={() => setCreating(true)} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" startIcon="add" onClick={() => setCreating(true)}>
|
||||||
{t('partner_create')}
|
{t('partner_create')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : undefined
|
) : undefined
|
||||||
@@ -245,10 +245,10 @@ export function PartnerCenterFormDialog({ center, onClose }: { center: PartnerCe
|
|||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} disabled={mutation.isPending} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} disabled={mutation.isPending}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="contained" color="primary" onClick={onSave} disabled={!valid || mutation.isPending} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onSave} disabled={!valid || mutation.isPending}>
|
||||||
{mutation.isPending ? t('saving') : t('save')}
|
{mutation.isPending ? t('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default function AdminPayoutBatchDetailPage() {
|
|||||||
variant="text"
|
variant="text"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.ADMIN_PAYOUTS}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.ADMIN_PAYOUTS}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back')}
|
{t('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -92,7 +92,7 @@ export default function AdminPayoutBatchDetailPage() {
|
|||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={t('payout_holiday_shift')}
|
label={t('payout_holiday_shift')}
|
||||||
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -226,7 +226,6 @@ const PayoutRowCard: FunctionComponent<{ row: AdminPayoutRow; batchId: number; c
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={() => setRetryOpen(true)}
|
onClick={() => setRetryOpen(true)}
|
||||||
disabled={retry.isPending}
|
disabled={retry.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('payout_retry')}
|
{t('payout_retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -251,7 +250,6 @@ const PayoutRowCard: FunctionComponent<{ row: AdminPayoutRow; batchId: number; c
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={onRecord}
|
onClick={onRecord}
|
||||||
disabled={reference.trim().length === 0 || record.isPending}
|
disabled={reference.trim().length === 0 || record.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{record.isPending ? t('saving') : t('save')}
|
{record.isPending ? t('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -279,7 +277,7 @@ const MetaLine: FunctionComponent<{ label: string; children: ReactNode }> = ({ l
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ fontWeight: 600, typography: 'body2' }}>{children}</Box>
|
<Box sx={{ fontWeight: 500, typography: 'body2' }}>{children}</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -288,7 +286,7 @@ const Field: FunctionComponent<{ label: string; children: ReactNode }> = ({ labe
|
|||||||
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{children}
|
{children}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export default function AdminPayoutsPage() {
|
|||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={t('payout_holiday_shift')}
|
label={t('payout_holiday_shift')}
|
||||||
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -143,7 +143,7 @@ export default function AdminPayoutsPage() {
|
|||||||
))}
|
))}
|
||||||
</TextField>
|
</TextField>
|
||||||
{caps.canPayout ? (
|
{caps.canPayout ? (
|
||||||
<AppButton variant="contained" color="primary" onClick={() => setPreviewOpen(true)} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={() => setPreviewOpen(true)}>
|
||||||
{t('payout_preview')}
|
{t('payout_preview')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -253,7 +253,6 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={onPreview}
|
onClick={onPreview}
|
||||||
disabled={!periodStart || !periodEnd || preview.isPending}
|
disabled={!periodStart || !periodEnd || preview.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('payout_preview')}
|
{t('payout_preview')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -272,7 +271,7 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
|
|||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={t('payout_holiday_shift')}
|
label={t('payout_holiday_shift')}
|
||||||
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -300,7 +299,7 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
|
|||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={t('payout_no_iban')}
|
label={t('payout_no_iban')}
|
||||||
sx={{ bgcolor: 'var(--bal-error)', color: 'var(--bal-error-contrast)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-error)', color: 'var(--bal-error-contrast)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -346,7 +345,7 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
|
|||||||
) : null}
|
) : null}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} disabled={run.isPending} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} disabled={run.isPending}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
{canPayout ? (
|
{canPayout ? (
|
||||||
@@ -355,7 +354,6 @@ function PreviewBatchDialog({ canPayout, onClose }: { canPayout: boolean; onClos
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setRunConfirmOpen(true)}
|
onClick={() => setRunConfirmOpen(true)}
|
||||||
disabled={!result || run.isPending}
|
disabled={!result || run.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('payout_run')}
|
{t('payout_run')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -182,13 +182,13 @@ function ModerationCard({
|
|||||||
|
|
||||||
{canModerate ? (
|
{canModerate ? (
|
||||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
||||||
<AppButton variant="contained" color="primary" startIcon="publish" onClick={() => onAct('publish')} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" startIcon="publish" onClick={() => onAct('publish')}>
|
||||||
{t('mod_publish')}
|
{t('mod_publish')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="outlined" color="inherit" onClick={() => onAct('hide')} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="inherit" onClick={() => onAct('hide')}>
|
||||||
{t('mod_hide')}
|
{t('mod_hide')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="outlined" color="error" onClick={() => onAct('reject')} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="error" onClick={() => onAct('reject')}>
|
||||||
{t('mod_reject')}
|
{t('mod_reject')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default function AdminRolesPage() {
|
|||||||
header: '',
|
header: '',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
render: (g: RoleGrant) => (
|
render: (g: RoleGrant) => (
|
||||||
<AppButton variant="text" color="error" startIcon="delete" onClick={() => setRevoking(g)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="error" startIcon="delete" onClick={() => setRevoking(g)}>
|
||||||
{t('role_revoke')}
|
{t('role_revoke')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
),
|
),
|
||||||
@@ -100,7 +100,7 @@ export default function AdminRolesPage() {
|
|||||||
subtitle={t('role_subtitle')}
|
subtitle={t('role_subtitle')}
|
||||||
actions={
|
actions={
|
||||||
caps.canManageRoles ? (
|
caps.canManageRoles ? (
|
||||||
<AppButton variant="contained" color="primary" startIcon="add" onClick={() => setGranting(true)} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" startIcon="add" onClick={() => setGranting(true)}>
|
||||||
{t('role_grant')}
|
{t('role_grant')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : undefined
|
) : undefined
|
||||||
@@ -193,10 +193,10 @@ function GrantRoleDialog({ onClose }: { onClose: () => void }) {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} disabled={grant.isPending} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} disabled={grant.isPending}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="contained" color="primary" onClick={onGrant} disabled={!valid || grant.isPending} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={onGrant} disabled={!valid || grant.isPending}>
|
||||||
{grant.isPending ? t('saving') : t('confirm')}
|
{grant.isPending ? t('saving') : t('confirm')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default function AdminTicketThreadPage() {
|
|||||||
variant="text"
|
variant="text"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.ADMIN_TICKETS}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.ADMIN_TICKETS}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back')}
|
{t('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -133,7 +133,6 @@ export default function AdminTicketThreadPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
startIcon="refunds"
|
startIcon="refunds"
|
||||||
onClick={() => setRefundShown((v) => !v)}
|
onClick={() => setRefundShown((v) => !v)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('refund_open')}
|
{t('refund_open')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -184,7 +183,7 @@ export default function AdminTicketThreadPage() {
|
|||||||
startIcon="send"
|
startIcon="send"
|
||||||
onClick={send}
|
onClick={send}
|
||||||
disabled={post.isPending || body.trim().length === 0}
|
disabled={post.isPending || body.trim().length === 0}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('ticket_send')}
|
{t('ticket_send')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -124,10 +124,10 @@ export default function AdminTicketsPage() {
|
|||||||
onChange={(e) => setDraft((d) => ({ ...d, referenceCode: e.target.value || undefined }))}
|
onChange={(e) => setDraft((d) => ({ ...d, referenceCode: e.target.value || undefined }))}
|
||||||
sx={{ minWidth: 220 }}
|
sx={{ minWidth: 220 }}
|
||||||
/>
|
/>
|
||||||
<AppButton variant="contained" color="primary" onClick={apply} sx={{ m: 0 }}>
|
<AppButton variant="contained" color="primary" onClick={apply}>
|
||||||
{t('apply')}
|
{t('apply')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="inherit" onClick={clear} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={clear}>
|
||||||
{t('clear')}
|
{t('clear')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default function AdminVerificationCasePage() {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
<Stack sx={{ gap: 1 }}>
|
<Stack sx={{ gap: 1 }}>
|
||||||
<AppButton variant="text" color="primary" onClick={backToQueue} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton variant="text" color="primary" onClick={backToQueue} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{t('back')}
|
{t('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AdminPageHeader title={t('ver_case_title')} />
|
<AdminPageHeader title={t('ver_case_title')} />
|
||||||
@@ -177,7 +177,6 @@ export default function AdminVerificationCasePage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setApproveOpen(true)}
|
onClick={() => setApproveOpen(true)}
|
||||||
disabled={!allPassed || !caps.canVerify}
|
disabled={!allPassed || !caps.canVerify}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('ver_approve')}
|
{t('ver_approve')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -186,7 +185,6 @@ export default function AdminVerificationCasePage() {
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={() => setRejectOpen(true)}
|
onClick={() => setRejectOpen(true)}
|
||||||
disabled={!caps.canVerify}
|
disabled={!caps.canVerify}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('ver_reject_all')}
|
{t('ver_reject_all')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -274,7 +272,7 @@ function StepCard({
|
|||||||
</Typography>
|
</Typography>
|
||||||
<StatusChip status={STEP_STATUS_KIND[step.status]} label={t(`step_${step.status}`)} />
|
<StatusChip status={STEP_STATUS_KIND[step.status]} label={t(`step_${step.status}`)} />
|
||||||
{step.isAutomated ? (
|
{step.isAutomated ? (
|
||||||
<Chip size="small" label={t('ver_automated_badge')} sx={{ bgcolor: 'action.hover', fontWeight: 600 }} />
|
<Chip size="small" label={t('ver_automated_badge')} sx={{ bgcolor: 'action.hover', fontWeight: 500 }} />
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
@@ -303,7 +301,6 @@ function StepCard({
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => (isCredentialStep ? setCredentialOpen(true) : onPass())}
|
onClick={() => (isCredentialStep ? setCredentialOpen(true) : onPass())}
|
||||||
disabled={decide.isPending}
|
disabled={decide.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('ver_pass')}
|
{t('ver_pass')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -312,7 +309,6 @@ function StepCard({
|
|||||||
color="error"
|
color="error"
|
||||||
onClick={() => setRejectOpen(true)}
|
onClick={() => setRejectOpen(true)}
|
||||||
disabled={decide.isPending}
|
disabled={decide.isPending}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('ver_reject')}
|
{t('ver_reject')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -434,7 +430,7 @@ function CredentialDialog({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={onClose} disabled={decide.isPending} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={onClose} disabled={decide.isPending}>
|
||||||
{t('cancel')}
|
{t('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
@@ -442,7 +438,6 @@ function CredentialDialog({
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
disabled={decide.isPending || expiryMissing}
|
disabled={decide.isPending || expiryMissing}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{decide.isPending ? t('saving') : t('save')}
|
{decide.isPending ? t('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function AdminVerificationQueuePage() {
|
|||||||
size="small"
|
size="small"
|
||||||
icon={<AppIcon icon="warning" size={14} color="var(--bal-warning-contrast)" />}
|
icon={<AppIcon icon="warning" size={14} color="var(--bal-warning-contrast)" />}
|
||||||
label={t('ver_expiring_warning')}
|
label={t('ver_expiring_warning')}
|
||||||
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-warning)', color: 'var(--bal-warning-contrast)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default function NurseBankPage() {
|
|||||||
onSuccess: () => enqueueSnackbar(t('primary_set'), { variant: 'success' }),
|
onSuccess: () => enqueueSnackbar(t('primary_set'), { variant: 'success' }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('make_primary')}
|
{t('make_primary')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -147,7 +147,7 @@ export default function NurseBankPage() {
|
|||||||
startIcon="bank"
|
startIcon="bank"
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={addAccount.isPending}
|
disabled={addAccount.isPending}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{addAccount.isPending ? t('submitting') : t('submit')}
|
{addAccount.isPending ? t('submitting') : t('submit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ export default function NurseCoveragePage() {
|
|||||||
key={area.id}
|
key={area.id}
|
||||||
label={chipLabel(area)}
|
label={chipLabel(area)}
|
||||||
onDelete={() => setRemoveTarget(area)}
|
onDelete={() => setRemoveTarget(area)}
|
||||||
sx={{ bgcolor: 'var(--bal-primary-soft)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-primary-soft)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -237,7 +237,7 @@ export default function NurseCoveragePage() {
|
|||||||
startIcon="add"
|
startIcon="add"
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
disabled={addArea.isPending}
|
disabled={addArea.isPending}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{addArea.isPending ? t('adding') : t('add')}
|
{addArea.isPending ? t('adding') : t('add')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ function ErrorPanel({ message, onRetry, retryLabel }: { message: string; onRetry
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
||||||
{message}
|
{message}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={onRetry} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={onRetry}>
|
||||||
{retryLabel}
|
{retryLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -199,13 +199,13 @@ function Pager({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', justifyContent: 'center' }}>
|
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<AppButton variant="text" color="primary" onClick={onPrev} disabled={page <= 1} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onPrev} disabled={page <= 1}>
|
||||||
{t('page_prev')}
|
{t('page_prev')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('page_indicator', { page: fmt(page), total: fmt(pageCount) })}
|
{t('page_indicator', { page: fmt(page), total: fmt(pageCount) })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="text" color="primary" onClick={onNext} disabled={page >= pageCount} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onNext} disabled={page >= pageCount}>
|
||||||
{t('page_next')}
|
{t('page_next')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function NursePayoutDetailPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
startIcon="wallet"
|
startIcon="wallet"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.NURSE_EARNINGS_PAYOUTS}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.NURSE_EARNINGS_PAYOUTS}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back_to_history')}
|
{t('back_to_history')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -193,7 +193,6 @@ export default function NursePayoutDetailPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="visits"
|
endIcon="visits"
|
||||||
onClick={() => router.push(`/${locale}${nurseBookingDetailPath(link.bookingId)}`)}
|
onClick={() => router.push(`/${locale}${nurseBookingDetailPath(link.bookingId)}`)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -213,7 +212,7 @@ const MetaLine: FunctionComponent<{ label: string; children: ReactNode }> = ({ l
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{children}
|
{children}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function NursePayoutHistoryPage() {
|
|||||||
color="primary"
|
color="primary"
|
||||||
startIcon="earnings"
|
startIcon="earnings"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.NURSE_EARNINGS}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.NURSE_EARNINGS}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('back_to_earnings')}
|
{t('back_to_earnings')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -58,7 +58,7 @@ export default function NursePayoutHistoryPage() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
||||||
{t('history_error')}
|
{t('history_error')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={() => history.refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={() => history.refetch()}>
|
||||||
{t('retry')}
|
{t('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -86,13 +86,13 @@ export default function NursePayoutHistoryPage() {
|
|||||||
|
|
||||||
{pageCount > 1 ? (
|
{pageCount > 1 ? (
|
||||||
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', justifyContent: 'center' }}>
|
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<AppButton variant="text" color="primary" onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={() => setPage((p) => Math.max(1, p - 1))} disabled={page <= 1}>
|
||||||
{t('page_prev')}
|
{t('page_prev')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('page_indicator', { page: fmt(page), total: fmt(pageCount) })}
|
{t('page_indicator', { page: fmt(page), total: fmt(pageCount) })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="text" color="primary" onClick={() => setPage((p) => Math.min(pageCount, p + 1))} disabled={page >= pageCount} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={() => setPage((p) => Math.min(pageCount, p + 1))} disabled={page >= pageCount}>
|
||||||
{t('page_next')}
|
{t('page_next')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const NurseProfileForm: FunctionComponent<{ initial: NurseProfile | null }> = ({
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('unverified_cta')}
|
{t('unverified_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -126,7 +126,7 @@ const NurseProfileForm: FunctionComponent<{ initial: NurseProfile | null }> = ({
|
|||||||
startIcon="camera"
|
startIcon="camera"
|
||||||
onClick={pickFile}
|
onClick={pickFile}
|
||||||
disabled={uploadAvatar.isPending}
|
disabled={uploadAvatar.isPending}
|
||||||
sx={{ m: 0, mt: 0.5, alignSelf: 'flex-start' }}
|
sx={{ mt: 0.5, alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{uploadAvatar.isPending ? t('uploading') : t('upload')}
|
{uploadAvatar.isPending ? t('uploading') : t('upload')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -166,7 +166,7 @@ const NurseProfileForm: FunctionComponent<{ initial: NurseProfile | null }> = ({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={upsert.isPending}
|
disabled={upsert.isPending}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{upsert.isPending ? tc('saving') : t('save')}
|
{upsert.isPending ? tc('saving') : t('save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function NurseRequestDetailPage() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
|
||||||
{t('not_found_body')}
|
{t('not_found_body')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => router.push(`/${locale}${ROUTES.NURSE_REQUESTS}`)} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={() => router.push(`/${locale}${ROUTES.NURSE_REQUESTS}`)}>
|
||||||
{t('inbox_title')}
|
{t('inbox_title')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -132,13 +132,13 @@ export default function NurseRequestDetailPage() {
|
|||||||
<Paper elevation={0} sx={{ p: 2.5, border: '1px solid', borderColor: 'divider', borderRadius: 2 }}>
|
<Paper elevation={0} sx={{ p: 2.5, border: '1px solid', borderColor: 'divider', borderRadius: 2 }}>
|
||||||
<Stack sx={{ gap: 1.5 }}>
|
<Stack sx={{ gap: 1.5 }}>
|
||||||
<DetailRow caption={t('summary_patient')}>
|
<DetailRow caption={t('summary_patient')}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{request.patientName}
|
{request.patientName}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
<DetailRow caption={t('summary_service')}>
|
<DetailRow caption={t('summary_service')}>
|
||||||
<Stack sx={{ gap: 0.25, alignItems: 'flex-end' }}>
|
<Stack sx={{ gap: 0.25, alignItems: 'flex-end' }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{request.variantLabel}
|
{request.variantLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
{request.variantPrice ? (
|
{request.variantPrice ? (
|
||||||
@@ -147,12 +147,12 @@ export default function NurseRequestDetailPage() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
<DetailRow caption={t('location_label')}>
|
<DetailRow caption={t('location_label')}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, textAlign: 'end' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, textAlign: 'end' }}>
|
||||||
{location}
|
{location}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
<DetailRow caption={t('summary_when')}>
|
<DetailRow caption={t('summary_when')}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, textAlign: 'end' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, textAlign: 'end' }}>
|
||||||
{whenLabel}
|
{whenLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
@@ -161,7 +161,7 @@ export default function NurseRequestDetailPage() {
|
|||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={t(`gender_${request.requiredCaregiverGender}`)}
|
label={t(`gender_${request.requiredCaregiverGender}`)}
|
||||||
sx={{ bgcolor: 'var(--bal-primary-soft)', color: 'var(--bal-primary)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-primary-soft)', color: 'var(--bal-primary)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -202,7 +202,7 @@ export default function NurseRequestDetailPage() {
|
|||||||
startIcon="verified"
|
startIcon="verified"
|
||||||
disabled={acceptRequest.isPending}
|
disabled={acceptRequest.isPending}
|
||||||
onClick={handleAccept}
|
onClick={handleAccept}
|
||||||
sx={{ m: 0, flex: 1, py: 1.25 }}
|
sx={{ flex: 1, py: 1.25 }}
|
||||||
>
|
>
|
||||||
{acceptRequest.isPending ? t('accepting') : t('accept')}
|
{acceptRequest.isPending ? t('accepting') : t('accept')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -212,7 +212,7 @@ export default function NurseRequestDetailPage() {
|
|||||||
startIcon="rejected"
|
startIcon="rejected"
|
||||||
disabled={acceptRequest.isPending}
|
disabled={acceptRequest.isPending}
|
||||||
onClick={() => setRejectOpen(true)}
|
onClick={() => setRejectOpen(true)}
|
||||||
sx={{ m: 0, flex: 1, py: 1.25 }}
|
sx={{ flex: 1, py: 1.25 }}
|
||||||
>
|
>
|
||||||
{t('reject')}
|
{t('reject')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function InboxCard({ item }: { item: BookingRequestListItem }) {
|
|||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={t('required_gender_chip', { gender: t(`gender_${item.requiredCaregiverGender}`) })}
|
label={t('required_gender_chip', { gender: t(`gender_${item.requiredCaregiverGender}`) })}
|
||||||
sx={{ bgcolor: 'var(--bal-primary-soft)', color: 'var(--bal-primary)', fontWeight: 600 }}
|
sx={{ bgcolor: 'var(--bal-primary-soft)', color: 'var(--bal-primary)', fontWeight: 500 }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -105,7 +105,7 @@ function InboxCard({ item }: { item: BookingRequestListItem }) {
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="requests"
|
endIcon="requests"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.NURSE_REQUESTS}/${item.id}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.NURSE_REQUESTS}/${item.id}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('open_detail')}
|
{t('open_detail')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const MyServicesList: FunctionComponent<MyServicesListProps> = ({ onAdd, onEdit
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{!isEmpty && !isLoading ? (
|
{!isEmpty && !isLoading ? (
|
||||||
<AppButton color="primary" variant="contained" startIcon="add" onClick={onAdd} sx={{ m: 0, flexShrink: 0 }}>
|
<AppButton color="primary" variant="contained" startIcon="add" onClick={onAdd} sx={{ flexShrink: 0 }}>
|
||||||
{t('add')}
|
{t('add')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ const PublishGate: FunctionComponent = () => {
|
|||||||
startIcon="publish"
|
startIcon="publish"
|
||||||
disabled={!approved}
|
disabled={!approved}
|
||||||
onClick={() => enqueueSnackbar(t('publish_done'), { variant: 'success' })}
|
onClick={() => enqueueSnackbar(t('publish_done'), { variant: 'success' })}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('publish_cta')}
|
{t('publish_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -71,7 +70,6 @@ const PublishGate: FunctionComponent = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('publish_complete_verification')}
|
{t('publish_complete_verification')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
borderInlineStartColor: 'var(--bal-warning)',
|
borderInlineStartColor: 'var(--bal-warning)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body2" sx={{ color: 'var(--bal-warning)', fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ color: 'var(--bal-warning)', fontWeight: 500 }}>
|
||||||
{t('duplicate_warning')}
|
{t('duplicate_warning')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -306,10 +306,10 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
{priceStep}
|
{priceStep}
|
||||||
|
|
||||||
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
||||||
<AppButton variant="text" onClick={onCancel} disabled={submitting} sx={{ m: 0 }}>
|
<AppButton variant="text" onClick={onCancel} disabled={submitting}>
|
||||||
{tc('cancel')}
|
{tc('cancel')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton color="primary" variant="contained" onClick={submit} disabled={submitting} sx={{ m: 0 }}>
|
<AppButton color="primary" variant="contained" onClick={submit} disabled={submitting}>
|
||||||
{submitting ? tc('saving') : t('submit_save')}
|
{submitting ? tc('saving') : t('submit_save')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -350,7 +350,7 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('categories_error')}
|
{t('categories_error')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" onClick={() => categoriesQuery.refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={() => categoriesQuery.refetch()}>
|
||||||
{tc('retry')}
|
{tc('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -411,7 +411,7 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
: group.isRequired
|
: group.isRequired
|
||||||
? 'var(--bal-primary)'
|
? 'var(--bal-primary)'
|
||||||
: 'text.secondary',
|
: 'text.secondary',
|
||||||
fontWeight: 600,
|
fontWeight: 500,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -435,7 +435,7 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{optionsError && missingRequiredGroups.length > 0 ? (
|
{optionsError && missingRequiredGroups.length > 0 ? (
|
||||||
<Typography variant="body2" sx={{ color: 'var(--bal-error)', fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ color: 'var(--bal-error)', fontWeight: 500 }}>
|
||||||
{t('options_incomplete')}
|
{t('options_incomplete')}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -458,7 +458,6 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
variant="text"
|
variant="text"
|
||||||
onClick={activeStep === 0 ? onCancel : () => setActiveStep((step) => step - 1)}
|
onClick={activeStep === 0 ? onCancel : () => setActiveStep((step) => step - 1)}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{activeStep === 0 ? tc('cancel') : tc('back')}
|
{activeStep === 0 ? tc('cancel') : tc('back')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -469,16 +468,15 @@ const VariantBuilder: FunctionComponent<VariantBuilderProps> = ({ initial, onDon
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => setActiveStep(1)}
|
onClick={() => setActiveStep(1)}
|
||||||
disabled={categoryId == null}
|
disabled={categoryId == null}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('next')}
|
{t('next')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : activeStep === 1 ? (
|
) : activeStep === 1 ? (
|
||||||
<AppButton color="primary" variant="contained" onClick={goNextFromOptions} sx={{ m: 0 }}>
|
<AppButton color="primary" variant="contained" onClick={goNextFromOptions}>
|
||||||
{t('next')}
|
{t('next')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<AppButton color="primary" variant="contained" onClick={submit} disabled={submitting} sx={{ m: 0 }}>
|
<AppButton color="primary" variant="contained" onClick={submit} disabled={submitting}>
|
||||||
{submitting ? tc('saving') : t('submit_create')}
|
{submitting ? tc('saving') : t('submit_create')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ const StepRow: FunctionComponent<{ step: VerificationStep; highlighted: boolean
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="edit"
|
endIcon="edit"
|
||||||
to={`/${locale}${route}`}
|
to={`/${locale}${route}`}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{step.status === 'failed' || step.status === 'expired' ? t('row_fix') : t('row_go')}
|
{step.status === 'failed' || step.status === 'expired' ? t('row_fix') : t('row_go')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export default function CredentialsSubmitPage() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('credentials_needs_start')}
|
{t('credentials_needs_start')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton color="primary" variant="contained" to={`/${locale}${ROUTES.NURSE_VERIFICATION}`} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton color="primary" variant="contained" to={`/${locale}${ROUTES.NURSE_VERIFICATION}`} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{t('back_to_checklist')}
|
{t('back_to_checklist')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -168,7 +168,7 @@ export default function CredentialsSubmitPage() {
|
|||||||
label={t.has(`specialty_${preset}`) ? t(`specialty_${preset}`) : preset}
|
label={t.has(`specialty_${preset}`) ? t(`specialty_${preset}`) : preset}
|
||||||
onClick={() => toggleSpecialty(preset)}
|
onClick={() => toggleSpecialty(preset)}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
fontWeight: 500,
|
||||||
backgroundColor: selected ? 'var(--bal-primary)' : 'var(--bal-primary-soft)',
|
backgroundColor: selected ? 'var(--bal-primary)' : 'var(--bal-primary-soft)',
|
||||||
color: selected ? 'var(--bal-primary-contrast)' : 'var(--bal-primary)',
|
color: selected ? 'var(--bal-primary-contrast)' : 'var(--bal-primary)',
|
||||||
}}
|
}}
|
||||||
@@ -182,7 +182,7 @@ export default function CredentialsSubmitPage() {
|
|||||||
key={value}
|
key={value}
|
||||||
label={value}
|
label={value}
|
||||||
onDelete={() => toggleSpecialty(value)}
|
onDelete={() => toggleSpecialty(value)}
|
||||||
sx={{ fontWeight: 600, backgroundColor: 'var(--bal-primary)', color: 'var(--bal-primary-contrast)' }}
|
sx={{ fontWeight: 500, backgroundColor: 'var(--bal-primary)', color: 'var(--bal-primary-contrast)' }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -199,7 +199,7 @@ export default function CredentialsSubmitPage() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="add" onClick={addCustomSpecialty} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="add" onClick={addCustomSpecialty}>
|
||||||
{t('specialty_add')}
|
{t('specialty_add')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -253,11 +253,10 @@ export default function CredentialsSubmitPage() {
|
|||||||
startIcon="license"
|
startIcon="license"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={submitCredentials.isPending || !anyUploaded}
|
disabled={submitCredentials.isPending || !anyUploaded}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{submitCredentials.isPending ? t('credentials_submitting') : t('credentials_submit')}
|
{submitCredentials.isPending ? t('credentials_submitting') : t('credentials_submit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="primary" to={`/${locale}${ROUTES.NURSE_VERIFICATION}`} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}>
|
||||||
{t('back_to_checklist')}
|
{t('back_to_checklist')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -143,11 +143,10 @@ export default function IdentitySubmitPage() {
|
|||||||
startIcon="identity"
|
startIcon="identity"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{submitIdentity.isPending ? t('identity_submitting') : t('identity_submit')}
|
{submitIdentity.isPending ? t('identity_submitting') : t('identity_submit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="text" color="primary" to={`/${locale}${ROUTES.NURSE_VERIFICATION}`} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}>
|
||||||
{t('back_to_checklist')}
|
{t('back_to_checklist')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function NurseVerificationPage() {
|
|||||||
<AppAlert
|
<AppAlert
|
||||||
severity="error"
|
severity="error"
|
||||||
action={
|
action={
|
||||||
<AppButton variant="text" color="inherit" onClick={() => refetch()} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={() => refetch()}>
|
||||||
{t('retry')}
|
{t('retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ function Approved({ onPublish }: { onPublish: () => void }) {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon="publish"
|
startIcon="publish"
|
||||||
onClick={onPublish}
|
onClick={onPublish}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('approved_cta')}
|
{t('approved_cta')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -200,7 +200,7 @@ function ContinueCta({
|
|||||||
const route = nextActionRoute(status);
|
const route = nextActionRoute(status);
|
||||||
if (route == null) return null;
|
if (route == null) return null;
|
||||||
return (
|
return (
|
||||||
<AppButton color="primary" variant="contained" onClick={onContinue} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton color="primary" variant="contained" onClick={onContinue} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{index != null ? t('continue_step', { n: index }) : t('continue')}
|
{index != null ? t('continue_step', { n: index }) : t('continue')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
);
|
);
|
||||||
@@ -225,10 +225,10 @@ function MockAdminControls({ onApprove, onReject }: { onApprove: () => void; onR
|
|||||||
{t('mock_admin_title')}
|
{t('mock_admin_title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
||||||
<AppButton variant="outlined" color="primary" onClick={onApprove} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" onClick={onApprove}>
|
||||||
{t('mock_admin_approve')}
|
{t('mock_admin_approve')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton variant="outlined" color="error" onClick={onReject} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="error" onClick={onReject}>
|
||||||
{t('mock_admin_reject')}
|
{t('mock_admin_reject')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function UnderReviewPage() {
|
|||||||
{isApproved ? t('review_approved_body') : t('review_body')}
|
{isApproved ? t('review_approved_body') : t('review_body')}
|
||||||
</Typography>
|
</Typography>
|
||||||
{!isApproved ? (
|
{!isApproved ? (
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, color: 'var(--bal-warning)' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, color: 'var(--bal-warning)' }}>
|
||||||
{t('review_eta')}
|
{t('review_eta')}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -83,7 +83,7 @@ export default function UnderReviewPage() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon="verification"
|
startIcon="verification"
|
||||||
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('review_view_status')}
|
{t('review_view_status')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export default function NurseVisitNotesPanel({ bookingId }: { bookingId: number
|
|||||||
startIcon="notes"
|
startIcon="notes"
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
disabled={createNote.isPending || !note.trim()}
|
disabled={createNote.isPending || !note.trim()}
|
||||||
sx={{ m: 0, alignSelf: 'flex-end' }}
|
sx={{ alignSelf: 'flex-end' }}
|
||||||
>
|
>
|
||||||
{createNote.isPending ? tc('saving') : t('note_submit')}
|
{createNote.isPending ? tc('saving') : t('note_submit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ function TodayVisitCard({ item, evv }: { item: BookingSessionListItemDto; evv: R
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="bookings"
|
endIcon="bookings"
|
||||||
onClick={() => router.push(`/${locale}${ROUTES.NURSE_VISITS}/${item.bookingId}`)}
|
onClick={() => router.push(`/${locale}${ROUTES.NURSE_VISITS}/${item.bookingId}`)}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -117,11 +117,11 @@ function DetailRow({ label, value, ltr }: { label: string; value: string | null;
|
|||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
{ltr ? (
|
{ltr ? (
|
||||||
<Typography component="span" dir="ltr" variant="body2" sx={{ fontWeight: 600, fontFamily: 'monospace' }}>
|
<Typography component="span" dir="ltr" variant="body2" sx={{ fontWeight: 500, fontFamily: 'monospace' }}>
|
||||||
{value ?? '—'}
|
{value ?? '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{value ?? '—'}
|
{value ?? '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function PartnerSettlementPage() {
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('settlement_iban')}
|
{t('settlement_iban')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="span" dir="ltr" variant="body2" sx={{ fontWeight: 600, fontFamily: 'monospace' }}>
|
<Typography component="span" dir="ltr" variant="body2" sx={{ fontWeight: 500, fontFamily: 'monospace' }}>
|
||||||
{c.settlementIbanMasked ?? '—'}
|
{c.settlementIbanMasked ?? '—'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { Metadata, Viewport } from 'next';
|
import type { Metadata, Viewport } from 'next';
|
||||||
import localFont from 'next/font/local';
|
import localFont from 'next/font/local';
|
||||||
|
import { Space_Grotesk } from 'next/font/google';
|
||||||
import { setRequestLocale, getMessages } from 'next-intl/server';
|
import { setRequestLocale, getMessages } from 'next-intl/server';
|
||||||
import { NextIntlClientProvider } from 'next-intl';
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
import { getThemeMode } from '@/lib/cookies/server';
|
import { getThemeMode } from '@/lib/cookies/server';
|
||||||
@@ -47,8 +48,22 @@ const mikhak = localFont({
|
|||||||
preload: false,
|
preload: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// EN brand font — Space Grotesk. Self-hosted by next/font/google at build
|
||||||
|
// time (no runtime Google Fonts request). Same discipline as Mikhak:
|
||||||
|
// preload: false + conditional className below so it is only fetched on `en`.
|
||||||
|
const spaceGrotesk = Space_Grotesk({
|
||||||
|
subsets: ['latin'],
|
||||||
|
weight: ['400', '500', '700'],
|
||||||
|
display: 'swap',
|
||||||
|
variable: '--font-space-grotesk',
|
||||||
|
preload: false,
|
||||||
|
});
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
themeColor: BRAND.teal,
|
themeColor: [
|
||||||
|
{ media: '(prefers-color-scheme: light)', color: BRAND.teal },
|
||||||
|
{ media: '(prefers-color-scheme: dark)', color: BRAND.tealDeep },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@@ -77,9 +92,9 @@ export default async function LocaleLayout({
|
|||||||
const authState = await getServerAuthState();
|
const authState = await getServerAuthState();
|
||||||
const dir = getDirection(safeLocale);
|
const dir = getDirection(safeLocale);
|
||||||
|
|
||||||
// Only attach the Mikhak font variable on RTL (Persian) routes so the
|
// Each locale's brand font variable is attached only on its own routes —
|
||||||
// Persian typeface is not loaded for English pages.
|
// Mikhak never ships to /en, Space Grotesk never ships to /fa.
|
||||||
const fontClassName = safeLocale === 'fa' ? mikhak.variable : undefined;
|
const fontClassName = safeLocale === 'fa' ? mikhak.variable : spaceGrotesk.variable;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -4,14 +4,27 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
/*
|
||||||
body {
|
* AppLink is the only place link affordance (color, underline) is decided —
|
||||||
max-width: 100vw;
|
* this reset keeps a bare <a> (e.g. inside rich text) from inheriting the
|
||||||
overflow-x: hidden;
|
* browser's default blue/underline before AppLink's own styling applies.
|
||||||
max-height: 100vh;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: var(--bal-primary-soft);
|
||||||
|
color: var(--bal-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Directional icons (back/chevron_start, …) are authored for LTR and mirror
|
||||||
|
* under RTL. AppIcon sets `data-icon-directional` for every name registered
|
||||||
|
* in AppIcon/config.ts's DIRECTIONAL_ICONS — this is the only flip rule in
|
||||||
|
* the app; never hand-roll a per-component mirror.
|
||||||
|
*/
|
||||||
|
[dir='rtl'] [data-icon-directional] {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const BankStatusPanel: FunctionComponent<BankStatusPanelProps> = ({
|
|||||||
{ibanLabel}
|
{ibanLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
<Typography sx={{ fontWeight: 600, letterSpacing: 1 }} dir="ltr">
|
<Typography sx={{ fontWeight: 500, letterSpacing: 1 }} dir="ltr">
|
||||||
{ibanMasked}
|
{ibanMasked}
|
||||||
</Typography>
|
</Typography>
|
||||||
{bankName ? (
|
{bankName ? (
|
||||||
@@ -105,7 +105,7 @@ const BankStatusPanel: FunctionComponent<BankStatusPanelProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{status === 'mismatch' && onReenter && reenterLabel ? (
|
{status === 'mismatch' && onReenter && reenterLabel ? (
|
||||||
<AppButton color="primary" variant="outlined" startIcon="bank" onClick={onReenter} sx={{ m: 0, alignSelf: 'flex-start' }}>
|
<AppButton color="primary" variant="outlined" startIcon="bank" onClick={onReenter} sx={{ alignSelf: 'flex-start' }}>
|
||||||
{reenterLabel}
|
{reenterLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -96,14 +96,14 @@ const BookingRequestSummaryCard: FunctionComponent<BookingRequestSummaryCardProp
|
|||||||
|
|
||||||
<Stack sx={{ gap: 1.5 }}>
|
<Stack sx={{ gap: 1.5 }}>
|
||||||
<SummaryRow caption={t('summary_patient')}>
|
<SummaryRow caption={t('summary_patient')}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{patientName}
|
{patientName}
|
||||||
</Typography>
|
</Typography>
|
||||||
</SummaryRow>
|
</SummaryRow>
|
||||||
|
|
||||||
<SummaryRow caption={t('summary_service')}>
|
<SummaryRow caption={t('summary_service')}>
|
||||||
<Stack sx={{ gap: 0.25, alignItems: 'flex-end' }}>
|
<Stack sx={{ gap: 0.25, alignItems: 'flex-end' }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{variantLabel}
|
{variantLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
{variantPrice ? (
|
{variantPrice ? (
|
||||||
@@ -113,13 +113,13 @@ const BookingRequestSummaryCard: FunctionComponent<BookingRequestSummaryCardProp
|
|||||||
</SummaryRow>
|
</SummaryRow>
|
||||||
|
|
||||||
<SummaryRow caption={t('summary_address')}>
|
<SummaryRow caption={t('summary_address')}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, textAlign: 'end' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, textAlign: 'end' }}>
|
||||||
{addressLabel}
|
{addressLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
</SummaryRow>
|
</SummaryRow>
|
||||||
|
|
||||||
<SummaryRow caption={t('summary_when')}>
|
<SummaryRow caption={t('summary_when')}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, textAlign: 'end' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, textAlign: 'end' }}>
|
||||||
{whenLabel}
|
{whenLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
</SummaryRow>
|
</SummaryRow>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const CancellationPolicyDisclosure: FunctionComponent<CancellationPolicyDisclosu
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t(`lead_${preview.leadTimeLabel}`)}
|
{t(`lead_${preview.leadTimeLabel}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ color: 'var(--bal-secondary)', fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ color: 'var(--bal-secondary)', fontWeight: 500 }}>
|
||||||
{t('fee_percent', { percent: feePercent })}
|
{t('fee_percent', { percent: feePercent })}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const CountdownTimer: FunctionComponent<CountdownTimerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Stack direction="row" sx={{ gap: 0.75, alignItems: 'center', color: 'text.secondary' }}>
|
<Stack direction="row" sx={{ gap: 0.75, alignItems: 'center', color: 'text.secondary' }}>
|
||||||
<AppIcon icon="pending" size={18} color="var(--bal-text-secondary)" />
|
<AppIcon icon="pending" size={18} color="var(--bal-text-secondary)" />
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{elapsedText}
|
{elapsedText}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -92,7 +92,7 @@ const CountdownTimer: FunctionComponent<CountdownTimerProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Stack sx={{ gap: 0.25, alignItems: 'center' }}>
|
<Stack sx={{ gap: 0.25, alignItems: 'center' }}>
|
||||||
{label ? (
|
{label ? (
|
||||||
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 600 }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ const DocumentUpload: FunctionComponent<DocumentUploadProps> = ({
|
|||||||
startIcon="upload"
|
startIcon="upload"
|
||||||
onClick={openPicker}
|
onClick={openPicker}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('upload_reupload')}
|
{t('upload_reupload')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -194,7 +194,7 @@ const DocumentUpload: FunctionComponent<DocumentUploadProps> = ({
|
|||||||
<Paper elevation={0} data-upload-state="uploading" sx={uploadedSx}>
|
<Paper elevation={0} data-upload-state="uploading" sx={uploadedSx}>
|
||||||
<Stack direction="row" sx={{ gap: 1, alignItems: 'center' }}>
|
<Stack direction="row" sx={{ gap: 1, alignItems: 'center' }}>
|
||||||
<AppIcon icon="upload" size={20} color="var(--bal-primary)" />
|
<AppIcon icon="upload" size={20} color="var(--bal-primary)" />
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, flexGrow: 1, wordBreak: 'break-all' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, flexGrow: 1, wordBreak: 'break-all' }}>
|
||||||
{fileName}
|
{fileName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
||||||
@@ -220,7 +220,7 @@ const DocumentUpload: FunctionComponent<DocumentUploadProps> = ({
|
|||||||
<AppIcon icon="document" size={28} color="var(--bal-primary)" />
|
<AppIcon icon="document" size={28} color="var(--bal-primary)" />
|
||||||
)}
|
)}
|
||||||
<Stack sx={{ gap: 0.25, flexGrow: 1, minWidth: 0 }}>
|
<Stack sx={{ gap: 0.25, flexGrow: 1, minWidth: 0 }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, wordBreak: 'break-all' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, wordBreak: 'break-all' }}>
|
||||||
{uploadedName}
|
{uploadedName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" sx={{ gap: 0.5, alignItems: 'center' }}>
|
<Stack direction="row" sx={{ gap: 0.5, alignItems: 'center' }}>
|
||||||
@@ -230,7 +230,7 @@ const DocumentUpload: FunctionComponent<DocumentUploadProps> = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<AppButton variant="text" color="primary" onClick={openPicker} disabled={disabled} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={openPicker} disabled={disabled}>
|
||||||
{t('upload_change')}
|
{t('upload_change')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -253,7 +253,7 @@ const DocumentUpload: FunctionComponent<DocumentUploadProps> = ({
|
|||||||
startIcon="refresh"
|
startIcon="refresh"
|
||||||
onClick={openPicker}
|
onClick={openPicker}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
sx={{ m: 0, alignSelf: 'flex-start' }}
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
{t('upload_retry')}
|
{t('upload_retry')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -287,7 +287,7 @@ const DocumentUpload: FunctionComponent<DocumentUploadProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppIcon icon={capture ? 'camera' : 'upload'} size={28} color="var(--bal-primary)" />
|
<AppIcon icon={capture ? 'camera' : 'upload'} size={28} color="var(--bal-primary)" />
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 700 }}>
|
||||||
{capture ? t('upload_capture') : t('upload_choose')}
|
{capture ? t('upload_capture') : t('upload_choose')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const EarningsBalanceHeader: FunctionComponent<EarningsBalanceHeaderProps> = ({
|
|||||||
<Stack sx={{ gap: 0.75 }}>
|
<Stack sx={{ gap: 0.75 }}>
|
||||||
<Stack direction="row" sx={{ gap: 1, alignItems: 'center' }}>
|
<Stack direction="row" sx={{ gap: 1, alignItems: 'center' }}>
|
||||||
<AppIcon icon={isOwed ? 'warning' : 'wallet'} size={20} color={accent} />
|
<AppIcon icon={isOwed ? 'warning' : 'wallet'} size={20} color={accent} />
|
||||||
<Typography variant="subtitle2" sx={{ color: 'text.secondary', fontWeight: 600 }}>
|
<Typography variant="subtitle2" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
||||||
{isOwed ? t('balance_owed_label') : t('balance_net_label')}
|
{isOwed ? t('balance_owed_label') : t('balance_net_label')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -67,7 +67,7 @@ const EarningsBalanceHeader: FunctionComponent<EarningsBalanceHeaderProps> = ({
|
|||||||
<Typography component="span" sx={{ fontWeight: 800, fontSize: '2rem', color: accent }}>
|
<Typography component="span" sx={{ fontWeight: 800, fontSize: '2rem', color: accent }}>
|
||||||
{formatIrrToToman(magnitude, locale)}
|
{formatIrrToToman(magnitude, locale)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="span" variant="subtitle1" sx={{ color: 'text.secondary', fontWeight: 600 }}>
|
<Typography component="span" variant="subtitle1" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
||||||
{tc('currency_toman')}
|
{tc('currency_toman')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -101,7 +101,7 @@ const EarningsBalanceHeader: FunctionComponent<EarningsBalanceHeaderProps> = ({
|
|||||||
<Stack sx={{ gap: 0.75 }}>
|
<Stack sx={{ gap: 0.75 }}>
|
||||||
<Stack direction="row" sx={{ gap: 0.75, alignItems: 'center' }}>
|
<Stack direction="row" sx={{ gap: 0.75, alignItems: 'center' }}>
|
||||||
<AppIcon icon={bucket.icon} size={16} color={bucket.token} />
|
<AppIcon icon={bucket.icon} size={16} color={bucket.token} />
|
||||||
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 600 }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
||||||
{t(`bucket_${bucket.key}`)}
|
{t(`bucket_${bucket.key}`)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ const EarningsRow: FunctionComponent<EarningsRowProps> = ({ item, onViewBooking,
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="visits"
|
endIcon="visits"
|
||||||
onClick={() => onViewBooking(item.bookingId)}
|
onClick={() => onViewBooking(item.bookingId)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('view_booking')}
|
{t('view_booking')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -178,7 +177,6 @@ const StateAffordance: FunctionComponent<{
|
|||||||
color="primary"
|
color="primary"
|
||||||
endIcon="wallet"
|
endIcon="wallet"
|
||||||
onClick={() => onViewPayout(item.nursePayoutId as number)}
|
onClick={() => onViewPayout(item.nursePayoutId as number)}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{t('view_payout')}
|
{t('view_payout')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const GenderToggle: FunctionComponent<GenderToggleProps> = ({
|
|||||||
'& .MuiToggleButton-root': {
|
'& .MuiToggleButton-root': {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
py: 1.25,
|
py: 1.25,
|
||||||
fontWeight: 600,
|
fontWeight: 700,
|
||||||
borderColor: error ? 'var(--bal-error)' : undefined,
|
borderColor: error ? 'var(--bal-error)' : undefined,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const PatientForm: FunctionComponent<PatientFormProps> = ({
|
|||||||
|
|
||||||
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
<Stack direction="row" sx={{ gap: 1, justifyContent: 'flex-end' }}>
|
||||||
{onCancel ? (
|
{onCancel ? (
|
||||||
<AppButton variant="text" onClick={onCancel} disabled={submitting} sx={{ m: 0 }}>
|
<AppButton variant="text" onClick={onCancel} disabled={submitting}>
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -170,7 +170,6 @@ const PatientForm: FunctionComponent<PatientFormProps> = ({
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{submitLabel}
|
{submitLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const PatientHeader: FunctionComponent<PatientHeaderProps> = ({
|
|||||||
{displayName}
|
{displayName}
|
||||||
</Typography>
|
</Typography>
|
||||||
{relationLabel ? (
|
{relationLabel ? (
|
||||||
<Chip size="small" label={relationLabel} sx={{ bgcolor: 'var(--bal-primary-soft)', fontWeight: 600 }} />
|
<Chip size="small" label={relationLabel} sx={{ bgcolor: 'var(--bal-primary-soft)', fontWeight: 500 }} />
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const PayoutHistoryRow: FunctionComponent<PayoutHistoryRowProps> = ({ item, onOp
|
|||||||
sx={{ justifyContent: 'space-between', alignItems: 'flex-start', gap: 1.5, flexWrap: 'wrap' }}
|
sx={{ justifyContent: 'space-between', alignItems: 'flex-start', gap: 1.5, flexWrap: 'wrap' }}
|
||||||
>
|
>
|
||||||
<Stack sx={{ gap: 0.25, minWidth: 0 }}>
|
<Stack sx={{ gap: 0.25, minWidth: 0 }}>
|
||||||
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 600 }}>
|
<Typography variant="caption" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
||||||
{t('payout_net_amount')}
|
{t('payout_net_amount')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="subtitle1" sx={{ fontWeight: 800 }}>
|
<Typography variant="subtitle1" sx={{ fontWeight: 800 }}>
|
||||||
@@ -110,7 +110,7 @@ const PayoutHistoryRow: FunctionComponent<PayoutHistoryRowProps> = ({ item, onOp
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Stack direction="row" sx={{ justifyContent: 'flex-end' }}>
|
<Stack direction="row" sx={{ justifyContent: 'flex-end' }}>
|
||||||
<AppButton variant="text" color="primary" endIcon="wallet" onClick={() => onOpen(item.id)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" endIcon="wallet" onClick={() => onOpen(item.id)}>
|
||||||
{t('view_payout_detail')}
|
{t('view_payout_detail')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -124,7 +124,7 @@ const MetaLine: FunctionComponent<{ label: string; children: ReactNode }> = ({ l
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{label}
|
{label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{children}
|
{children}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const PriceBreakdown: FunctionComponent<PriceBreakdownProps> = ({ rows, totalLab
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{row.label}
|
{row.label}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
{formatIrrToToman(row.amountIrr, locale)}
|
{formatIrrToToman(row.amountIrr, locale)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ const PriceDisplay: FunctionComponent<PriceDisplayProps> = ({
|
|||||||
<Stack sx={{ gap: 0.25, alignItems: align === 'center' ? 'center' : 'flex-start' }}>
|
<Stack sx={{ gap: 0.25, alignItems: align === 'center' ? 'center' : 'flex-start' }}>
|
||||||
<Typography component="p" sx={{ fontWeight: 700 }}>
|
<Typography component="p" sx={{ fontWeight: 700 }}>
|
||||||
{amount} {tc('currency_toman')}{' '}
|
{amount} {tc('currency_toman')}{' '}
|
||||||
<Typography component="span" variant="body2" sx={{ color: 'text.secondary', fontWeight: 600 }}>
|
<Typography component="span" variant="body2" sx={{ color: 'text.secondary', fontWeight: 500 }}>
|
||||||
{unitLabel}
|
{unitLabel}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Typography>
|
</Typography>
|
||||||
{hasEstimate ? (
|
{hasEstimate ? (
|
||||||
<Typography variant="caption" sx={{ color: 'var(--bal-primary)', fontWeight: 600 }}>
|
<Typography variant="caption" sx={{ color: 'var(--bal-primary)', fontWeight: 500 }}>
|
||||||
{t('estimate_label')}: {total} {tc('currency_toman')} ·{' '}
|
{t('estimate_label')}: {total} {tc('currency_toman')} ·{' '}
|
||||||
{t('estimate_for', { count: countLabel, unit: t(`count_${priceUnit}`) })}
|
{t('estimate_for', { count: countLabel, unit: t(`count_${priceUnit}`) })}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const RefundEtaBanner: FunctionComponent<RefundEtaBannerProps> = ({ channel, eta
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">{t(copy.bodyKey)}</Typography>
|
<Typography variant="body2">{t(copy.bodyKey)}</Typography>
|
||||||
{channel === 'bnpl_revert' && (
|
{channel === 'bnpl_revert' && (
|
||||||
<Typography variant="caption" sx={{ fontWeight: 600 }} data-testid="refund-eta-window">
|
<Typography variant="caption" sx={{ fontWeight: 500 }} data-testid="refund-eta-window">
|
||||||
{t('eta_business_days')}
|
{t('eta_business_days')}
|
||||||
{eta ? ` · ${t('eta_expected_label', { date: formatShamsiDate(eta, locale) })}` : ''}
|
{eta ? ` · ${t('eta_expected_label', { date: formatShamsiDate(eta, locale) })}` : ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const RefundStatusCard: FunctionComponent<RefundStatusCardProps> = ({ refund })
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">{t('failed_body')}</Typography>
|
<Typography variant="body2">{t('failed_body')}</Typography>
|
||||||
{refund.externalRevertReference && (
|
{refund.externalRevertReference && (
|
||||||
<Typography variant="caption" sx={{ fontWeight: 600, direction: 'ltr' }}>
|
<Typography variant="caption" sx={{ fontWeight: 500, direction: 'ltr' }}>
|
||||||
{t('reference_label')}: {refund.externalRevertReference}
|
{t('reference_label')}: {refund.externalRevertReference}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
@@ -97,7 +97,7 @@ const RefundStatusCard: FunctionComponent<RefundStatusCardProps> = ({ refund })
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{t('reference_label')}
|
{t('reference_label')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, direction: 'ltr' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, direction: 'ltr' }}>
|
||||||
{refund.externalRevertReference}
|
{refund.externalRevertReference}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const RelationSelect: FunctionComponent<RelationSelectProps> = ({ options, value
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{option.icon ? <AppIcon icon={option.icon} size={26} color="var(--bal-primary)" /> : null}
|
{option.icon ? <AppIcon icon={option.icon} size={26} color="var(--bal-primary)" /> : null}
|
||||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const ServicePriceRow: FunctionComponent<ServicePriceRowProps> = ({
|
|||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, flexGrow: 1 }}>
|
<Typography variant="body1" sx={{ fontWeight: 500, flexGrow: 1 }}>
|
||||||
{displayName}
|
{displayName}
|
||||||
</Typography>
|
</Typography>
|
||||||
<PriceDisplay price={priceIrr} priceUnit={priceUnit} sessionCount={sessionCount} align="start" />
|
<PriceDisplay price={priceIrr} priceUnit={priceUnit} sessionCount={sessionCount} align="start" />
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const StatusChip: FunctionComponent<StatusChipProps> = ({ status, label, size =
|
|||||||
size={size}
|
size={size}
|
||||||
label={label}
|
label={label}
|
||||||
icon={<AppIcon icon={style.icon} size={16} color={style.fg} />}
|
icon={<AppIcon icon={style.icon} size={16} color={style.fg} />}
|
||||||
sx={{ backgroundColor: style.bg, color: style.fg, fontWeight: 600, ...sx }}
|
sx={{ backgroundColor: style.bg, color: style.fg, fontWeight: 500, ...sx }}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const VariantCard: FunctionComponent<VariantCardProps> = ({ variant, onEdit, onT
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="edit" onClick={onEdit} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="edit" onClick={onEdit}>
|
||||||
{t('edit')}
|
{t('edit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
@@ -84,7 +84,6 @@ const VariantCard: FunctionComponent<VariantCardProps> = ({ variant, onEdit, onT
|
|||||||
color={active ? 'inherit' : 'primary'}
|
color={active ? 'inherit' : 'primary'}
|
||||||
startIcon={active ? 'visibilityoff' : 'visibilityon'}
|
startIcon={active ? 'visibilityoff' : 'visibilityon'}
|
||||||
onClick={onToggleActive}
|
onClick={onToggleActive}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{active ? t('deactivate') : t('activate')}
|
{active ? t('deactivate') : t('activate')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const AdminErrorState: FunctionComponent<AdminErrorStateProps> = ({ message, ret
|
|||||||
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 1.5 }}>
|
||||||
{message}
|
{message}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={onRetry} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={onRetry}>
|
||||||
{retryLabel}
|
{retryLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ const AdminPager: FunctionComponent<AdminPagerProps> = ({
|
|||||||
if (pageCount <= 1) return null;
|
if (pageCount <= 1) return null;
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', justifyContent: 'center', mt: 1 }}>
|
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', justifyContent: 'center', mt: 1 }}>
|
||||||
<AppButton variant="text" color="primary" onClick={onPrev} disabled={page <= 1} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onPrev} disabled={page <= 1}>
|
||||||
{prevLabel}
|
{prevLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||||
{indicator}
|
{indicator}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="text" color="primary" onClick={onNext} disabled={page >= pageCount} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" onClick={onNext} disabled={page >= pageCount}>
|
||||||
{nextLabel}
|
{nextLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const AuditLogRow: FunctionComponent<AuditLogRowProps> = ({ entry }) => {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{fields.map(([field, delta]) => (
|
{fields.map(([field, delta]) => (
|
||||||
<TableRow key={field}>
|
<TableRow key={field}>
|
||||||
<TableCell sx={{ fontWeight: 600 }}>{field}</TableCell>
|
<TableCell sx={{ fontWeight: 500 }}>{field}</TableCell>
|
||||||
<TableCell sx={{ color: 'text.secondary' }}>{displayValue(delta.old)}</TableCell>
|
<TableCell sx={{ color: 'text.secondary' }}>{displayValue(delta.old)}</TableCell>
|
||||||
<TableCell>{displayValue(delta.new)}</TableCell>
|
<TableCell>{displayValue(delta.new)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ const ConfigRow: FunctionComponent<ConfigRowProps> = ({ config, canEdit = false,
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
||||||
<AppButton variant="text" color="primary" startIcon="history" onClick={() => onHistory?.(config)} sx={{ m: 0 }}>
|
<AppButton variant="text" color="primary" startIcon="history" onClick={() => onHistory?.(config)}>
|
||||||
{t('cfg_history')}
|
{t('cfg_history')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
{canEdit ? (
|
{canEdit ? (
|
||||||
<AppButton variant="outlined" color="primary" startIcon="edit" onClick={() => onEdit?.(config)} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="edit" onClick={() => onEdit?.(config)}>
|
||||||
{t('cfg_edit')}
|
{t('cfg_edit')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const ConfirmDialog: FunctionComponent<ConfirmDialogProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||||
<AppButton variant="text" color="inherit" onClick={close} disabled={loading} sx={{ m: 0 }}>
|
<AppButton variant="text" color="inherit" onClick={close} disabled={loading}>
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
<AppButton
|
<AppButton
|
||||||
@@ -102,7 +102,6 @@ const ConfirmDialog: FunctionComponent<ConfirmDialogProps> = ({
|
|||||||
onClick={confirm}
|
onClick={confirm}
|
||||||
disabled={confirmDisabled}
|
disabled={confirmDisabled}
|
||||||
startIcon={loading ? <CircularProgress size={16} color="inherit" /> : undefined}
|
startIcon={loading ? <CircularProgress size={16} color="inherit" /> : undefined}
|
||||||
sx={{ m: 0 }}
|
|
||||||
>
|
>
|
||||||
{confirmLabel}
|
{confirmLabel}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const DocumentViewer: FunctionComponent<DocumentViewerProps> = ({ document }) =>
|
|||||||
>
|
>
|
||||||
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', mb: 1 }}>
|
<Stack direction="row" sx={{ gap: 1, alignItems: 'center', mb: 1 }}>
|
||||||
<AppIcon icon="document" size={18} color="var(--bal-text-secondary)" />
|
<AppIcon icon="document" size={18} color="var(--bal-text-secondary)" />
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, flexGrow: 1, wordBreak: 'break-all' }}>
|
<Typography variant="body2" sx={{ fontWeight: 500, flexGrow: 1, wordBreak: 'break-all' }}>
|
||||||
{t('doc_file_meta', { name: document.originalFileName ?? `#${document.id}`, size: formatSize(document.fileSizeBytes) })}
|
{t('doc_file_meta', { name: document.originalFileName ?? `#${document.id}`, size: formatSize(document.fileSizeBytes) })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton
|
<AppButton
|
||||||
@@ -47,7 +47,7 @@ const DocumentViewer: FunctionComponent<DocumentViewerProps> = ({ document }) =>
|
|||||||
startIcon="refresh"
|
startIcon="refresh"
|
||||||
onClick={() => signed.refetch()}
|
onClick={() => signed.refetch()}
|
||||||
disabled={signed.isFetching}
|
disabled={signed.isFetching}
|
||||||
sx={{ m: 0, minWidth: 0 }}
|
sx={{ minWidth: 0 }}
|
||||||
>
|
>
|
||||||
{t('doc_reload')}
|
{t('doc_reload')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
@@ -60,7 +60,7 @@ const DocumentViewer: FunctionComponent<DocumentViewerProps> = ({ document }) =>
|
|||||||
<Typography variant="body2" sx={{ color: 'var(--bal-error)' }}>
|
<Typography variant="body2" sx={{ color: 'var(--bal-error)' }}>
|
||||||
{t('doc_error')}
|
{t('doc_error')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={() => signed.refetch()} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="refresh" onClick={() => signed.refetch()}>
|
||||||
{t('doc_reload')}
|
{t('doc_reload')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -74,7 +74,7 @@ const DocumentViewer: FunctionComponent<DocumentViewerProps> = ({ document }) =>
|
|||||||
sx={{ maxWidth: '100%', maxHeight: 320, borderRadius: 1, display: 'block' }}
|
sx={{ maxWidth: '100%', maxHeight: 320, borderRadius: 1, display: 'block' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<AppButton variant="outlined" color="primary" endIcon="external" href={signed.data.url} openInNewTab sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" endIcon="external" href={signed.data.url} openInNewTab>
|
||||||
{t('doc_open_new')}
|
{t('doc_open_new')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -74,11 +74,11 @@ const PartnerSettlementRow: FunctionComponent<PartnerSettlementRowProps> = ({ in
|
|||||||
/>
|
/>
|
||||||
{invoice.pdfUrl ? (
|
{invoice.pdfUrl ? (
|
||||||
downloadError ? (
|
downloadError ? (
|
||||||
<AppButton variant="outlined" color="error" startIcon="refresh" onClick={() => onRetryDownload?.(invoice)} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="error" startIcon="refresh" onClick={() => onRetryDownload?.(invoice)}>
|
||||||
{t('invoice_pdf_error')}
|
{t('invoice_pdf_error')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
) : (
|
) : (
|
||||||
<AppButton variant="outlined" color="primary" startIcon="download" onClick={() => onDownloadPdf?.(invoice)} sx={{ m: 0 }}>
|
<AppButton variant="outlined" color="primary" startIcon="download" onClick={() => onDownloadPdf?.(invoice)}>
|
||||||
{t('invoice_download')}
|
{t('invoice_download')}
|
||||||
</AppButton>
|
</AppButton>
|
||||||
)
|
)
|
||||||
|
|||||||