another step

This commit is contained in:
hamid
2026-06-23 23:36:19 +03:30
parent 3fd147cf80
commit be07c703ec
27 changed files with 3682 additions and 194 deletions
+16 -2
View File
@@ -65,8 +65,22 @@ The two communicate over **HTTP/JSON** (optionally gRPC). The client reads the A
intentionally removed. Don't add them back. intentionally removed. Don't add them back.
6. **Never commit secrets.** Use `.env` (client) and `appsettings.*.json` / user-secrets (server). 6. **Never commit secrets.** Use `.env` (client) and `appsettings.*.json` / user-secrets (server).
Real connection strings, keys, and tokens never enter git. Real connection strings, keys, and tokens never enter git.
7. **Keep docs honest.** If you change how something works, update the `CLAUDE.md` that describes it 7. **Keep docs honest, and keep the architecture map current.** If you change how something works,
in the same change. Stale instructions are worse than none. update the `CLAUDE.md` that describes it in the same change. Each level documents its architecture
in one canonical place — **this file's "Repository layout"** (repo), **client/CLAUDE.md "Project
Structure"** (frontend), **server/CLAUDE.md "Project map"** (backend). When a change alters that
structure — adds, removes, or renames a project, layer, route group, provider, or major folder, or
changes a cross-project / cross-layer boundary — update the matching architecture section in the
same change. Stale instructions are worse than none.
8. **Write clean, self-documenting code.**
- **No dead code.** Remove unused variables, imports/usings, parameters, and private members —
don't leave them behind and don't suppress the warning. The client enforces this with ESLint
(`@typescript-eslint/no-unused-vars` as an *error*); on the server they are build warnings and
the gate is zero new warnings. Per-project specifics live in each project's `CLAUDE.md` /
`CONVENTIONS.md`.
- **Comment the *why*, not the *what*.** Don't write verbose comments that restate what the code
already says. Add a comment only where a non-obvious decision, constraint, business rule, or
trade-off isn't evident from the code itself. Prefer a clearer name over a comment.
--- ---
+65 -13
View File
@@ -20,8 +20,8 @@ i18n, cookies, and the rules every change must follow.
- **MUI v9** (`@mui/material`) for components and theming; **Emotion** underneath (RTL via - **MUI v9** (`@mui/material`) for components and theming; **Emotion** underneath (RTL via
`stylis-plugin-rtl`). `stylis-plugin-rtl`).
- **next-intl v4** for i18n — locales `fa` (default, RTL) and `en`. - **next-intl v4** for i18n — locales `fa` (default, RTL) and `en`.
- **TanStack Query v5** for server state; a small **AppStore** (React context + reducer, `src/store/`) - **TanStack Query v5** for server state; a small **AuthContext** (React context + reducer,
for client state. `src/context/auth/`, seeded with server-read auth state) for auth/session state.
- **notistack** for toasts; **js-cookie** (wrapped) for client cookies. - **notistack** for toasts; **js-cookie** (wrapped) for client cookies.
- **Jest** + **Testing Library** for unit tests. - **Jest** + **Testing Library** for unit tests.
- Quality gates: **tsc**, **ESLint 9** (flat config), **Prettier**. - Quality gates: **tsc**, **ESLint 9** (flat config), **Prettier**.
@@ -58,6 +58,10 @@ Rules for this project:
- **This project is flat-config only.** Do not add `.eslintrc*` files — put any rule changes in - **This project is flat-config only.** Do not add `.eslintrc*` files — put any rule changes in
`eslint.config.mjs`. `eslint.config.mjs`.
- **ESLint owns correctness, Prettier owns formatting.** Don't add stylistic ESLint rules. - **ESLint owns correctness, Prettier owns formatting.** Don't add stylistic ESLint rules.
- **No unused variables or imports.** `@typescript-eslint/no-unused-vars` is raised from
eslint-config-next's default `warn` to **`error`** (in `eslint.config.mjs`), so dead code fails
`npm run check`. Delete unused code rather than disabling the rule; prefix a deliberately-unused
binding with `_` (e.g. `_event`, `catch (_err)`) to opt out.
- **Prefer fixing code over silencing the linter.** When a disable is genuinely correct — e.g. a - **Prefer fixing code over silencing the linter.** When a disable is genuinely correct — e.g. a
deliberate browser-only read after mount that trips `react-hooks/set-state-in-effect` — use a deliberate browser-only read after mount that trips `react-hooks/set-state-in-effect` — use a
scoped `// eslint-disable-next-line <rule>` with a one-line reason, never a file-wide disable. scoped `// eslint-disable-next-line <rule>` with a one-line reason, never a file-wide disable.
@@ -86,9 +90,16 @@ A change is "done" only if it respects all of these — each has a full section
8. **Shared components get a co-located `*.test.tsx`.** (A component imported from >1 place.) 8. **Shared components get a co-located `*.test.tsx`.** (A component imported from >1 place.)
9. **Magic strings become named constants** (`src/constants/` or a co-located `constants.ts`). 9. **Magic strings become named constants** (`src/constants/` or a co-located `constants.ts`).
10. **`npm run check` is green** and translations stay in sync before you finish. 10. **`npm run check` is green** and translations stay in sync before you finish.
11. **No dead code; comment the *why*, not the *what*.** Unused vars/imports are lint errors — remove
them. Don't add comments that restate the code; comment only a non-obvious decision, constraint, or
trade-off. See **Comments & dead code** below.
## Project Structure ## Project Structure
**This section is the canonical description of the client's architecture.** When a change adds, removes,
or renames a route group, provider, or top-level `src/` folder, update this tree in the same change
(root `CLAUDE.md` working agreement #7).
``` ```
client/ client/
├── messages/ # Translation files (add keys to BOTH files) ├── messages/ # Translation files (add keys to BOTH files)
@@ -101,7 +112,7 @@ client/
│ ├── globals.css │ ├── globals.css
│ ├── fonts/ # Local font files (woff2) — Mikhak for fa │ ├── fonts/ # Local font files (woff2) — Mikhak for fa
│ └── [locale]/ │ └── [locale]/
│ ├── layout.tsx # ROOT RSC: renders <html lang/dir> + fonts + setRequestLocale + NextIntlClientProvider + ThemeProvider + AppStoreProvider │ ├── layout.tsx # ROOT RSC: renders <html lang/dir> + fonts + setRequestLocale + NextIntlClientProvider + ThemeProvider + AuthProvider (seeded via getServerAuthState)
│ ├── (private-routes)/ │ ├── (private-routes)/
│ │ ├── layout.tsx # 'use client' — wraps PrivateLayout │ │ ├── layout.tsx # 'use client' — wraps PrivateLayout
│ │ └── page.tsx │ │ └── page.tsx
@@ -129,6 +140,9 @@ client/
│ │ ├── client.ts # clientFetch<T> — throws ApiError on error; use in hooks/client components │ │ ├── client.ts # clientFetch<T> — throws ApiError on error; use in hooks/client components
│ │ ├── server.ts # serverFetch<T> — throws ApiError on error; use in RSCs/Server Actions │ │ ├── server.ts # serverFetch<T> — throws ApiError on error; use in RSCs/Server Actions
│ │ └── errors.ts # ApiError class (status, message, code) │ │ └── errors.ts # ApiError class (status, message, code)
│ ├── auth/
│ │ ├── token.ts # decodeJwtPayload / isTokenAlive — edge-safe, shared with middleware (no next/headers)
│ │ └── server.ts # getServerAuthState — access-token cookie → AuthState for AuthProvider
│ ├── query/ │ ├── query/
│ │ ├── queryClient.ts # makeQueryClient factory + getQueryClient() SSR-safe singleton │ │ ├── queryClient.ts # makeQueryClient factory + getQueryClient() SSR-safe singleton
│ │ └── QueryProvider.tsx # 'use client' — QueryClientProvider + ReactQueryDevtools │ │ └── QueryProvider.tsx # 'use client' — QueryClientProvider + ReactQueryDevtools
@@ -146,7 +160,8 @@ client/
│ │ └── serverApi.ts # Namespace object wrapping serverFetch calls (only when needed) │ │ └── serverApi.ts # Namespace object wrapping serverFetch calls (only when needed)
│ └── hooks/ │ └── hooks/
│ └── use{Action}.ts # One hook per file — useQuery or useMutation │ └── use{Action}.ts # One hook per file — useQuery or useMutation
├── store/ # AppStore (Redux-like client state) ├── context/ # React context providers
│ └── auth/ # AuthContext — AuthProvider (server-seeded) + reducer + useAuth
├── theme/ ├── theme/
│ ├── ThemeProvider.tsx # MuiThemeProvider wrapper + ColorSchemeScript + ColorSchemeCookieSync │ ├── ThemeProvider.tsx # MuiThemeProvider wrapper + ColorSchemeScript + ColorSchemeCookieSync
│ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE │ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE
@@ -174,7 +189,7 @@ client/
- Loads the Mikhak font and attaches its CSS-variable class to `<html>` **only for `fa`** (see Fonts). - Loads the Mikhak font and attaches its CSS-variable class to `<html>` **only for `fa`** (see Fonts).
- Calls `setRequestLocale(locale)` so server components deeper in the tree can call `getLocale()` / `getTranslations()` reliably. - Calls `setRequestLocale(locale)` so server components deeper in the tree can call `getLocale()` / `getTranslations()` reliably.
- Calls `getMessages({ locale })` with the locale passed **explicitly** so `getRequestConfig` receives it via `Promise.resolve(locale)` (not through the React.cache read), avoiding any cache-ordering race. - Calls `getMessages({ locale })` with the locale passed **explicitly** so `getRequestConfig` receives it via `Promise.resolve(locale)` (not through the React.cache read), avoiding any cache-ordering race.
- Wraps children with `NextIntlClientProvider`, `AppStoreProvider`, and `ThemeProvider`. - Wraps children with `NextIntlClientProvider`, `AuthProvider` (seeded with server-read auth state), and `ThemeProvider`.
- Exports `generateStaticParams` so Next.js can enumerate locale routes at build time. - Exports `generateStaticParams` so Next.js can enumerate locale routes at build time.
**WHY `<html>` MUST live in `[locale]/layout.tsx` and not a layout above it**: a layout above the `[locale]` segment is *shared* between `/fa` and `/en`. Next.js statically caches it at build time with `defaultLocale` ('fa') and never re-renders it on a client-side locale switch (the segment doesn't change). Its `lang`/`dir`/messages therefore freeze on 'fa'/'rtl' for every route, including `/en`. The `[locale]` layout is the lowest boundary keyed on the locale param, so it is the only place where `<html lang dir>` reliably tracks the active locale. **WHY `<html>` MUST live in `[locale]/layout.tsx` and not a layout above it**: a layout above the `[locale]` segment is *shared* between `/fa` and `/en`. Next.js statically caches it at build time with `defaultLocale` ('fa') and never re-renders it on a client-side locale switch (the segment doesn't change). Its `lang`/`dir`/messages therefore freeze on 'fa'/'rtl' for every route, including `/en`. The `[locale]` layout is the lowest boundary keyed on the locale param, so it is the only place where `<html lang dir>` reliably tracks the active locale.
@@ -370,6 +385,21 @@ Enforcement: before removing or renaming a shared component, check whether `src/
--- ---
## Comments & dead code
- **No dead code.** Unused variables, imports, parameters, and private members are lint errors
(`@typescript-eslint/no-unused-vars`, raised to `error` — see *Quality gates*). Delete them; don't
comment them out and don't silence the rule. Prefix a deliberately-unused binding with `_` to opt out.
- **Comment the *why*, never the *what*.** Code should read for itself — a comment that restates what
the code already says is noise. Don't write `// set the access token` above `setClientCookie(...)`, or
JSDoc that just echoes a function's name.
- **Do** add a tight comment when a decision is genuinely non-obvious from the code: a workaround for a
framework quirk, a business rule, an ordering or security constraint, a deliberate deviation. Explain
*why it is this way*. The comments in `src/app/[locale]/layout.tsx` (why `<html>` lives in the
`[locale]` layout) and `src/lib/auth/token.ts` (why the JWT `exp` check is UX-only, never a security
boundary) are the model to follow.
- Prefer a clearer name or a small helper over a comment whenever that removes the need for it.
## Anti-patterns (do not do these) ## Anti-patterns (do not do these)
- **Do not** read `localStorage` or `document.cookie` in render functions — use `useEffect` or server-side `cookies()` from `next/headers`. - **Do not** read `localStorage` or `document.cookie` in render functions — use `useEffect` or server-side `cookies()` from `next/headers`.
@@ -423,24 +453,46 @@ Central fetch primitives live in `src/lib/api/`:
--- ---
## Auth Cookies ## Auth Cookies & session state
| Cookie | Constant | TTL | Set by | | Cookie | Constant | TTL | Set by |
|--------|----------|-----|--------| |--------|----------|-----|--------|
| `access_token` | `COOKIE_NAMES.ACCESS_TOKEN` | 15 min | `useLogin()` in `src/services/auth/hooks/useLogin.ts` | | `access_token` | `COOKIE_NAMES.ACCESS_TOKEN` | 15 min | `useLogin()` in `src/services/auth/hooks/useLogin.ts` |
| `refresh_token` | `COOKIE_NAMES.REFRESH_TOKEN` | 7 days | `useLogin()` in `src/services/auth/hooks/useLogin.ts` | | `refresh_token` | `COOKIE_NAMES.REFRESH_TOKEN` | 7 days | `useLogin()` in `src/services/auth/hooks/useLogin.ts` |
Both are regular (non-httpOnly) cookies so they are readable by both server and client. **Session state lives in `AuthContext`** (`src/context/auth/`). The root layout resolves the session on
the server with `getServerAuthState()` (`src/lib/auth/server.ts`) — which reads the `access_token` cookie
and checks the JWT `exp` via the shared `isTokenAlive` (`src/lib/auth/token.ts`) — and passes it to
`<AuthProvider initialState={…}>`. So the **first render already knows whether the user is
authenticated**: no logged-out flash, no post-mount cookie read.
**Lifecycle:** **Lifecycle:**
- Written after a successful login via `setClientCookie` with `AUTH_ACCESS_COOKIE_OPTIONS` / `AUTH_REFRESH_COOKIE_OPTIONS` - Written after a successful login via `setClientCookie` (`useLogin`), which also dispatches `LOG_IN` to
- Deleted by `useLogout()` (`src/services/auth/hooks/useLogout.ts`) / `useEventLogout()` (`src/hooks/auth.ts`), and automatically by `clientFetch` on 401 keep `AuthContext` in sync on the client without a reload.
- Read by `serverFetch` via `getServerCookie(COOKIE_NAMES.ACCESS_TOKEN)` - Deleted by `useLogout()` (`src/services/auth/hooks/useLogout.ts`) — the single logout path: it calls
- Read by `clientFetch` via `getClientCookie(COOKIE_NAMES.ACCESS_TOKEN)` the API, clears both cookies, dispatches `LOG_OUT`, and redirects — and automatically by `clientFetch`
on 401.
- Read on the server by `serverFetch` / `getServerAuthState` via `getServerCookie`.
- Read on the client by `clientFetch` via `getClientCookie` (to attach `Authorization: Bearer`).
**Middleware:** validates the `exp` claim of the access token locally (base64-decode the JWT payload, no signature check) before rendering any private page. An absent or expired token redirects to `/{locale}/login`. **`useIsAuthenticated()`** (`src/hooks/auth.ts`) reads the server-seeded `AuthContext`, so it is correct
on the first paint (it no longer reads the cookie after mount).
**`useIsAuthenticated()` hook:** SSR-safe — initialises `false`, sets the real value in `useEffect` by reading the cookie. This prevents hydration mismatches. **Middleware** (`middleware.ts`) gates private routes with the same `isTokenAlive` helper before render.
**Security posture — current limits and best-practice follow-ups.** The flow above is the intended
client design, but the auth model has known gaps that need *server* coordination to close. Don't
silently "fix" them client-only:
- **Tokens are non-httpOnly cookies** (JS-readable) so `clientFetch` can attach the bearer header — this
trades XSS-hardening for the bearer pattern. Real hardening (httpOnly cookies set by the server + a
same-origin proxy) spans the server.
- **The middleware check is UX-only, not a security boundary:** it decodes the JWT and checks `exp` but
does **not** verify the signature. The API is the only authority; never gate real authorization on the
middleware or `isTokenAlive`.
- **No refresh-token rotation** is implemented — the `refresh_token` cookie is set/cleared but never
exchanged; access expiry just forces re-login on the next 401.
- **No role/permission model** yet (`User` is `{ id, username }`); authorization is binary. Add roles to
`User`/`AuthState` and the server before gating UI by permission.
--- ---
+6 -5
View File
@@ -72,9 +72,9 @@ src/
├── hooks/ Custom hooks (auth, layout/mobile, events, window size) ├── hooks/ Custom hooks (auth, layout/mobile, events, window size)
├── i18n/ next-intl routing + request config ├── i18n/ next-intl routing + request config
├── layout/ PublicLayout / PrivateLayout + TopBar, SideBar, BottomBar ├── layout/ PublicLayout / PrivateLayout + TopBar, SideBar, BottomBar
├── lib/ api (client/server fetch), cookies, query (TanStack), toast ├── lib/ api (client/server fetch), auth (JWT/session helpers), cookies, query, toast
├── services/ Domain services: {domain}/{types,keys,apis,hooks} ├── services/ Domain services: {domain}/{types,keys,apis,hooks}
├── store/ AppStore (React context + reducer) for client state ├── context/ React context providers — auth/ is AuthContext (server-seeded session state)
├── theme/ ThemeProvider, palettes, tokens.css, typography, direction ├── theme/ ThemeProvider, palettes, tokens.css, typography, direction
└── utils/ Helpers (storage, navigation, env, text, types) └── utils/ Helpers (storage, navigation, env, text, types)
``` ```
@@ -89,6 +89,7 @@ Translation files live in [`messages/`](messages) (`en.json` / `fa.json`) and mu
- This is a server-rendered Next.js app (App Router + middleware + server-side cookies) — **not** a - This is a server-rendered Next.js app (App Router + middleware + server-side cookies) — **not** a
static export. static export.
- Internationalization is locale-prefixed (`/fa`, `/en`); `fa` is the default and is RTL. - Internationalization is locale-prefixed (`/fa`, `/en`); `fa` is the default and is RTL.
- Authentication is cookie-based (`access_token` / `refresh_token`), managed through - Authentication is cookie-based (`access_token` / `refresh_token`). Session state lives in
`src/lib/cookies/` and the `src/services/auth/` hooks. The API base URL comes from `AuthContext` (`src/context/auth/`), seeded on the server from the request cookie so the first
`NEXT_PUBLIC_API_URL`. render reflects the real session; cookies are managed through `src/lib/cookies/` and the
`src/services/auth/` hooks. The API base URL comes from `NEXT_PUBLIC_API_URL`.
+28 -5
View File
@@ -9,6 +9,31 @@
import next from 'eslint-config-next'; import next from 'eslint-config-next';
import prettier from 'eslint-config-prettier/flat'; import prettier from 'eslint-config-prettier/flat';
// Unused variables/imports are dead code. eslint-config-next already enables
// `@typescript-eslint/no-unused-vars` at 'warn'; we raise it to 'error' so
// `npm run check` fails on dead code instead of merely warning. We patch the
// severity *in place* on next's own config objects rather than adding a separate
// override object, because in flat config a rule can only be referenced from a
// config object that registers its plugin — and the `@typescript-eslint` plugin
// is registered inside next's objects, not ours. Prefix a name with `_` to opt
// out (intentionally-unused args, catch bindings, rest-spread siblings).
const NO_UNUSED_VARS = [
'error',
{
args: 'after-used',
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
];
const nextWithStrictUnusedVars = [...next].map((entry) =>
entry?.rules?.['@typescript-eslint/no-unused-vars']
? { ...entry, rules: { ...entry.rules, '@typescript-eslint/no-unused-vars': NO_UNUSED_VARS } }
: entry
);
/** @type {import('eslint').Linter.Config[]} */ /** @type {import('eslint').Linter.Config[]} */
const config = [ const config = [
// Things ESLint should never look at. // Things ESLint should never look at.
@@ -16,17 +41,15 @@ const config = [
ignores: ['.next/**', 'out/**', 'coverage/**', 'node_modules/**', 'next-env.d.ts'], ignores: ['.next/**', 'out/**', 'coverage/**', 'node_modules/**', 'next-env.d.ts'],
}, },
// Next.js + TypeScript + React + import/a11y rule sets. // Next.js + TypeScript + React + import/a11y rule sets, with no-unused-vars
...next, // raised to error (see NO_UNUSED_VARS above).
...nextWithStrictUnusedVars,
// Turn off every stylistic rule that would fight Prettier. Keep this last // Turn off every stylistic rule that would fight Prettier. Keep this last
// among the rule-providing entries so it wins. Formatting is owned by // among the rule-providing entries so it wins. Formatting is owned by
// Prettier (`npm run format`), never by ESLint. // Prettier (`npm run format`), never by ESLint.
prettier, prettier,
// Project-specific rule overrides go here, e.g.:
// { rules: { 'react/jsx-key': 'error' } }
//
// NOTE: `import/no-cycle` is intentionally NOT enabled. On this toolchain the // NOTE: `import/no-cycle` is intentionally NOT enabled. On this toolchain the
// eslint-plugin-import TypeScript resolver bundled by eslint-config-next 16 // eslint-plugin-import TypeScript resolver bundled by eslint-config-next 16
// throws "invalid interface loaded as resolver" for that rule, and it cannot // throws "invalid interface loaded as resolver" for that rule, and it cannot
+1 -10
View File
@@ -2,20 +2,11 @@ import createMiddleware from 'next-intl/middleware';
import { type NextRequest, NextResponse } from 'next/server'; import { type NextRequest, NextResponse } from 'next/server';
import { routing } from './src/i18n/routing'; import { routing } from './src/i18n/routing';
import { COOKIE_NAMES } from './src/lib/cookies'; import { COOKIE_NAMES } from './src/lib/cookies';
import { isTokenAlive } from './src/lib/auth/token';
import { HEADER_NAMES, PUBLIC_PATHS, ROUTES } from './src/constants'; import { HEADER_NAMES, PUBLIC_PATHS, ROUTES } from './src/constants';
const intlMiddleware = createMiddleware(routing); const intlMiddleware = createMiddleware(routing);
function isTokenAlive(token?: string): boolean {
if (!token) return false;
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return typeof payload.exp === 'number' && payload.exp * 1000 > Date.now();
} catch {
return false;
}
}
export default function middleware(request: NextRequest) { export default function middleware(request: NextRequest) {
const i18nResponse = intlMiddleware(request); const i18nResponse = intlMiddleware(request);
+5 -3
View File
@@ -4,7 +4,8 @@ import localFont from 'next/font/local';
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';
import { AppStoreProvider } from '@/store'; import { getServerAuthState } from '@/lib/auth/server';
import { AuthProvider } from '@/context/auth';
import { ThemeProvider, getDirection } from '@/theme'; import { ThemeProvider, getDirection } from '@/theme';
import { BRAND } from '@/theme/colors'; import { BRAND } from '@/theme/colors';
import { NotistackProvider } from '@/lib/toast'; import { NotistackProvider } from '@/lib/toast';
@@ -73,6 +74,7 @@ export default async function LocaleLayout({
const messages = await getMessages({ locale: safeLocale }); const messages = await getMessages({ locale: safeLocale });
const { colorScheme, defaultMode } = await getThemeMode(); const { colorScheme, defaultMode } = await getThemeMode();
const authState = await getServerAuthState();
const dir = getDirection(safeLocale); const dir = getDirection(safeLocale);
// Only attach the Mikhak font variable on RTL (Persian) routes so the // Only attach the Mikhak font variable on RTL (Persian) routes so the
@@ -88,7 +90,7 @@ export default async function LocaleLayout({
> >
<body> <body>
<NextIntlClientProvider locale={safeLocale} messages={messages}> <NextIntlClientProvider locale={safeLocale} messages={messages}>
<AppStoreProvider> <AuthProvider initialState={authState}>
<ThemeProvider dir={dir} defaultMode={defaultMode}> <ThemeProvider dir={dir} defaultMode={defaultMode}>
<QueryProvider> <QueryProvider>
<NotistackProvider> <NotistackProvider>
@@ -96,7 +98,7 @@ export default async function LocaleLayout({
</NotistackProvider> </NotistackProvider>
</QueryProvider> </QueryProvider>
</ThemeProvider> </ThemeProvider>
</AppStoreProvider> </AuthProvider>
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>
</html> </html>
+24
View File
@@ -0,0 +1,24 @@
'use client';
import { createContext, useContext, useReducer } from 'react';
import type { Dispatch, FunctionComponent, PropsWithChildren } from 'react';
import { authReducer } from './authReducer';
import { INITIAL_AUTH_STATE } from './types';
import type { AuthAction, AuthState } from './types';
export type AuthContextValue = [AuthState, Dispatch<AuthAction>];
const AuthContext = createContext<AuthContextValue>([INITIAL_AUTH_STATE, () => null]);
interface AuthProviderProps extends PropsWithChildren {
// Auth state resolved on the server from the request's access-token cookie.
// Seeding the reducer with it means the first client render already reflects
// the real session — no logged-out flash, no post-mount cookie read.
initialState?: AuthState;
}
export const AuthProvider: FunctionComponent<AuthProviderProps> = ({ initialState, children }) => {
const value = useReducer(authReducer, initialState ?? INITIAL_AUTH_STATE);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = (): AuthContextValue => useContext(AuthContext);
+13
View File
@@ -0,0 +1,13 @@
import type { Reducer } from 'react';
import type { AuthAction, AuthState } from './types';
export const authReducer: Reducer<AuthState, AuthAction> = (state, action) => {
switch (action.type) {
case 'LOG_IN':
return { isAuthenticated: true, currentUser: action.user ?? state.currentUser };
case 'LOG_OUT':
return { isAuthenticated: false };
default:
return state;
}
};
+4
View File
@@ -0,0 +1,4 @@
export { AuthProvider, useAuth } from './AuthContext';
export type { AuthContextValue } from './AuthContext';
export { INITIAL_AUTH_STATE } from './types';
export type { AuthAction, AuthState } from './types';
+12
View File
@@ -0,0 +1,12 @@
import type { User } from '@/services/auth/types';
export interface AuthState {
isAuthenticated: boolean;
currentUser?: User;
}
export type AuthAction = { type: 'LOG_IN'; user?: User } | { type: 'LOG_OUT' };
export const INITIAL_AUTH_STATE: AuthState = {
isAuthenticated: false,
};
+9 -38
View File
@@ -1,42 +1,13 @@
import { useCallback, useEffect, useState } from 'react'; import { useAuth } from '@/context/auth';
import { useRouter } from 'next/navigation';
import { useLocale } from 'next-intl';
import { COOKIE_NAMES } from '@/lib/cookies';
import { deleteClientCookie, getClientCookie } from '@/lib/cookies/client';
import { ROUTES } from '@/constants';
import { useAppStore } from '../store';
/** /**
* Returns true when an access_token cookie is present on the client. * True when the current session is authenticated.
* Initialises as false (SSR-safe) and updates after mount to avoid hydration mismatches. *
* Reads AuthContext, which the root layout seeds from the request's access-token
* cookie on the server. The value is therefore correct on the first render — no
* post-mount cookie read, no hydration flash.
*/ */
export function useIsAuthenticated() { export function useIsAuthenticated(): boolean {
const [isAuthenticated, setIsAuthenticated] = useState(false); const [state] = useAuth();
return state.isAuthenticated;
useEffect(() => {
// SSR-safe: read the browser-only cookie once after mount so the server-rendered
// `false` reconciles on the client without a hydration mismatch. Deliberate setState.
// eslint-disable-next-line react-hooks/set-state-in-effect
setIsAuthenticated(Boolean(getClientCookie(COOKIE_NAMES.ACCESS_TOKEN)));
}, []);
return isAuthenticated;
}
/**
* Returns a handler that logs the current user out:
* clears auth cookies, resets AppStore, and redirects to the login page.
*/
export function useEventLogout() {
const [, dispatch] = useAppStore();
const router = useRouter();
const locale = useLocale();
return useCallback(() => {
deleteClientCookie(COOKIE_NAMES.ACCESS_TOKEN);
deleteClientCookie(COOKIE_NAMES.REFRESH_TOKEN);
dispatch({ type: 'LOG_OUT' });
router.replace(`/${locale}${ROUTES.LOGIN}`);
}, [dispatch, router, locale]);
} }
+8 -18
View File
@@ -1,21 +1,11 @@
'use client'; import type { FunctionComponent, PropsWithChildren } from 'react';
import { FunctionComponent, PropsWithChildren, useEffect } from 'react';
import { COOKIE_NAMES } from '@/lib/cookies';
import { getClientCookie } from '@/lib/cookies/client';
import { useAppStore } from '@/store';
const PrivateLayout: FunctionComponent<PropsWithChildren> = ({ children }) => { /**
const [,dispatch] = useAppStore(); * Authenticated layout shell. Auth state is seeded on the server by AuthProvider
* (see getServerAuthState) and kept current by the login/logout hooks, so this
useEffect(() => { * component no longer reads the cookie after mount — it is the place to build the
if (getClientCookie(COOKIE_NAMES.ACCESS_TOKEN)) { * authenticated layout chrome.
dispatch({ type: 'LOG_IN' }); */
} const PrivateLayout: FunctionComponent<PropsWithChildren> = ({ children }) => <>{children}</>;
}, [dispatch]);
return (
children
);
};
export default PrivateLayout; export default PrivateLayout;
+4 -3
View File
@@ -1,7 +1,8 @@
import { FunctionComponent, useCallback, MouseEvent } from 'react'; import { FunctionComponent, useCallback, MouseEvent } from 'react';
import { Stack, Divider, Drawer, DrawerProps } from '@mui/material'; import { Stack, Divider, Drawer, DrawerProps } from '@mui/material';
import { LinkToPage } from '@/utils'; import { LinkToPage } from '@/utils';
import { useEventLogout, useIsAuthenticated, useIsMobile } from '@/hooks'; import { useIsAuthenticated, useIsMobile } from '@/hooks';
import { useLogout } from '@/services/auth';
import { AppIconButton, UserInfo } from '@/components'; import { AppIconButton, UserInfo } from '@/components';
import { SIDE_BAR_WIDTH, TOP_BAR_DESKTOP_HEIGHT } from '../config'; import { SIDE_BAR_WIDTH, TOP_BAR_DESKTOP_HEIGHT } from '../config';
import SideBarNavList from './SideBarNavList'; import SideBarNavList from './SideBarNavList';
@@ -18,7 +19,7 @@ export interface SideBarProps extends Pick<DrawerProps, 'anchor' | 'className' |
const SideBar: FunctionComponent<SideBarProps> = ({ anchor, open, variant, items, onClose, ...restOfProps }) => { const SideBar: FunctionComponent<SideBarProps> = ({ anchor, open, variant, items, onClose, ...restOfProps }) => {
const isAuthenticated = useIsAuthenticated(); const isAuthenticated = useIsAuthenticated();
const onMobile = useIsMobile(); const onMobile = useIsMobile();
const onLogout = useEventLogout(); const { mutate: logout } = useLogout();
const handleAfterLinkClick = useCallback( const handleAfterLinkClick = useCallback(
(event: MouseEvent) => { (event: MouseEvent) => {
@@ -72,7 +73,7 @@ const SideBar: FunctionComponent<SideBarProps> = ({ anchor, open, variant, items
{/* Only DarkModeFormSwitch subscribes to useColorScheme — it's the sole re-render target */} {/* Only DarkModeFormSwitch subscribes to useColorScheme — it's the sole re-render target */}
<DarkModeFormSwitch /> <DarkModeFormSwitch />
{isAuthenticated && <AppIconButton icon="logout" title="Logout Current User" onClick={onLogout} />} {isAuthenticated && <AppIconButton icon="logout" title="Logout Current User" onClick={() => logout()} />}
</Stack> </Stack>
</Stack> </Stack>
</Drawer> </Drawer>
+22
View File
@@ -0,0 +1,22 @@
/**
* Server-only auth utilities.
* Import as: import { getServerAuthState } from '@/lib/auth/server'
*
* Uses `next/headers` (via the server cookie manager) and must only be called
* from Server Components, Server Actions, or Route Handlers — never from a
* client component.
*/
import { COOKIE_NAMES } from '@/lib/cookies';
import { getServerCookie } from '@/lib/cookies/server';
import type { AuthState } from '@/context/auth/types';
import { isTokenAlive } from './token';
/**
* Resolve the current request's auth state from the access-token cookie so the
* root layout can seed AuthProvider with a server-correct value before the first
* paint.
*/
export async function getServerAuthState(): Promise<AuthState> {
const token = await getServerCookie(COOKIE_NAMES.ACCESS_TOKEN);
return { isAuthenticated: isTokenAlive(token) };
}
+38
View File
@@ -0,0 +1,38 @@
/**
* JWT helpers shared by the edge middleware and server components.
*
* Pure functions with no `next/headers` dependency, so they are safe to import
* from `middleware.ts` (edge runtime) and from RSCs alike. They only inspect the
* token's `exp` claim — a cheap liveness check for routing/UX, NOT a security
* boundary. The signature is never verified here; the API server is the only
* authority that actually trusts the token.
*/
export interface JwtPayload {
exp?: number;
sub?: string;
[claim: string]: unknown;
}
// JWTs use base64url (no padding, `-`/`_` instead of `+`/`/`); atob expects
// standard base64, so normalise before decoding.
function base64UrlDecode(segment: string): string {
const base64 = segment.replace(/-/g, '+').replace(/_/g, '/');
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
return atob(padded);
}
export function decodeJwtPayload(token: string | undefined): JwtPayload | null {
if (!token) return null;
const segment = token.split('.')[1];
if (!segment) return null;
try {
return JSON.parse(base64UrlDecode(segment)) as JwtPayload;
} catch {
return null;
}
}
export function isTokenAlive(token: string | undefined): boolean {
const payload = decodeJwtPayload(token);
return typeof payload?.exp === 'number' && payload.exp * 1000 > Date.now();
}
@@ -4,16 +4,22 @@ import { AUTH_ACCESS_COOKIE_OPTIONS, AUTH_REFRESH_COOKIE_OPTIONS, COOKIE_NAMES }
import { setClientCookie } from '@/lib/cookies/client'; import { setClientCookie } from '@/lib/cookies/client';
import { dispatchToast } from '@/lib/toast/dispatchToast'; import { dispatchToast } from '@/lib/toast/dispatchToast';
import type { ApiError } from '@/lib/api/errors'; import type { ApiError } from '@/lib/api/errors';
import { useAuth } from '@/context/auth';
import { AuthClientApi } from '../apis/clientApi'; import { AuthClientApi } from '../apis/clientApi';
import type { LoginDto } from '../types'; import type { LoginDto } from '../types';
export function useLogin() { export function useLogin() {
const [, dispatch] = useAuth();
return useMutation({ return useMutation({
mutationFn: (dto: LoginDto) => AuthClientApi.login(dto), mutationFn: (dto: LoginDto) => AuthClientApi.login(dto),
onSuccess: (data) => { onSuccess: (data) => {
setClientCookie(COOKIE_NAMES.ACCESS_TOKEN, data.accessToken, AUTH_ACCESS_COOKIE_OPTIONS); setClientCookie(COOKIE_NAMES.ACCESS_TOKEN, data.accessToken, AUTH_ACCESS_COOKIE_OPTIONS);
setClientCookie(COOKIE_NAMES.REFRESH_TOKEN, data.refreshToken, AUTH_REFRESH_COOKIE_OPTIONS); setClientCookie(COOKIE_NAMES.REFRESH_TOKEN, data.refreshToken, AUTH_REFRESH_COOKIE_OPTIONS);
// Sync AuthContext after a client-side login so the UI reflects the new
// session immediately, without waiting for the next full server render.
dispatch({ type: 'LOG_IN' });
}, },
onError: (error: ApiError) => { onError: (error: ApiError) => {
dispatchToast(error.message, 'error'); dispatchToast(error.message, 'error');
+2 -2
View File
@@ -5,12 +5,12 @@ import { useLocale } from 'next-intl';
import { COOKIE_NAMES } from '@/lib/cookies'; import { COOKIE_NAMES } from '@/lib/cookies';
import { deleteClientCookie } from '@/lib/cookies/client'; import { deleteClientCookie } from '@/lib/cookies/client';
import { ROUTES } from '@/constants'; import { ROUTES } from '@/constants';
import { useAppStore } from '@/store'; import { useAuth } from '@/context/auth';
import { AuthClientApi } from '../apis/clientApi'; import { AuthClientApi } from '../apis/clientApi';
export function useLogout() { export function useLogout() {
const [, dispatch] = useAppStore(); const [, dispatch] = useAuth();
const router = useRouter(); const router = useRouter();
const locale = useLocale(); const locale = useLocale();
-18
View File
@@ -1,18 +0,0 @@
import { Reducer } from 'react';
import { AppStoreState } from './config';
const AppReducer: Reducer<AppStoreState, any> = (state, action) => {
switch (action.type || action.action) {
case 'CURRENT_USER':
return { ...state, currentUser: action?.currentUser || action?.payload };
case 'SIGN_UP':
case 'LOG_IN':
return { ...state, isAuthenticated: true };
case 'LOG_OUT':
return { ...state, isAuthenticated: false, currentUser: undefined };
default:
return state;
}
};
export default AppReducer;
-32
View File
@@ -1,32 +0,0 @@
'use client';
import {
createContext,
useReducer,
useContext,
FunctionComponent,
PropsWithChildren,
Dispatch,
ComponentType,
} from 'react';
import AppReducer from './AppReducer';
import { APP_STORE_INITIAL_STATE, AppStoreState } from './config';
export type AppContextReturningType = [AppStoreState, Dispatch<any>];
const AppContext = createContext<AppContextReturningType>([APP_STORE_INITIAL_STATE, () => null]);
const AppStoreProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {
const value: AppContextReturningType = useReducer(AppReducer, APP_STORE_INITIAL_STATE);
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
const useAppStore = (): AppContextReturningType => useContext(AppContext);
interface WithAppStoreProps {
appStore: AppContextReturningType;
}
const withAppStore = (Component: ComponentType<WithAppStoreProps>): FunctionComponent =>
function ComponentWithAppStore(props) {
return <Component {...props} appStore={useAppStore()} />;
};
export { AppStoreProvider, useAppStore, withAppStore };
-8
View File
@@ -1,8 +0,0 @@
export interface AppStoreState {
isAuthenticated: boolean;
currentUser?: object | undefined;
}
export const APP_STORE_INITIAL_STATE: AppStoreState = {
isAuthenticated: false,
};
-3
View File
@@ -1,3 +0,0 @@
import { AppStoreProvider, useAppStore, withAppStore } from './AppStore';
export { AppStoreProvider, useAppStore, withAppStore };
+669
View File
@@ -0,0 +1,669 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;}
body{margin:0;}
::-webkit-scrollbar{height:12px;width:12px;}
::-webkit-scrollbar-thumb{background:#cdc9bf;border-radius:8px;border:3px solid #e9e7e2;}
</style>
</helmet>
<div dir="rtl" style="font-family:'Vazirmatn',Tahoma,sans-serif;background:#e9e7e2;width:max-content;min-width:100%;min-height:100vh;box-sizing:border-box;padding:48px 56px 72px;">
<!-- ============ TITLE + LEGEND ============ -->
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:32px;max-width:1100px;margin-bottom:8px;flex-wrap:wrap;">
<div>
<div style="display:flex;align-items:center;gap:13px;">
<div style="width:40px;height:40px;border-radius:11px;background:#1d4a40;color:#f3efe9;display:grid;place-items:center;font:800 18px Vazirmatn;flex:none;">ب</div>
<div>
<div style="font:800 26px Vazirmatn;color:#123029;letter-spacing:-.01em;">بالین‌یار</div>
<div style="font:500 13px Vazirmatn;color:#6b7280;margin-top:1px;">وایرفریم جریان‌ها · اپلیکیشن موبایل · نسخه میان‌فی</div>
</div>
</div>
</div>
<div style="display:flex;gap:18px;flex-wrap:wrap;align-items:center;background:#fff;border:1px solid #ddd9d0;border-radius:12px;padding:11px 16px;">
<span style="font:700 11px Vazirmatn;color:#52606d;">راهنما:</span>
<span style="display:flex;align-items:center;gap:6px;font:600 11px Vazirmatn;color:#1f6b50;"><span style="width:9px;height:9px;border-radius:50%;background:#1f6b50;"></span>خودکار / تاییدشده</span>
<span style="display:flex;align-items:center;gap:6px;font:600 11px Vazirmatn;color:#8a6418;"><span style="width:9px;height:9px;border-radius:50%;background:#cf9b2c;"></span>در انتظار</span>
<span style="display:flex;align-items:center;gap:6px;font:600 11px Vazirmatn;color:#6b7280;"><span style="width:9px;height:9px;border-radius:50%;background:#b8b3a8;"></span>دستی / بعداً</span>
<span style="display:flex;align-items:center;gap:6px;font:600 11px Vazirmatn;color:#bf6f4d;"><span style="width:14px;height:9px;border-radius:3px;background:#d98c6a;"></span>اقساط / مالی</span>
</div>
</div>
<!-- ============ SECTION A — USER LOGIN + ONBOARDING ============ -->
<div style="margin:42px 0 16px;">
<div style="display:flex;align-items:center;gap:12px;">
<div style="width:30px;height:30px;border-radius:8px;background:#1d4a40;color:#fff;display:grid;place-items:center;font:800 14px Vazirmatn;flex:none;">A</div>
<h2 style="margin:0;font:800 20px Vazirmatn;color:#1d2a26;">ورود و آنبوردینگ کاربر</h2>
<span style="font:500 13px Vazirmatn;color:#9aa39e;">User login &amp; onboarding</span>
</div>
<sc-if value="{{ showNotes }}" hint-placeholder-val="{{ true }}">
<div style="margin:11px 0 0;padding:9px 14px;background:#fbf0e8;border:1px dashed #d98c6a;border-radius:8px;font:500 12.5px Vazirmatn;color:#8a5a3c;max-width:780px;">خانواده با شماره موبایل وارد می‌شود، یک بیمار ثبت می‌کند و مستقیم به خانه می‌رسد. احراز هویت سنگین برای کاربر لازم نیست.</div>
</sc-if>
</div>
<div style="display:flex;gap:22px;align-items:flex-start;padding-bottom:8px;">
<!-- A1 ورود -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">A۱ · ورود / ثبت‌نام</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;">
<div style="margin-top:48px;display:flex;flex-direction:column;align-items:center;gap:10px;">
<div style="width:62px;height:62px;border-radius:17px;background:#1d4a40;color:#f3efe9;display:grid;place-items:center;font:800 26px Vazirmatn;">ب</div>
<div style="font:800 20px Vazirmatn;color:#123029;">بالین‌یار</div>
<div style="font:500 12px Vazirmatn;color:#6b7280;text-align:center;">مراقبت خانگی قابل‌اعتماد، نزدیک شما</div>
</div>
<div style="margin-top:38px;display:flex;flex-direction:column;gap:6px;">
<div style="font:600 11px Vazirmatn;color:#52606d;">شماره موبایل</div>
<div style="border:1px solid #1d4a40;border-radius:11px;padding:12px 13px;font:500 13px Vazirmatn;color:#1d2a26;background:#fff;">۰۹۱۲ ۰۰۰ ۰۰۰۰</div>
</div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">دریافت کد تایید</div>
<div style="margin-top:14px;text-align:center;font:600 12px Vazirmatn;color:#1d4a40;border-top:1px solid #eceae5;padding-top:13px;">پرستار هستید؟ ورود پرستاران ←</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- A2 OTP -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">A۲ · کد تایید</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;">
<div style="font:700 16px Vazirmatn;color:#123029;margin-top:34px;">کد تایید را وارد کنید</div>
<div style="font:500 12px Vazirmatn;color:#6b7280;margin-top:7px;line-height:1.9;">کد ۴ رقمی به شماره <span style="color:#1d2a26;font-weight:700;">۰۹۱۲ ۰۰۰ ۰۰۰۰</span> پیامک شد.</div>
<div style="display:flex;gap:10px;margin-top:26px;direction:ltr;justify-content:center;">
<div style="width:52px;height:58px;border:1px solid #1d4a40;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#1d2a26;">۴</div>
<div style="width:52px;height:58px;border:1px solid #d9d6cf;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#1d2a26;">۸</div>
<div style="width:52px;height:58px;border:1px solid #d9d6cf;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#9aa39e;">_</div>
<div style="width:52px;height:58px;border:1px solid #d9d6cf;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#9aa39e;">_</div>
</div>
<div style="margin-top:18px;text-align:center;font:500 12px Vazirmatn;color:#9aa39e;">ارسال مجدد کد تا ۰۰:۴۵</div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">تایید و ادامه</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- A3 onboarding -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">A۳ · برای چه کسی؟</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;gap:11px;">
<div style="display:flex;gap:6px;margin-bottom:2px;"><span style="height:4px;flex:1;border-radius:2px;background:#1d4a40;"></span><span style="height:4px;flex:1;border-radius:2px;background:#e6e3dd;"></span></div>
<div style="font:700 16px Vazirmatn;color:#123029;">برای چه کسی مراقبت می‌خواهید؟</div>
<div style="display:flex;align-items:center;gap:11px;border:1px solid #1d4a40;background:#e7efec;border-radius:12px;padding:12px;"><div style="width:34px;height:34px;border-radius:9px;background:#cdd9d3;flex:none;"></div><div style="font:700 13px Vazirmatn;color:#1d2a26;">پدر یا مادر</div><div style="margin-right:auto;width:18px;height:18px;border-radius:50%;border:5px solid #1d4a40;"></div></div>
<div style="display:flex;align-items:center;gap:11px;border:1px solid #e6e3dd;border-radius:12px;padding:12px;"><div style="width:34px;height:34px;border-radius:9px;background:#eceae5;flex:none;"></div><div style="font:600 13px Vazirmatn;color:#52606d;">همسر</div><div style="margin-right:auto;width:18px;height:18px;border-radius:50%;border:1px solid #c8c4ba;"></div></div>
<div style="display:flex;align-items:center;gap:11px;border:1px solid #e6e3dd;border-radius:12px;padding:12px;"><div style="width:34px;height:34px;border-radius:9px;background:#eceae5;flex:none;"></div><div style="font:600 13px Vazirmatn;color:#52606d;">فرزند</div><div style="margin-right:auto;width:18px;height:18px;border-radius:50%;border:1px solid #c8c4ba;"></div></div>
<div style="display:flex;align-items:center;gap:11px;border:1px solid #e6e3dd;border-radius:12px;padding:12px;"><div style="width:34px;height:34px;border-radius:9px;background:#eceae5;flex:none;"></div><div style="font:600 13px Vazirmatn;color:#52606d;">خودم</div><div style="margin-right:auto;width:18px;height:18px;border-radius:50%;border:1px solid #c8c4ba;"></div></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ادامه</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- A4 add patient -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">A۴ · ثبت بیمار</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;gap:13px;">
<div style="font:700 16px Vazirmatn;color:#123029;">ثبت بیمار</div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">نام و نام خانوادگی</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 12px Vazirmatn;color:#9aa39e;background:#fafafa;">مثلاً پدر — حسن رضایی</div></div>
<div style="display:flex;gap:10px;"><div style="flex:1;display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">سن</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 12px Vazirmatn;color:#9aa39e;background:#fafafa;">۷۲</div></div><div style="flex:1;display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">جنسیت</div><div style="display:flex;gap:6px;"><div style="flex:1;text-align:center;border:1px solid #1d4a40;background:#e7efec;border-radius:10px;padding:10px 0;font:700 11px Vazirmatn;color:#1d4a40;">مرد</div><div style="flex:1;text-align:center;border:1px solid #d9d6cf;border-radius:10px;padding:10px 0;font:600 11px Vazirmatn;color:#9aa39e;">زن</div></div></div></div>
<div style="display:flex;flex-direction:column;gap:6px;"><div style="font:600 11px Vazirmatn;color:#52606d;">شرایط / وضعیت</div><div style="display:flex;gap:7px;flex-wrap:wrap;"><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#e7efec;color:#1d4a40;border:1px solid #cfe0da;">سالمند</span><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#f4f2ec;color:#6b7280;border:1px solid #e6e3dd;">پس از جراحی</span><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#f4f2ec;color:#6b7280;border:1px solid #e6e3dd;">دیابت</span><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#f4f2ec;color:#6b7280;border:1px solid #e6e3dd;">+ بیشتر</span></div></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ذخیره و ادامه</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- A5 home -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">A۵ · خانه</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px 12px;display:flex;flex-direction:column;gap:13px;overflow:hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;"><div><div style="font:500 11px Vazirmatn;color:#9aa39e;">سلام،</div><div style="font:800 16px Vazirmatn;color:#123029;">مریم رضایی</div></div><div style="width:36px;height:36px;border-radius:50%;background:#eceae5;"></div></div>
<div style="display:flex;align-items:center;gap:9px;border:1px solid #d9d6cf;border-radius:12px;padding:11px 13px;background:#fafafa;"><div style="width:16px;height:16px;border-radius:50%;border:2px solid #9aa39e;"></div><span style="font:500 12px Vazirmatn;color:#9aa39e;">جستجوی خدمت یا پرستار…</span></div>
<div style="font:700 13px Vazirmatn;color:#1d2a26;">دسته‌بندی خدمات</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<div style="width:calc(50% - 5px);border:1px solid #cfe0da;background:#e7efec;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#bcd0c8;"></div><div style="font:700 12px Vazirmatn;color:#1d4a40;">مراقبت سالمند</div></div>
<div style="width:calc(50% - 5px);border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#eceae5;"></div><div style="font:700 12px Vazirmatn;color:#52606d;">پرستار کودک</div></div>
<div style="width:calc(50% - 5px);border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#eceae5;"></div><div style="font:700 12px Vazirmatn;color:#52606d;">تزریقات و سرم</div></div>
<div style="width:calc(50% - 5px);border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#eceae5;"></div><div style="font:700 12px Vazirmatn;color:#52606d;">فیزیوتراپی</div></div>
</div>
<div style="border:1px dashed #d98c6a;background:#fbf0e8;border-radius:12px;padding:11px 13px;display:flex;align-items:center;gap:10px;"><div style="width:30px;height:30px;border-radius:50%;background:#f0d2bf;flex:none;"></div><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#8a5a3c;">پدر</div><div style="font:500 10.5px Vazirmatn;color:#a87a5c;">پرونده بیمار را کامل کنید ←</div></div></div>
</div>
<div style="flex:none;display:flex;border-top:1px solid #eceae5;background:#fff;padding:7px 2px;">
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#1d4a40;"></div><span style="font:700 8.5px Vazirmatn;color:#1d4a40;">خانه</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">رزروها</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">بیماران</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">کیف‌پول</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">پروفایل</span></div>
</div>
</div>
</div>
</div>
<!-- ============ SECTION B — NURSE LOGIN + VERIFICATION ============ -->
<div style="margin:48px 0 16px;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<div style="width:30px;height:30px;border-radius:8px;background:#1d4a40;color:#fff;display:grid;place-items:center;font:800 14px Vazirmatn;flex:none;">B</div>
<h2 style="margin:0;font:800 20px Vazirmatn;color:#1d2a26;">ورود و احراز هویت پرستار</h2>
<span style="font:500 13px Vazirmatn;color:#9aa39e;">Nurse login &amp; verification</span>
<span style="font:700 10px Vazirmatn;color:#bf6f4d;background:#fbf0e8;border:1px solid #ecc9b3;border-radius:20px;padding:3px 10px;">نمای پرستار</span>
</div>
<sc-if value="{{ showNotes }}" hint-placeholder-val="{{ true }}">
<div style="margin:11px 0 0;padding:9px 14px;background:#fbf0e8;border:1px dashed #d98c6a;border-radius:8px;font:500 12.5px Vazirmatn;color:#8a5a3c;max-width:840px;">احراز هویت پلتفرم‌محور و الزامی است: زنجیره مرحله‌ای (هویت ثبت‌احوال، پروانه نظام پرستاری، سوءپیشینه، مصاحبه). تا کامل‌شدن، پرستار قابل رزرو نیست.</div>
</sc-if>
</div>
<div style="display:flex;gap:22px;align-items:flex-start;padding-bottom:8px;">
<!-- B1 ورود پرستار -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۱ · ورود پرستار</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;">
<div style="margin-top:42px;display:flex;flex-direction:column;align-items:center;gap:10px;">
<div style="width:62px;height:62px;border-radius:17px;background:#123029;color:#f3efe9;display:grid;place-items:center;font:800 24px Vazirmatn;">ب</div>
<div style="font:800 18px Vazirmatn;color:#123029;">ورود پرستاران</div>
<div style="font:500 12px Vazirmatn;color:#6b7280;text-align:center;">ویژه پرستاران دارای پروانه نظام پرستاری</div>
</div>
<div style="margin-top:34px;display:flex;flex-direction:column;gap:6px;">
<div style="font:600 11px Vazirmatn;color:#52606d;">شماره موبایل</div>
<div style="border:1px solid #1d4a40;border-radius:11px;padding:12px 13px;font:500 13px Vazirmatn;color:#1d2a26;">۰۹۱۲ ۰۰۰ ۰۰۰۰</div>
</div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">دریافت کد تایید</div>
<div style="margin-top:14px;text-align:center;font:600 12px Vazirmatn;color:#1d4a40;border-top:1px solid #eceae5;padding-top:13px;">خانواده هستید؟ ورود کاربران ←</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- B2 OTP -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۲ · کد تایید</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;">
<div style="font:700 16px Vazirmatn;color:#123029;margin-top:34px;">کد تایید را وارد کنید</div>
<div style="font:500 12px Vazirmatn;color:#6b7280;margin-top:7px;line-height:1.9;">کد ۴ رقمی به <span style="color:#1d2a26;font-weight:700;">۰۹۱۲ ۰۰۰ ۰۰۰۰</span> پیامک شد.</div>
<div style="display:flex;gap:10px;margin-top:26px;direction:ltr;justify-content:center;">
<div style="width:52px;height:58px;border:1px solid #1d4a40;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#1d2a26;">۲</div>
<div style="width:52px;height:58px;border:1px solid #d9d6cf;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#9aa39e;">_</div>
<div style="width:52px;height:58px;border:1px solid #d9d6cf;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#9aa39e;">_</div>
<div style="width:52px;height:58px;border:1px solid #d9d6cf;border-radius:11px;display:grid;place-items:center;font:700 22px Vazirmatn;color:#9aa39e;">_</div>
</div>
<div style="margin-top:18px;text-align:center;font:500 12px Vazirmatn;color:#9aa39e;">ارسال مجدد کد تا ۰۰:۵۸</div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">تایید و ورود</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- B3 verification status -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۳ · وضعیت احراز هویت</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 16px Vazirmatn;color:#123029;">احراز هویت و صلاحیت</div>
<div style="display:flex;align-items:center;gap:9px;"><div style="flex:1;height:6px;border-radius:3px;background:#e6e3dd;overflow:hidden;"><div style="width:40%;height:100%;background:#1d4a40;"></div></div><span style="font:700 11px Vazirmatn;color:#52606d;">۲ از ۵</span></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #cfe0da;background:#f3f8f6;border-radius:11px;padding:11px;"><div style="width:24px;height:24px;border-radius:50%;background:#1f6b50;color:#fff;display:grid;place-items:center;font:700 11px Vazirmatn;flex:none;"></div><div style="flex:1;font:700 12px Vazirmatn;color:#1d2a26;">شماره موبایل</div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">تاییدشده</span></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #1d4a40;border-radius:11px;padding:11px;"><div style="width:24px;height:24px;border-radius:50%;border:2px solid #cf9b2c;color:#8a6418;display:grid;place-items:center;font:700 11px Vazirmatn;flex:none;">۲</div><div style="flex:1;font:700 12px Vazirmatn;color:#1d2a26;">هویت — کارت ملی و سلفی</div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#fbf3df;color:#8a6418;">در انتظار</span></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #e6e3dd;border-radius:11px;padding:11px;opacity:.75;"><div style="width:24px;height:24px;border-radius:50%;background:#eceae5;color:#9aa39e;display:grid;place-items:center;font:700 11px Vazirmatn;flex:none;">۳</div><div style="flex:1;font:600 12px Vazirmatn;color:#6b7280;">پروانه نظام پرستاری</div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#eef0ee;color:#9aa39e;">بعدی</span></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #e6e3dd;border-radius:11px;padding:11px;opacity:.75;"><div style="width:24px;height:24px;border-radius:50%;background:#eceae5;color:#9aa39e;display:grid;place-items:center;font:700 11px Vazirmatn;flex:none;">۴</div><div style="flex:1;font:600 12px Vazirmatn;color:#6b7280;">گواهی سوءپیشینه</div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#eef0ee;color:#9aa39e;">بعدی</span></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #e6e3dd;border-radius:11px;padding:11px;opacity:.75;"><div style="width:24px;height:24px;border-radius:50%;background:#eceae5;color:#9aa39e;display:grid;place-items:center;font:700 11px Vazirmatn;flex:none;">۵</div><div style="flex:1;font:600 12px Vazirmatn;color:#6b7280;">مصاحبه ویدیویی</div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#eef0ee;color:#9aa39e;">بعدی</span></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ادامه مرحله ۲</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- B4 identity -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۴ · تایید هویت</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 16px Vazirmatn;color:#123029;">تایید هویت</div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">کد ملی</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 12px Vazirmatn;color:#9aa39e;background:#fafafa;">۰۰۱۲۳۴۵۶۷۸</div></div>
<div style="border:1.5px dashed #b8c4be;border-radius:12px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:7px;background:#f7faf9;"><div style="width:40px;height:40px;border-radius:10px;background:#dde6e2;"></div><div style="font:700 12px Vazirmatn;color:#1d4a40;">بارگذاری تصویر کارت ملی</div><div style="font:500 10px Vazirmatn;color:#9aa39e;">از دوربین یا گالری</div></div>
<div style="border:1.5px dashed #b8c4be;border-radius:12px;padding:16px;display:flex;flex-direction:column;align-items:center;gap:7px;background:#f7faf9;"><div style="width:40px;height:40px;border-radius:50%;background:#dde6e2;"></div><div style="font:700 12px Vazirmatn;color:#1d4a40;">احراز زنده‌بودن (سلفی)</div><div style="font:500 10px Vazirmatn;color:#9aa39e;">تطبیق چهره با کارت ملی</div></div>
<div style="display:flex;align-items:center;gap:8px;background:#e4f0ea;border-radius:9px;padding:9px 11px;"><span style="width:8px;height:8px;border-radius:50%;background:#1f6b50;flex:none;"></span><span style="font:600 10.5px Vazirmatn;color:#1f6b50;">استعلام خودکار از ثبت احوال</span></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ارسال و استعلام</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- B5 credentials -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۵ · مدارک حرفه‌ای</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 16px Vazirmatn;color:#123029;">مدارک پرستاری</div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">شماره نظام پرستاری</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 12px Vazirmatn;color:#9aa39e;background:#fafafa;">ن-۱۲۳۴۵۶</div></div>
<div style="border:1.5px dashed #b8c4be;border-radius:12px;padding:14px;display:flex;align-items:center;gap:11px;background:#f7faf9;"><div style="width:34px;height:34px;border-radius:9px;background:#dde6e2;flex:none;"></div><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#1d4a40;">پروانه / کارت نظام پرستاری</div><div style="font:500 10px Vazirmatn;color:#9aa39e;">بارگذاری تصویر</div></div></div>
<div style="border:1px solid #cfe0da;background:#f3f8f6;border-radius:12px;padding:14px;display:flex;align-items:center;gap:11px;"><div style="width:34px;height:34px;border-radius:9px;background:#bcd0c8;flex:none;display:grid;place-items:center;font:700 13px Vazirmatn;color:#1d4a40;"></div><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">مدرک تحصیلی</div><div style="font:500 10px Vazirmatn;color:#1f6b50;">بارگذاری شد</div></div></div>
<div style="display:flex;flex-direction:column;gap:6px;"><div style="font:600 11px Vazirmatn;color:#52606d;">تخصص‌ها</div><div style="display:flex;gap:7px;flex-wrap:wrap;"><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#e7efec;color:#1d4a40;border:1px solid #cfe0da;">سالمندان</span><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#e7efec;color:#1d4a40;border:1px solid #cfe0da;">ICU</span><span style="font:600 11px Vazirmatn;padding:6px 11px;border-radius:20px;background:#f4f2ec;color:#6b7280;border:1px solid #e6e3dd;">+ افزودن</span></div></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ثبت مدارک</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- B6 under review -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۶ · در حال بررسی</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;align-items:center;text-align:center;">
<div style="margin-top:54px;width:88px;height:88px;border-radius:50%;background:#fbf3df;display:grid;place-items:center;"><div style="width:46px;height:46px;border-radius:50%;border:4px solid #cf9b2c;"></div></div>
<div style="font:800 17px Vazirmatn;color:#123029;margin-top:22px;">مدارک شما در حال بررسی است</div>
<div style="font:500 12px Vazirmatn;color:#6b7280;margin-top:9px;line-height:1.9;">کارشناسان بالین‌یار صلاحیت شما را بررسی می‌کنند. معمولاً ۲۴ تا ۴۸ ساعت زمان می‌برد.</div>
<div style="width:100%;margin-top:26px;display:flex;flex-direction:column;gap:9px;text-align:right;">
<div style="display:flex;align-items:center;gap:9px;font:600 11px Vazirmatn;color:#1f6b50;"><span style="width:8px;height:8px;border-radius:50%;background:#1f6b50;"></span>هویت تایید شد</div>
<div style="display:flex;align-items:center;gap:9px;font:600 11px Vazirmatn;color:#8a6418;"><span style="width:8px;height:8px;border-radius:50%;background:#cf9b2c;"></span>بررسی مدارک حرفه‌ای</div>
<div style="display:flex;align-items:center;gap:9px;font:600 11px Vazirmatn;color:#9aa39e;"><span style="width:8px;height:8px;border-radius:50%;background:#b8b3a8;"></span>مصاحبه ویدیویی</div>
</div>
<div style="margin-top:auto;width:100%;border:1px solid #1d4a40;color:#1d4a40;border-radius:12px;padding:12px;text-align:center;font:700 13px Vazirmatn;">مشاهده وضعیت</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- B7 complete profile -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">B۷ · تکمیل پروفایل و خدمات</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 16px Vazirmatn;color:#123029;">تکمیل پروفایل</div>
<div style="display:flex;align-items:center;gap:11px;"><div style="width:52px;height:52px;border-radius:50%;background:#eceae5;flex:none;display:grid;place-items:center;font:700 18px Vazirmatn;color:#9aa39e;">+</div><div style="font:500 11px Vazirmatn;color:#6b7280;line-height:1.8;">عکس پروفایل و معرفی کوتاه خود را اضافه کنید</div></div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;margin-top:2px;">خدمات و قیمت‌ها</div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:11px;display:flex;align-items:center;gap:10px;"><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">مراقبت سالمند — ساعتی</div><div style="font:600 11px Vazirmatn;color:#1d4a40;margin-top:2px;">۲۸۰٬۰۰۰ تومان / ساعت</div></div><div style="font:600 11px Vazirmatn;color:#9aa39e;">ویرایش</div></div>
<div style="border:1px dashed #cfe0da;border-radius:11px;padding:11px;text-align:center;font:700 12px Vazirmatn;color:#1d4a40;">+ افزودن خدمت</div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;margin-top:2px;">روزهای در دسترس</div>
<div style="display:flex;gap:5px;flex-wrap:wrap;"><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#e7efec;color:#1d4a40;">ش</span><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#e7efec;color:#1d4a40;">ی</span><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#f4f2ec;color:#9aa39e;">د</span><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#e7efec;color:#1d4a40;">س</span><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#f4f2ec;color:#9aa39e;">چ</span><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#e7efec;color:#1d4a40;">پ</span><span style="font:700 10px Vazirmatn;padding:6px 9px;border-radius:8px;background:#f4f2ec;color:#9aa39e;">ج</span></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">انتشار پروفایل</div>
</div>
</div>
</div>
</div>
<!-- ============ SECTION C — REQUEST NURSING SERVICE ============ -->
<div style="margin:48px 0 16px;">
<div style="display:flex;align-items:center;gap:12px;">
<div style="width:30px;height:30px;border-radius:8px;background:#1d4a40;color:#fff;display:grid;place-items:center;font:800 14px Vazirmatn;flex:none;">C</div>
<h2 style="margin:0;font:800 20px Vazirmatn;color:#1d2a26;">درخواست خدمت پرستاری</h2>
<span style="font:500 13px Vazirmatn;color:#9aa39e;">Search → match → request → book</span>
</div>
<sc-if value="{{ showNotes }}" hint-placeholder-val="{{ true }}">
<div style="margin:11px 0 0;padding:9px 14px;background:#fbf0e8;border:1px dashed #d98c6a;border-radius:8px;font:500 12.5px Vazirmatn;color:#8a5a3c;max-width:840px;">جستجو بر اساس دسته، موقعیت، قیمت و جنسیت. درخواست → تایید پرستار → پرداخت → تایید نهایی. هم‌جنس‌بودن مراقب نزدیک به الزامی است.</div>
</sc-if>
</div>
<div style="display:flex;gap:22px;align-items:flex-start;padding-bottom:8px;">
<!-- C1 search/category -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">C۱ · جستجو و فیلتر</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 16px Vazirmatn;color:#123029;">جستجوی پرستار</div>
<div style="display:flex;align-items:center;gap:9px;border:1px solid #1d4a40;border-radius:12px;padding:11px 13px;"><div style="width:16px;height:16px;border-radius:50%;border:2px solid #1d4a40;"></div><span style="font:500 12px Vazirmatn;color:#1d2a26;">مراقبت سالمند</span></div>
<div style="display:flex;gap:7px;flex-wrap:wrap;"><span style="font:600 11px Vazirmatn;padding:7px 12px;border-radius:20px;background:#e7efec;color:#1d4a40;border:1px solid #cfe0da;">تهران ▾</span><span style="font:600 11px Vazirmatn;padding:7px 12px;border-radius:20px;background:#f4f2ec;color:#6b7280;border:1px solid #e6e3dd;">تاریخ ▾</span><span style="font:600 11px Vazirmatn;padding:7px 12px;border-radius:20px;background:#f4f2ec;color:#6b7280;border:1px solid #e6e3dd;">جنسیت ▾</span></div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;margin-top:2px;">دسته‌بندی</div>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<div style="width:calc(50% - 5px);border:1px solid #1d4a40;background:#e7efec;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#bcd0c8;"></div><div style="font:700 12px Vazirmatn;color:#1d4a40;">مراقبت سالمند</div></div>
<div style="width:calc(50% - 5px);border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#eceae5;"></div><div style="font:700 12px Vazirmatn;color:#52606d;">پرستار کودک</div></div>
<div style="width:calc(50% - 5px);border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#eceae5;"></div><div style="font:700 12px Vazirmatn;color:#52606d;">تزریقات و سرم</div></div>
<div style="width:calc(50% - 5px);border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;flex-direction:column;gap:8px;"><div style="width:30px;height:30px;border-radius:9px;background:#eceae5;"></div><div style="font:700 12px Vazirmatn;color:#52606d;">مراقبت زخم</div></div>
</div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">مشاهده ۲۴ پرستار</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- C2 results -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">C۲ · نتایج جستجو</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:14px 14px;display:flex;flex-direction:column;gap:11px;overflow:hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;"><span style="font:700 13px Vazirmatn;color:#1d2a26;">۲۴ پرستار</span><span style="font:600 11px Vazirmatn;color:#1d4a40;">مرتب‌سازی: امتیاز ▾</span></div>
<div style="border:1px solid #cfe0da;border-radius:13px;padding:11px;display:flex;gap:11px;"><div style="width:50px;height:50px;border-radius:11px;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="display:flex;align-items:center;gap:6px;"><span style="font:700 12px Vazirmatn;color:#1d2a26;">زهرا کریمی</span><span style="font:700 8px Vazirmatn;padding:1px 6px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">✓ تاییدشده</span></div><div style="font:600 10.5px Vazirmatn;color:#8a6418;margin-top:3px;">★ ۴٫۹ (۱۲۰ نظر) · ۲٫۱ کیلومتر</div><div style="font:700 11px Vazirmatn;color:#1d4a40;margin-top:4px;">از ۲۵۰٬۰۰۰ تومان / ساعت</div></div></div>
<div style="border:1px solid #e6e3dd;border-radius:13px;padding:11px;display:flex;gap:11px;"><div style="width:50px;height:50px;border-radius:11px;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="display:flex;align-items:center;gap:6px;"><span style="font:700 12px Vazirmatn;color:#1d2a26;">مریم احمدی</span><span style="font:700 8px Vazirmatn;padding:1px 6px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">✓ تاییدشده</span></div><div style="font:600 10.5px Vazirmatn;color:#8a6418;margin-top:3px;">★ ۴٫۸ (۹۸ نظر) · ۳٫۴ کیلومتر</div><div style="font:700 11px Vazirmatn;color:#1d4a40;margin-top:4px;">از ۲۹۰٬۰۰۰ تومان / ساعت</div></div></div>
<div style="border:1px solid #e6e3dd;border-radius:13px;padding:11px;display:flex;gap:11px;"><div style="width:50px;height:50px;border-radius:11px;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="display:flex;align-items:center;gap:6px;"><span style="font:700 12px Vazirmatn;color:#1d2a26;">سارا موسوی</span><span style="font:700 8px Vazirmatn;padding:1px 6px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">✓ تاییدشده</span></div><div style="font:600 10.5px Vazirmatn;color:#8a6418;margin-top:3px;">★ ۴٫۷ (۷۶ نظر) · ۴٫۰ کیلومتر</div><div style="font:700 11px Vazirmatn;color:#1d4a40;margin-top:4px;">از ۲۴۰٬۰۰۰ تومان / ساعت</div></div></div>
</div>
<div style="flex:none;display:flex;border-top:1px solid #eceae5;background:#fff;padding:7px 2px;">
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#1d4a40;"></div><span style="font:700 8.5px Vazirmatn;color:#1d4a40;">خانه</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">رزروها</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">بیماران</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">کیف‌پول</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">پروفایل</span></div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- C3 nurse profile -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">C۳ · پروفایل پرستار</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px 0;display:flex;flex-direction:column;gap:11px;overflow:hidden;">
<div style="display:flex;align-items:center;gap:12px;"><div style="width:62px;height:62px;border-radius:16px;background:#eceae5;flex:none;"></div><div><div style="display:flex;align-items:center;gap:6px;"><span style="font:800 15px Vazirmatn;color:#123029;">زهرا کریمی</span></div><div style="font:600 11px Vazirmatn;color:#8a6418;margin-top:3px;">★ ۴٫۹ · ۱۲۰ نظر</div><div style="display:flex;gap:5px;margin-top:5px;"><span style="font:700 8px Vazirmatn;padding:2px 7px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">✓ تاییدشده</span><span style="font:700 8px Vazirmatn;padding:2px 7px;border-radius:20px;background:#e7efec;color:#1d4a40;">نظام پرستاری ✓</span></div></div></div>
<div style="display:flex;gap:6px;flex-wrap:wrap;"><span style="font:600 10px Vazirmatn;padding:5px 10px;border-radius:20px;background:#f4f2ec;color:#52606d;border:1px solid #e6e3dd;">سالمندان</span><span style="font:600 10px Vazirmatn;padding:5px 10px;border-radius:20px;background:#f4f2ec;color:#52606d;border:1px solid #e6e3dd;">تزریقات</span><span style="font:600 10px Vazirmatn;padding:5px 10px;border-radius:20px;background:#f4f2ec;color:#52606d;border:1px solid #e6e3dd;">۸ سال سابقه</span></div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;">خدمات و قیمت</div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:10px;display:flex;justify-content:space-between;align-items:center;"><span style="font:600 11px Vazirmatn;color:#1d2a26;">مراقبت سالمند (ساعتی)</span><span style="font:700 11px Vazirmatn;color:#1d4a40;">۲۵۰٬۰۰۰</span></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:10px;display:flex;justify-content:space-between;align-items:center;"><span style="font:600 11px Vazirmatn;color:#1d2a26;">شیفت شبانه (۱۲ ساعت)</span><span style="font:700 11px Vazirmatn;color:#1d4a40;">۲٬۴۰۰٬۰۰۰</span></div>
<div style="background:#f7faf9;border:1px solid #e6e3dd;border-radius:11px;padding:10px;"><div style="font:600 10px Vazirmatn;color:#8a6418;">★★★★★ آخرین نظر</div><div style="font:500 10.5px Vazirmatn;color:#6b7280;margin-top:4px;line-height:1.8;">«بسیار دلسوز و وقت‌شناس بود، از مراقبت پدرم راضی بودیم.»</div></div>
</div>
<div style="flex:none;padding:12px 16px;border-top:1px solid #eceae5;background:#fff;"><div style="background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">درخواست رزرو</div></div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- C4 request form -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">C۴ · فرم درخواست</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;overflow:hidden;">
<div style="font:700 15px Vazirmatn;color:#123029;">درخواست خدمت</div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">بیمار</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:600 12px Vazirmatn;color:#1d2a26;display:flex;justify-content:space-between;">پدر — حسن رضایی <span style="color:#9aa39e;"></span></div></div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">نوع خدمت</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:600 12px Vazirmatn;color:#1d2a26;display:flex;justify-content:space-between;">مراقبت سالمند (ساعتی) <span style="color:#9aa39e;"></span></div></div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">آدرس</div><div style="height:54px;border-radius:10px;background:#e9ece9;border:1px solid #d9d6cf;display:flex;align-items:center;padding:0 12px;gap:8px;"><div style="width:18px;height:18px;border-radius:50%;background:#1d4a40;flex:none;"></div><span style="font:600 11px Vazirmatn;color:#52606d;">منزل — نارمک، تهران</span></div></div>
<div style="display:flex;gap:9px;"><div style="flex:1;display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">تاریخ</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:9px 11px;font:600 11px Vazirmatn;color:#1d2a26;">۱۴۰۴/۰۴/۱۵</div></div><div style="flex:1;display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">ساعت</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:9px 11px;font:600 11px Vazirmatn;color:#1d2a26;">۰۹:۰۰</div></div></div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">جنسیت پرستار</div><div style="display:flex;gap:7px;"><div style="flex:1;text-align:center;border:1px solid #1d4a40;background:#e7efec;border-radius:9px;padding:8px 0;font:700 11px Vazirmatn;color:#1d4a40;">خانم</div><div style="flex:1;text-align:center;border:1px solid #d9d6cf;border-radius:9px;padding:8px 0;font:600 11px Vazirmatn;color:#9aa39e;">آقا</div><div style="flex:1;text-align:center;border:1px solid #d9d6cf;border-radius:9px;padding:8px 0;font:600 11px Vazirmatn;color:#9aa39e;">فرقی ندارد</div></div></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ارسال درخواست</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- C5 pending -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">C۵ · در انتظار تایید پرستار</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 18px;display:flex;flex-direction:column;align-items:center;text-align:center;">
<div style="margin-top:44px;width:84px;height:84px;border-radius:50%;background:#fbf3df;display:grid;place-items:center;font:700 30px Vazirmatn;color:#cf9b2c;"></div>
<div style="font:800 16px Vazirmatn;color:#123029;margin-top:20px;">درخواست برای پرستار ارسال شد</div>
<div style="font:500 12px Vazirmatn;color:#6b7280;margin-top:8px;line-height:1.9;">به‌محض تایید زهرا کریمی، برای پرداخت به شما اطلاع می‌دهیم.</div>
<div style="width:100%;border:1px solid #e6e3dd;border-radius:12px;padding:12px;margin-top:18px;text-align:right;"><div style="display:flex;justify-content:space-between;font:600 11px Vazirmatn;color:#52606d;margin-bottom:6px;"><span>پرستار</span><span style="color:#1d2a26;">زهرا کریمی</span></div><div style="display:flex;justify-content:space-between;font:600 11px Vazirmatn;color:#52606d;"><span>زمان</span><span style="color:#1d2a26;">۱۴۰۴/۰۴/۱۵ — ۰۹:۰۰</span></div></div>
<div style="width:100%;margin-top:18px;display:flex;flex-direction:column;gap:10px;text-align:right;">
<div style="display:flex;align-items:center;gap:9px;font:600 11px Vazirmatn;color:#1f6b50;"><span style="width:8px;height:8px;border-radius:50%;background:#1f6b50;"></span>درخواست ثبت شد</div>
<div style="display:flex;align-items:center;gap:9px;font:600 11px Vazirmatn;color:#8a6418;"><span style="width:8px;height:8px;border-radius:50%;background:#cf9b2c;"></span>در انتظار تایید پرستار</div>
<div style="display:flex;align-items:center;gap:9px;font:600 11px Vazirmatn;color:#9aa39e;"><span style="width:8px;height:8px;border-radius:50%;background:#b8b3a8;"></span>پرداخت و تایید نهایی</div>
</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- C6 summary + pay -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">C۶ · خلاصه و پرداخت</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;">
<div style="display:flex;align-items:center;gap:7px;"><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">✓ پرستار تایید کرد</span></div>
<div style="font:700 15px Vazirmatn;color:#123029;">تایید و پرداخت</div>
<div style="border:1px solid #e6e3dd;border-radius:12px;padding:13px;display:flex;flex-direction:column;gap:9px;">
<div style="display:flex;justify-content:space-between;font:600 11px Vazirmatn;color:#52606d;"><span>هزینه خدمت (۸ ساعت)</span><span style="color:#1d2a26;">۲٬۰۰۰٬۰۰۰</span></div>
<div style="display:flex;justify-content:space-between;font:600 11px Vazirmatn;color:#52606d;"><span>کارمزد پلتفرم</span><span style="color:#1d2a26;">۲۴۰٬۰۰۰</span></div>
<div style="display:flex;justify-content:space-between;font:600 11px Vazirmatn;color:#52606d;"><span>مالیات</span><span style="color:#1d2a26;">۹۰٬۰۰۰</span></div>
<div style="height:1px;background:#e6e3dd;margin:2px 0;"></div>
<div style="display:flex;justify-content:space-between;font:800 13px Vazirmatn;color:#123029;"><span>مبلغ کل</span><span>۲٬۳۳۰٬۰۰۰ تومان</span></div>
</div>
<div style="display:flex;gap:9px;background:#e7efec;border:1px solid #cfe0da;border-radius:11px;padding:11px;"><div style="width:22px;height:22px;border-radius:6px;background:#1d4a40;flex:none;display:grid;place-items:center;color:#fff;font:700 11px Vazirmatn;">🔒</div><div style="font:500 10.5px Vazirmatn;color:#1d4a40;line-height:1.8;">مبلغ به‌صورت امانی نزد بالین‌یار می‌ماند و پس از پایان ویزیت برای پرستار آزاد می‌شود.</div></div>
<div style="margin-top:auto;background:#1d4a40;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ادامه پرداخت ←</div>
</div>
</div>
</div>
</div>
<!-- ============ SECTION D — BNPL / INSTALLMENTS ============ -->
<div style="margin:48px 0 16px;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<div style="width:30px;height:30px;border-radius:8px;background:#d98c6a;color:#fff;display:grid;place-items:center;font:800 14px Vazirmatn;flex:none;">D</div>
<h2 style="margin:0;font:800 20px Vazirmatn;color:#1d2a26;">پرداخت اقساطی (BNPL)</h2>
<span style="font:500 13px Vazirmatn;color:#9aa39e;">دیجی‌پی · اسنپ‌پی · اقساط بالین‌یار</span>
</div>
<sc-if value="{{ showNotes }}" hint-placeholder-val="{{ true }}">
<div style="margin:11px 0 0;padding:9px 14px;background:#fbf0e8;border:1px dashed #d98c6a;border-radius:8px;font:500 12.5px Vazirmatn;color:#8a5a3c;max-width:840px;">یک روش پرداخت جایگزین. ارائه‌دهنده کل مبلغ را یک‌جا به بالین‌یار می‌پردازد و ریسک نکول مشتری کاملاً با اوست؛ بازپرداخت اقساط بین مشتری و ارائه‌دهنده است.</div>
</sc-if>
</div>
<div style="display:flex;gap:22px;align-items:flex-start;padding-bottom:8px;">
<!-- D1 method -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">D۱ · روش پرداخت</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;">
<div style="font:700 15px Vazirmatn;color:#123029;">روش پرداخت</div>
<div style="text-align:center;background:#f7faf9;border:1px solid #e6e3dd;border-radius:12px;padding:13px;"><div style="font:500 10px Vazirmatn;color:#9aa39e;">مبلغ قابل پرداخت</div><div style="font:800 19px Vazirmatn;color:#123029;margin-top:3px;">۲٬۳۳۰٬۰۰۰ تومان</div></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #d9d6cf;border-radius:11px;padding:12px;"><div style="width:36px;height:24px;border-radius:5px;background:#dde6e2;flex:none;"></div><div style="flex:1;font:700 12px Vazirmatn;color:#1d2a26;">پرداخت کامل (کارت بانکی)</div><div style="width:18px;height:18px;border-radius:50%;border:1px solid #c8c4ba;"></div></div>
<div style="font:700 11px Vazirmatn;color:#bf6f4d;margin-top:2px;">پرداخت اقساطی</div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #d98c6a;background:#fbf0e8;border-radius:11px;padding:12px;"><div style="width:36px;height:24px;border-radius:5px;background:#f0d2bf;flex:none;display:grid;place-items:center;font:800 9px Vazirmatn;color:#bf6f4d;">DG</div><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">دیجی‌پی</div><div style="font:500 9.5px Vazirmatn;color:#a87a5c;">۳ تا ۱۲ قسط</div></div><div style="width:18px;height:18px;border-radius:50%;border:5px solid #d98c6a;"></div></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #e6e3dd;border-radius:11px;padding:12px;"><div style="width:36px;height:24px;border-radius:5px;background:#eceae5;flex:none;display:grid;place-items:center;font:800 9px Vazirmatn;color:#52606d;">SP</div><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">اسنپ‌پی</div><div style="font:500 9.5px Vazirmatn;color:#9aa39e;">۴ قسط بدون سود</div></div><div style="width:18px;height:18px;border-radius:50%;border:1px solid #c8c4ba;"></div></div>
<div style="display:flex;align-items:center;gap:10px;border:1px solid #e6e3dd;border-radius:11px;padding:12px;"><div style="width:36px;height:24px;border-radius:5px;background:#dde6e2;flex:none;display:grid;place-items:center;font:800 11px Vazirmatn;color:#1d4a40;">ب</div><div style="flex:1;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">اقساط بالین‌یار</div><div style="font:500 9.5px Vazirmatn;color:#9aa39e;">طرح داخلی</div></div><div style="width:18px;height:18px;border-radius:50%;border:1px solid #c8c4ba;"></div></div>
<div style="margin-top:auto;background:#d98c6a;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ادامه با دیجی‌پی</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- D2 plan -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">D۲ · انتخاب طرح اقساط</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;">
<div style="display:flex;align-items:center;gap:8px;"><div style="width:32px;height:22px;border-radius:5px;background:#f0d2bf;display:grid;place-items:center;font:800 9px Vazirmatn;color:#bf6f4d;">DG</div><span style="font:700 14px Vazirmatn;color:#123029;">طرح اقساط دیجی‌پی</span></div>
<div style="display:flex;justify-content:space-between;font:600 11px Vazirmatn;color:#52606d;background:#f7faf9;border-radius:9px;padding:9px 11px;"><span>مبلغ کل</span><span style="color:#1d2a26;font-weight:800;">۲٬۳۳۰٬۰۰۰</span></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:12px;display:flex;justify-content:space-between;align-items:center;"><div><div style="font:700 12px Vazirmatn;color:#1d2a26;">۳ ماهه</div><div style="font:500 10px Vazirmatn;color:#9aa39e;">بدون سود</div></div><div style="text-align:left;"><div style="font:800 13px Vazirmatn;color:#1d4a40;">۷۷۷٬۰۰۰</div><div style="font:500 9px Vazirmatn;color:#9aa39e;">ماهانه</div></div></div>
<div style="border:2px solid #d98c6a;background:#fbf0e8;border-radius:11px;padding:12px;display:flex;justify-content:space-between;align-items:center;"><div><div style="font:700 12px Vazirmatn;color:#1d2a26;">۶ ماهه</div><div style="font:500 10px Vazirmatn;color:#bf6f4d;">کارمزد ۴٪</div></div><div style="text-align:left;"><div style="font:800 13px Vazirmatn;color:#bf6f4d;">۴۰۴٬۰۰۰</div><div style="font:500 9px Vazirmatn;color:#a87a5c;">ماهانه</div></div></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:12px;display:flex;justify-content:space-between;align-items:center;"><div><div style="font:700 12px Vazirmatn;color:#1d2a26;">۱۲ ماهه</div><div style="font:500 10px Vazirmatn;color:#9aa39e;">کارمزد ۹٪</div></div><div style="text-align:left;"><div style="font:800 13px Vazirmatn;color:#1d4a40;">۲۱۲٬۰۰۰</div><div style="font:500 9px Vazirmatn;color:#9aa39e;">ماهانه</div></div></div>
<div style="display:flex;flex-direction:column;gap:6px;margin-top:2px;"><div style="display:flex;justify-content:space-between;font:600 10px Vazirmatn;color:#52606d;"><span>پیش‌پرداخت</span><span style="color:#1d2a26;">۲۰٪</span></div><div style="height:6px;border-radius:3px;background:#e6e3dd;"><div style="width:20%;height:100%;border-radius:3px;background:#d98c6a;"></div></div></div>
<div style="margin-top:auto;background:#d98c6a;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">ادامه</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- D3 eligibility -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">D۳ · اعتبارسنجی</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 15px Vazirmatn;color:#123029;">اعتبارسنجی</div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">کد ملی</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 12px Vazirmatn;color:#1d2a26;background:#fafafa;">۰۰۱۲۳۴۵۶۷۸</div></div>
<div style="display:flex;flex-direction:column;gap:5px;"><div style="font:600 11px Vazirmatn;color:#52606d;">شماره موبایل</div><div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 12px Vazirmatn;color:#1d2a26;background:#fafafa;">۰۹۱۲ ۰۰۰ ۰۰۰۰</div></div>
<div style="display:flex;align-items:flex-start;gap:9px;border:1px solid #e6e3dd;border-radius:11px;padding:11px;"><div style="width:18px;height:18px;border-radius:5px;background:#1d4a40;flex:none;display:grid;place-items:center;color:#fff;font:700 11px Vazirmatn;"></div><div style="font:500 10.5px Vazirmatn;color:#6b7280;line-height:1.8;">با استعلام اعتبارسنجی و سابقه اعتباری من توسط دیجی‌پی موافقم.</div></div>
<div style="border:1px solid #cfe0da;background:#f3f8f6;border-radius:12px;padding:14px;text-align:center;"><div style="display:flex;align-items:center;justify-content:center;gap:7px;"><span style="width:9px;height:9px;border-radius:50%;background:#1f6b50;"></span><span style="font:700 12px Vazirmatn;color:#1f6b50;">اعتبار شما تایید شد</span></div><div style="font:500 10px Vazirmatn;color:#52606d;margin-top:6px;">سقف اعتبار قابل استفاده</div><div style="font:800 16px Vazirmatn;color:#123029;margin-top:2px;">۱۵٬۰۰۰٬۰۰۰ تومان</div></div>
<div style="margin-top:auto;background:#d98c6a;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">تایید و ادامه</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- D4 contract -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">D۴ · تایید طرح و قرارداد</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:10px;">
<div style="font:700 15px Vazirmatn;color:#123029;">جدول بازپرداخت</div>
<div style="display:flex;justify-content:space-between;font:600 10px Vazirmatn;color:#9aa39e;padding:0 4px;"><span>قسط</span><span>سررسید</span><span>مبلغ</span></div>
<div style="border:1px solid #e6e3dd;border-radius:10px;padding:10px 11px;display:flex;justify-content:space-between;align-items:center;"><span style="font:700 11px Vazirmatn;color:#1d2a26;">پیش‌پرداخت</span><span style="font:600 10px Vazirmatn;color:#52606d;">امروز</span><span style="font:700 11px Vazirmatn;color:#bf6f4d;">۴۶۶٬۰۰۰</span></div>
<div style="border:1px solid #e6e3dd;border-radius:10px;padding:10px 11px;display:flex;justify-content:space-between;align-items:center;"><span style="font:700 11px Vazirmatn;color:#1d2a26;">قسط ۱</span><span style="font:600 10px Vazirmatn;color:#52606d;">۱۴۰۴/۰۵/۱۵</span><span style="font:700 11px Vazirmatn;color:#1d2a26;">۴۰۴٬۰۰۰</span></div>
<div style="border:1px solid #e6e3dd;border-radius:10px;padding:10px 11px;display:flex;justify-content:space-between;align-items:center;"><span style="font:700 11px Vazirmatn;color:#1d2a26;">قسط ۲</span><span style="font:600 10px Vazirmatn;color:#52606d;">۱۴۰۴/۰۶/۱۵</span><span style="font:700 11px Vazirmatn;color:#1d2a26;">۴۰۴٬۰۰۰</span></div>
<div style="border:1px solid #e6e3dd;border-radius:10px;padding:10px 11px;display:flex;justify-content:space-between;align-items:center;opacity:.7;"><span style="font:700 11px Vazirmatn;color:#1d2a26;"></span><span style="font:600 10px Vazirmatn;color:#52606d;">تا قسط ۶</span><span style="font:700 11px Vazirmatn;color:#1d2a26;">۴۰۴٬۰۰۰</span></div>
<div style="display:flex;align-items:flex-start;gap:9px;border:1px solid #e6e3dd;border-radius:11px;padding:11px;margin-top:2px;"><div style="width:18px;height:18px;border-radius:5px;background:#1d4a40;flex:none;display:grid;place-items:center;color:#fff;font:700 11px Vazirmatn;"></div><div style="font:500 10px Vazirmatn;color:#6b7280;line-height:1.8;">شرایط و قرارداد اقساط را خوانده‌ام و می‌پذیرم.</div></div>
<div style="margin-top:auto;background:#d98c6a;color:#fff;border-radius:12px;padding:13px;text-align:center;font:700 13px Vazirmatn;">تایید نهایی و پرداخت پیش‌پرداخت</div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- D5 repayment tracking -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">D۵ · پیگیری اقساط (کیف‌پول)</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;overflow:hidden;">
<div style="font:700 15px Vazirmatn;color:#123029;">کیف‌پول و اقساط</div>
<div style="background:#123029;border-radius:14px;padding:14px;color:#f3efe9;"><div style="font:500 10px Vazirmatn;color:#9fb0a9;">مانده بدهی اقساط</div><div style="font:800 20px Vazirmatn;margin-top:3px;">۱٬۶۱۶٬۰۰۰ تومان</div><div style="display:flex;justify-content:space-between;margin-top:11px;align-items:center;"><div><div style="font:500 9px Vazirmatn;color:#9fb0a9;">قسط بعدی · ۱۴۰۴/۰۶/۱۵</div><div style="font:700 13px Vazirmatn;">۴۰۴٬۰۰۰</div></div><div style="background:#d98c6a;color:#fff;border-radius:9px;padding:8px 13px;font:700 11px Vazirmatn;">پرداخت زودهنگام</div></div></div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;">سررسیدها</div>
<div style="border:1px solid #cfe0da;background:#f3f8f6;border-radius:11px;padding:11px;display:flex;align-items:center;gap:10px;"><div style="width:22px;height:22px;border-radius:50%;background:#1f6b50;color:#fff;display:grid;place-items:center;font:700 10px Vazirmatn;flex:none;"></div><div style="flex:1;"><div style="font:700 11px Vazirmatn;color:#1d2a26;">قسط ۱ · ۱۴۰۴/۰۵/۱۵</div></div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#e4f0ea;color:#1f6b50;">پرداخت‌شده</span></div>
<div style="border:1px solid #ecc9b3;background:#fbf3df;border-radius:11px;padding:11px;display:flex;align-items:center;gap:10px;"><div style="width:22px;height:22px;border-radius:50%;border:2px solid #cf9b2c;flex:none;"></div><div style="flex:1;"><div style="font:700 11px Vazirmatn;color:#1d2a26;">قسط ۲ · ۱۴۰۴/۰۶/۱۵</div></div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#fbf3df;color:#8a6418;">سررسید نزدیک</span></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:11px;display:flex;align-items:center;gap:10px;opacity:.8;"><div style="width:22px;height:22px;border-radius:50%;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="font:700 11px Vazirmatn;color:#52606d;">قسط ۳ · ۱۴۰۴/۰۷/۱۵</div></div><span style="font:700 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#eef0ee;color:#9aa39e;">آینده</span></div>
</div>
<div style="flex:none;display:flex;border-top:1px solid #eceae5;background:#fff;padding:7px 2px;">
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">خانه</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">رزروها</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">بیماران</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#1d4a40;"></div><span style="font:700 8.5px Vazirmatn;color:#1d4a40;">کیف‌پول</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">پروفایل</span></div>
</div>
</div>
</div>
</div>
<!-- ============ SECTION E — PATIENT PROFILE ============ -->
<div style="margin:48px 0 16px;">
<div style="display:flex;align-items:center;gap:12px;">
<div style="width:30px;height:30px;border-radius:8px;background:#1d4a40;color:#fff;display:grid;place-items:center;font:800 14px Vazirmatn;flex:none;">E</div>
<h2 style="margin:0;font:800 20px Vazirmatn;color:#1d2a26;">پرونده بیمار</h2>
<span style="font:500 13px Vazirmatn;color:#9aa39e;">Living patient profile</span>
</div>
<sc-if value="{{ showNotes }}" hint-placeholder-val="{{ true }}">
<div style="margin:11px 0 0;padding:9px 14px;background:#fbf0e8;border:1px dashed #d98c6a;border-radius:8px;font:500 12.5px Vazirmatn;color:#8a5a3c;max-width:840px;">پرونده متعلق به خانواده است و با تعویض پرستار حفظ می‌شود. پرستار فقط می‌تواند یادداشت ویزیت و مشاهدات اضافه کند (نه ویرایش کامل).</div>
</sc-if>
</div>
<div style="display:flex;gap:22px;align-items:flex-start;padding-bottom:8px;">
<!-- E1 patients list -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">E۱ · لیست بیماران</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:12px;">
<div style="font:700 16px Vazirmatn;color:#123029;">بیماران من</div>
<div style="border:1px solid #cfe0da;border-radius:13px;padding:13px;display:flex;gap:12px;"><div style="width:50px;height:50px;border-radius:13px;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="font:800 13px Vazirmatn;color:#1d2a26;">پدر — حسن رضایی</div><div style="font:500 10.5px Vazirmatn;color:#6b7280;margin-top:3px;">۷۲ ساله · مرد</div><div style="display:flex;gap:5px;margin-top:6px;"><span style="font:600 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#e7efec;color:#1d4a40;">سالمند</span><span style="font:600 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#f4f2ec;color:#6b7280;">دیابت</span></div></div></div>
<div style="border:1px solid #e6e3dd;border-radius:13px;padding:13px;display:flex;gap:12px;"><div style="width:50px;height:50px;border-radius:13px;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="font:800 13px Vazirmatn;color:#1d2a26;">مادر — فاطمه رضایی</div><div style="font:500 10.5px Vazirmatn;color:#6b7280;margin-top:3px;">۶۸ ساله · زن</div><div style="display:flex;gap:5px;margin-top:6px;"><span style="font:600 9px Vazirmatn;padding:2px 8px;border-radius:20px;background:#f4f2ec;color:#6b7280;">پس از جراحی</span></div></div></div>
<div style="border:1px dashed #cfe0da;border-radius:13px;padding:14px;text-align:center;font:700 12px Vazirmatn;color:#1d4a40;">+ افزودن بیمار</div>
</div>
<div style="flex:none;display:flex;border-top:1px solid #eceae5;background:#fff;padding:7px 2px;">
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">خانه</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">رزروها</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#1d4a40;"></div><span style="font:700 8.5px Vazirmatn;color:#1d4a40;">بیماران</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">کیف‌پول</span></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;"><div style="width:17px;height:17px;border-radius:5px;background:#d4d1c9;"></div><span style="font:600 8.5px Vazirmatn;color:#9aa39e;">پروفایل</span></div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- E2 patient profile -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">E۲ · پرونده بیمار</div>
<div style="width:288px;height:600px;background:#fff;border:1px solid #d9d6cf;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;overflow:hidden;">
<div style="display:flex;align-items:center;gap:11px;"><div style="width:46px;height:46px;border-radius:12px;background:#eceae5;flex:none;"></div><div style="flex:1;"><div style="font:800 14px Vazirmatn;color:#123029;">پدر — حسن رضایی</div><div style="font:500 10px Vazirmatn;color:#6b7280;">۷۲ ساله · مرد · دیابت، فشار خون</div></div><div style="font:600 11px Vazirmatn;color:#1d4a40;">ویرایش</div></div>
<div style="display:flex;gap:4px;background:#f4f2ec;border-radius:10px;padding:3px;"><div style="flex:1;text-align:center;background:#fff;border-radius:8px;padding:7px 0;font:700 10.5px Vazirmatn;color:#1d4a40;box-shadow:0 1px 3px rgba(0,0,0,.06);">داروها</div><div style="flex:1;text-align:center;padding:7px 0;font:600 10.5px Vazirmatn;color:#9aa39e;">روتین</div><div style="flex:1;text-align:center;padding:7px 0;font:600 10.5px Vazirmatn;color:#9aa39e;">سوابق</div><div style="flex:1;text-align:center;padding:7px 0;font:600 10.5px Vazirmatn;color:#9aa39e;">وظایف</div></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:11px;"><div style="display:flex;justify-content:space-between;align-items:center;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">متفورمین ۵۰۰</div><span style="font:600 9px Vazirmatn;color:#1d4a40;">۲ بار در روز</span></div><div style="font:500 10px Vazirmatn;color:#6b7280;margin-top:4px;">صبح ۰۸:۰۰ · شب ۲۰:۰۰ — همراه غذا</div></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:11px;"><div style="display:flex;justify-content:space-between;align-items:center;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">لوزارتان ۲۵</div><span style="font:600 9px Vazirmatn;color:#1d4a40;">۱ بار در روز</span></div><div style="font:500 10px Vazirmatn;color:#6b7280;margin-top:4px;">صبح ۰۸:۰۰ — کنترل فشار خون</div></div>
<div style="border:1px solid #e6e3dd;border-radius:11px;padding:11px;"><div style="display:flex;justify-content:space-between;align-items:center;"><div style="font:700 12px Vazirmatn;color:#1d2a26;">انسولین</div><span style="font:600 9px Vazirmatn;color:#8a6418;">حسب قند خون</span></div><div style="font:500 10px Vazirmatn;color:#6b7280;margin-top:4px;">پیش از وعده‌ها — طبق دستور پزشک</div></div>
<div style="display:flex;align-items:center;gap:8px;background:#e7efec;border-radius:9px;padding:9px 11px;margin-top:auto;"><span style="width:8px;height:8px;border-radius:50%;background:#1d4a40;flex:none;"></span><span style="font:600 9.5px Vazirmatn;color:#1d4a40;">این پرونده متعلق به خانواده است و با تعویض پرستار حفظ می‌شود.</span></div>
</div>
</div>
</div>
<div style="flex:none;align-self:center;color:#b8b3a8;font:300 26px Vazirmatn;"></div>
<!-- E3 nurse visit note -->
<div style="flex:none;width:288px;">
<div style="font:600 12px Vazirmatn;color:#52606d;margin-bottom:9px;text-align:center;">E۳ · ثبت یادداشت ویزیت <span style="color:#bf6f4d;">(نمای پرستار)</span></div>
<div style="width:288px;height:600px;background:#fff;border:2px solid #d98c6a;border-radius:26px;box-shadow:0 8px 24px rgba(20,30,28,.08);overflow:hidden;display:flex;flex-direction:column;">
<div style="height:28px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font:600 10px Vazirmatn;color:#1d2a26;"><span>۹:۴۱</span><span style="letter-spacing:1px;">▦ ▮▮▮</span></div>
<div style="flex:1;padding:16px 16px;display:flex;flex-direction:column;gap:11px;overflow:hidden;">
<div style="font:700 14px Vazirmatn;color:#123029;">ویزیت امروز — پدرِ مریم</div>
<div style="display:flex;align-items:center;gap:8px;background:#e4f0ea;border-radius:9px;padding:9px 11px;"><span style="width:8px;height:8px;border-radius:50%;background:#1f6b50;flex:none;"></span><span style="font:700 10px Vazirmatn;color:#1f6b50;">ورود ثبت شد ۰۹:۰۲ · موقعیت تایید شد (EVV)</span></div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;">وظایف امروز</div>
<div style="display:flex;align-items:center;gap:9px;border:1px solid #e6e3dd;border-radius:10px;padding:9px 11px;"><div style="width:18px;height:18px;border-radius:5px;background:#1f6b50;flex:none;display:grid;place-items:center;color:#fff;font:700 10px Vazirmatn;"></div><span style="font:600 11px Vazirmatn;color:#1d2a26;">داروی متفورمین ساعت ۱۰</span></div>
<div style="display:flex;align-items:center;gap:9px;border:1px solid #e6e3dd;border-radius:10px;padding:9px 11px;"><div style="width:18px;height:18px;border-radius:5px;border:1px solid #c8c4ba;flex:none;"></div><span style="font:600 11px Vazirmatn;color:#52606d;">اندازه‌گیری فشار خون</span></div>
<div style="display:flex;align-items:center;gap:9px;border:1px solid #e6e3dd;border-radius:10px;padding:9px 11px;"><div style="width:18px;height:18px;border-radius:5px;border:1px solid #c8c4ba;flex:none;"></div><span style="font:600 11px Vazirmatn;color:#52606d;">پیاده‌روی کوتاه</span></div>
<div style="font:700 12px Vazirmatn;color:#1d2a26;margin-top:2px;">یادداشت ویزیت</div>
<div style="border:1px solid #d9d6cf;border-radius:10px;padding:10px 12px;font:400 11px Vazirmatn;color:#9aa39e;background:#fafafa;min-height:46px;">وضعیت بیمار، مشاهدات و توصیه‌ها…</div>
<div style="display:flex;gap:9px;margin-top:auto;"><div style="flex:1;border:1px solid #1d4a40;color:#1d4a40;border-radius:11px;padding:11px;text-align:center;font:700 11px Vazirmatn;">ثبت خروج (EVV)</div><div style="flex:1;background:#d98c6a;color:#fff;border-radius:11px;padding:11px;text-align:center;font:700 11px Vazirmatn;">ثبت یادداشت</div></div>
</div>
</div>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;showNotes&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:true,&quot;tsType&quot;:&quot;boolean&quot;}}">
class Component extends DCLogic {
renderVals() {
return { showNotes: this.props.showNotes ?? true };
}
}
</script>
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
1.(برای آینده) فلوی قیمت باید سمت ما باشه:
- ما وقتی فرد میخواد دستمزد ساعتی دریافتی و روزانه اش رو مشخص کنه باید یه رنجی از بازار بهش نشون بدیم بنظرم و چندتا نکته بنویسیم اگر زیاد گذاشت یا کم که آقا رنج مناسبی نذاشتی و بگیم که مشتریات کم میشه + از بابت اینکه نسبت به سابقه و مهارت ها و ... میتونیم یه عدد پیشنهادی تو بازه بهش نشون بدیم که کار رو راحت تر کنه
- در آینده باید داستان بوست رو برای هر دو طرف مشخص کنیم یعنی اگر فرد بخواد زودتر کسی رو پیدا کنه، چه پرستار بخواد زودتر کار بگیره (مرحله خیلی بعد)
- کلا پروسه بید زدن پرستار هارو بزاریم برای بعدا چون مشتری رو ناراضی میکنه الان وقتش نیست
2. سیستم اقساطی باید بصرفه یعنی نباید برای یک روز ما خدمات قساطی بدیم، برای درخواستی بالای سه روز باید باشه که عددش معنی دار بشه شاید هم حتی بالاتر - کسی نمیاد 4-5 تومن رو قطی بده دیگه (این گمان هست باید دیتا بیشتر ببینم)
پس نیاز داریم که بتونیم برای بازه های قیمتی متفاوت گزینه های پرداخت رو فعال یا غیر فعال کنیم.
3. برای آینده، یک بلاگ در نظر داشته باشیم
File diff suppressed because it is too large Load Diff
+2 -33
View File
@@ -1,33 +1,2 @@
add no unused var rule to client lint rules and agent rules, clean the product folder to have strcutured folder for data and not a huge file, so information about each part could be found easily. so the agent should read the docs, categories information then format files and folder and isolate information about the product and then create seprate html doc for it and if two parts are related just link them together in proper place.
for both projects, and eliminate duplicate information.
read agent specific files, and if it's not specified, specify a place which define project arcitecture and also add rules for subsequent agents to update that file, if their task changes the project in a way that the description should be updated
=======================================================================
the auth flow should be revised,
we send the cookie to the server which contains the auth cookies, so we can pass that down as props also and use it as initial data for the AppStore, also the AppStore name should change to AuthContext, thats better
and the flow of authentication and authorization should be reviewed to ensure it is configured with regaurd to best practices,
=======================================================================
rules should be added to the projects for agents to not to add verbose comment, if some where there is a really decision made that the code does not tells us ( me and agent) why that piece of code is like that,
comment should be added
===================================================================
in client project if there is still javascript code, rewrite it with ts, with reguard to type rules, no type error and mismatch
=====================================================================================
in product folder, read all the docs, and extract the full information without summarizing and skipping data ( obviousely you can skip duplicate data), and create a single file,
which is a coprehensive step by step explaination to the bussiness and data model with clear descriptions.
write it in as html file with styles matched to the client project theme,
just a single file, not more. so, long story short:
# do not skip or ignore data
# clear and comprehensive step by step explanation
# map each step of the bussines and its description to the data model.
# at the end of the file consider a whole section for all data models together with their realtions and the diagram( you can use canvas or anyhting that you can attach using cdn in the file)
+16 -1
View File
@@ -63,14 +63,21 @@ Server is required to start.
## Quality gates — run before declaring work done ## Quality gates — run before declaring work done
1. `dotnet build Baya.sln` — zero new warnings introduced. 1. `dotnet build Baya.sln` — zero new warnings introduced. Unused `using`s, locals, parameters,
private fields, or members count as failures — delete them, don't suppress them
([CONVENTIONS.md](CONVENTIONS.md) §2 "No unused code").
2. `dotnet test Baya.sln` — all tests pass. 2. `dotnet test Baya.sln` — all tests pass.
3. Read your own diff as if reviewing a PR: would a senior engineer approve it without comment? 3. Read your own diff as if reviewing a PR: would a senior engineer approve it without comment?
4. If the change alters the architecture, update the **Project map** below in the same change
(see "Keeping the Project map current").
--- ---
## Project map ## Project map
This tree is the **canonical description of the server's architecture** — the authoritative list of
projects/assemblies, Clean-Architecture layers, and cross-layer dependencies.
``` ```
src/ src/
├── Core/ ├── Core/
@@ -95,6 +102,12 @@ src/
Domain. Infrastructure and API implement/consume Application contracts. Never make Domain or Domain. Infrastructure and API implement/consume Application contracts. Never make Domain or
Application reference Infrastructure or the API — this is a hard rule. Application reference Infrastructure or the API — this is a hard rule.
**Keeping the Project map current.** When a change touches the architecture — adds, removes, or
renames a project/assembly, a Clean-Architecture layer, or a major folder, or changes a cross-layer
dependency — you **must** update this Project map (and the dependency rule above, if affected) in the
**same** change. This is the server-specific form of the root "Keep docs honest" rule: the map is
only canonical if it stays accurate.
--- ---
## Startup wiring ## Startup wiring
@@ -179,6 +192,8 @@ Full rules in [CONVENTIONS.md](CONVENTIONS.md). The essentials:
- `async`/`await` all the way; pass `CancellationToken` through every async call; never `.Result`/`.Wait()`/`async void`. - `async`/`await` all the way; pass `CancellationToken` through every async call; never `.Result`/`.Wait()`/`async void`.
- Mapster for mapping; FluentValidation for validation (validate at the boundary). - Mapster for mapping; FluentValidation for validation (validate at the boundary).
- Package versions live **only** in `Directory.Packages.props` — never `Version=` in a `.csproj`. - Package versions live **only** in `Directory.Packages.props` — never `Version=` in a `.csproj`.
- No unused code (usings, locals, parameters, private fields/members) and no *what*-comments — explain *why*, prefer self-documenting names (§2).
- Architecture changes (a project/layer/major folder or a cross-layer dependency) must update the **Project map** in the same change.
- The `Baya.*` namespace is project naming — do not rename without explicit instruction. - The `Baya.*` namespace is project naming — do not rename without explicit instruction.
--- ---
+26
View File
@@ -94,6 +94,32 @@ List<string> tags = ["new", "sale"];
No abbreviations unless universally understood (`dto`, `id`, `url`). No Hungarian notation (`strName`, `intCount`). No abbreviations unless universally understood (`dto`, `id`, `url`). No Hungarian notation (`strName`, `intCount`).
### No unused code
Leave nothing dead behind. Remove unused `using` directives, local variables, parameters, private fields, and private members rather than letting them accumulate.
- These already surface as compiler/analyzer signals — `CS0168` (variable declared, never used), `CS0219` (variable assigned, value never used), `CS0169` (private field never used), `IDE0005` (unnecessary `using`). The quality gate is **zero new warnings**, so treat unused code as a gate failure.
- **Delete it — don't silence it.** Do not add `#pragma warning disable`, throwaway discards, or `_ =` assignments just to quiet the analyzer.
- The one exception: a parameter that must exist to satisfy an interface or delegate signature but is genuinely unused. Keep it, name it conventionally, and add a one-line `// why` only if the reason isn't obvious.
### Comments — explain *why*, never *what*
Code that needs a comment to be understood usually needs a better name instead. Prefer self-documenting names over prose.
- **Do not** write comments that restate what the code already says — no `// constructor`, `// loop over users`, or XML-doc that merely echoes the method name.
- **Do** add a comment only where a non-obvious decision, constraint, business rule, workaround, or trade-off is *not* evident from the code — explain the reasoning, not the mechanics.
- Keep any necessary comment tight, and delete comments that no longer match the code.
```csharp
// ❌ restates the obvious
// increment the retry counter
retryCount++;
// ✅ captures a non-obvious constraint the code can't express on its own
// Payment gateway rejects amounts above 50M IRR per call; split larger settlements upstream.
if (amount > MaxPerCallRial) ...
```
--- ---
## 3. Async / await ## 3. Async / await