diff --git a/client/CLAUDE.md b/client/CLAUDE.md index b89f954..e8ea544 100644 --- a/client/CLAUDE.md +++ b/client/CLAUDE.md @@ -105,13 +105,15 @@ client/ ├── messages/ # Translation files (add keys to BOTH files) │ ├── en.json │ └── fa.json -├── middleware.ts # next-intl routing middleware (locale detection + redirect) +├── middleware.ts # next-intl routing middleware (locale detection + redirect) + (ui-phase-13) the guest front-door: after the i18n 307/308 early-return, an UNAUTHENTICATED exact-match on '/' is `NextResponse.rewrite()`d to `/{locale}/welcome` (never a redirect, so the URL/SEO canonical stays '/'); an AUTHENTICATED hit on `/welcome` redirects to '/'. The bare-root matcher entry (`'/'`, alongside the existing catch-all regex) is load-bearing — this Next 16/Turbopack build did not reliably invoke middleware for the literal root through the negative-lookahead pattern alone (verified in dev; a production `next build && next start` confirmed the intended behavior end-to-end, so this is a dev-server-only quirk, not a logic bug) ├── next.config.mjs # createNextIntlPlugin wires i18n into Next.js └── src/ ├── app/ │ ├── globals.css │ ├── fonts/ # Local font files (woff2) — Mikhak for fa │ ├── global-error.tsx # Special file above [locale] — replaces the root layout on a root-level crash; renders its own , so it CANNOT use next-intl. The one sanctioned static-string exception (minimal, bilingual fa+en). + │ ├── robots.ts # (ui-phase-13) replaces the old static `public/robots.txt` (deleted) — allows the public surface, disallows every private route root for every locale, points at sitemap.xml + │ ├── sitemap.ts # (ui-phase-13) public routes only ('/', /login, /terms, /privacy) × both locales, with hreflang alternates │ └── [locale]/ │ ├── layout.tsx # ROOT RSC: renders + fonts + setRequestLocale + NextIntlClientProvider + ThemeProvider + AuthProvider (seeded via getServerAuthState) + generateMetadata (the '%s | برند' title template) │ ├── error.tsx # Branded, localized error boundary for the whole [locale] segment — reset() retries, a "go home" link escapes @@ -222,7 +224,11 @@ client/ │ │ ├── page.tsx # Thin RSC — generateMetadata (auth.customer_title) + renders LoginScreen │ │ └── LoginScreen.tsx # 'use client' — the actual LoginFlow body │ ├── terms/page.tsx # /terms — draft Terms of Service (ui-phase-3; DRAFT COPY, needs human/legal review before launch) - │ └── privacy/page.tsx # /privacy — draft Privacy Policy (ui-phase-3; DRAFT COPY, needs human/legal review before launch) + │ ├── privacy/page.tsx # /privacy — draft Privacy Policy (ui-phase-3; DRAFT COPY, needs human/legal review before launch) + │ └── welcome/ # /welcome — ui-phase-13 public front door; middleware REWRITES an unauthenticated '/' here (URL stays '/'), so this is what a guest, and every crawler, actually sees at the root + │ ├── page.tsx # Thin RSC — generateMetadata (description/OG/`alternates.canonical` pointing at `/{locale}`; no `title` override, so it inherits the root layout's default "بالینیار"/"Balinyaar" — that's how it's told apart from the customer home's own `shell.customer_app` title in view-source) + renders WelcomeScreen + │ ├── WelcomeScreen.tsx # Async Server Component (no 'use client') — hero (BrandMark) + static category grid (CategoryTile via its new `href` prop, §3.1 tier a+b — NOT the live catalog) + how-it-works (step 2 reuses EscrowNotice verbatim) + a static trust/verification explainer + nurse-recruitment CTA + footer; zero query hooks anywhere in the tree — first paint is the whole page + │ └── opengraph-image.tsx # `next/og` ImageResponse — a static brand-mark composition (Latin-only; Persian would need an embedded Mikhak font buffer, left for a follow-up) on the brand teal/cream, served as the page's absolute `og:image` ├── components/ # Shared UI components (each with .test.tsx if imported >1 place) │ ├── common/ # Foundational primitives (import from @/components or @/components/common) │ │ ├── AppButton/, AppIconButton/, AppIcon/, AppLink/, AppAlert/, AppLoading/ # house-default MUI wrappers (see frontend-designer skill §4) @@ -258,7 +264,7 @@ client/ │ ├── PatientForm/ # A4 patient form (first/last name, age/gender/conditions/relation) — reused create+edit; ui-phase-9 split the single full-name field into first/last (lastName falls back to firstName when blank — the wire requires it) and added an `onDirtyChange` prop for `FormDialogShell`'s discard-confirm │ ├── PatientCard/ # E1 care-circle summary card (composes the shared PatientHeader, now with its avatar) + edit/archive actions; ui-phase-9 replaced the invisible tap target with a pressable `ButtonBase` surface (hover/press + a trailing chevron) and added an optional `lastVisitLabel` teaser (never populated without a `patientId` on the cached bookings list — REQ-057) │ ├── BankStatusPanel/ # Nurse bank-account ownership state (pending/verified/mismatch), masked IBAN - │ ├── CategoryTile/ # f4 tappable service-category tile (icon+label; `selected` state for the builder) — Home grid + builder step 1 (tested) + │ ├── CategoryTile/ # f4 tappable service-category tile (icon+label; `selected` state for the builder) — Home grid + builder step 1; ui-phase-13 added an `href` mode (renders as the anchor itself via `ButtonBase`'s `component` swap to `AppLink` — never a button nested inside a link) for the public landing's static category grid (tested) │ ├── PriceDisplay/ # f4 price renderer: money-util Toman + i18n unit label + unit-aware estimated total (never a total from price alone) (tested) │ ├── VariantCard/ # f4 nurse offering card: display_name, PriceDisplay, active/deactivated distinction, edit/deactivate (no delete); ui-phase-8 added `interactive={false}` for a read-only preview use (the builder's live listing preview + the profile preview) (tested) │ ├── ActivationChecklist/ # ui-phase-8 — the unified go-live tracker (`useActivationChecklist` hook, self-fetching): five already-cached queries folded into rows — two-tier honesty (identity/profile/services/coverage drive search visibility; bank drives "getting paid", labelled separately and never gates search) — collapses to a compact «فعال در جستجو» state once everything passes AND accepting-bookings is on; mounted on `/nurse/services` and the dashboard's `DashboardActivationSlot`, one shared component; `useActivationChecklist` is also consumed directly by `PublishGate` so the go-live gate and the checklist never compute the conditions twice (tested) @@ -430,6 +436,12 @@ root template; `page.tsx` itself never renders `
nurse_clawbacks with next-batch netting and write-off; unique booking↔payout link; iranian_holidays-aware scheduling; verified-IBAN payouts with reconciliation references.These fill gaps the requirements left open; the payout engine was built to them:
+nurse_clawbacks row is atomic — it is recovered in full or not at all — so a batch nets the largest set of whole pending clawbacks (oldest first) that fits within the nurse's earnings that week; net_amount = gross_earnings − clawback_applied ≥ 0 (never negative). A single clawback larger than a batch's earnings stays fully pending and recovers from a later, larger batch (it is not partially recovered). The recovery is a real ledger movement (DEBIT nurse_payable / CREDIT nurse_clawback_receivable), not just a status flag, so the derived balances reconcile.
reversed, so paying its frozen nurse_payout_amount would overpay. It is excluded until resolved (this is the operational reading of "no open dispute", since there is no separate dispute table yet).
payout_satna_threshold_irr config (SATNA for net amounts at/abovethe threshold, else PAYA).
+require_bnpl_settlement_for_payout (config, default off) — when on, a BNPL-paid booking is payout-eligible only once its provider settlement (settled_at) is received.
nurse_payout_interval_days (default 7) for the future scheduler.
nurse_payout_batches, nurse_payouts (with gross_earnings_irr, clawback_applied_irr, net_amount_irr, iban_snapshot), nurse_payout_booking_links (unique per booking), nurse_clawbacks, ledger_entries, iranian_holidays, bookings.dispute_window_ends_at, nurse_bank_accounts.
Related: Data model — Payouts.
diff --git a/product/notes/open-questions.html b/product/notes/open-questions.html index c1c3723..31308b5 100644 --- a/product/notes/open-questions.html +++ b/product/notes/open-questions.html @@ -48,6 +48,33 @@(Under BNPL, who pays the nurse?) → Resolved. Balinyaar pays the nurse, on its own weekly schedule, from
gross − balinyaar_commission— identical to a card booking; the BNPL commission is a platform expense, never the nurse's. See Q2 — who pays the nurse & when.
See also the launch-blocking items to confirm with counsel / providers: research open questions and data-model open items.
+
+Decided — public guest-browse depth (ui-phase-13, 2026-07-19) #
+Question: how deep should the anonymous (logged-out) web experience go, now that the entire public surface is a bare
+/login?Three tiers were framed:
++
+- (a) Landing only — a marketing page at
+/; zero data, zero new endpoints.- (b) Landing + static public pages — (a) plus category/how-it-works/trust content; still zero
+data (a static i18n category grid, not the live catalog).
++
+- (c) Guest search + public nurse profiles — read-only anonymous variants of the search-results
+and nurse-profile screens. Requires public read endpoints that don't exist today (search and profile reads sit behind the auth middleware + cookie-bearing
+clientFetch) plus a privacy review of which nurse fields may be shown logged-out (never phone, exact address, or document data).Decision: ship (a)+(b) now; tier (c) is deferred as a REQ-gated follow-up, not built against placeholder endpoints. Rationale:
++
+- (a)+(b) already close the biggest gap — a family can see the brand, the trust story (identity/
+license/INO/bank verification), the escrow guarantee, and the service categories before creating an account, all with zero new backend surface.
++
+- (c) is a materially bigger commitment: a new anonymous, rate-limited search read and a
+privacy-reviewed public nurse-profile shape are backend work with real abuse/PII exposure considerations (scraping, competitor intel, nurse safety) — the kind of decision that shouldn't be made by inferring it from a frontend phase.
++
+- What (c) would need, if approved later: an anonymous
+nurse_searchread (verified-only invariantpreserved, rate-limited, no customer-context fields) and a privacy-reviewed public nurse-profile read (display name, photo, verified-badge state, rating aggregate, service/price rows only — never phone, exact coverage area, or document data). Filed as REQ-066/REQ-067 in for-backend.md; guest-search routes and the guest→login handoff at the booking CTA stay unbuilt until those land.
+Implemented as: a public landing rewritten in at
↑ Back to top diff --git a/product/notes/open-questions.md b/product/notes/open-questions.md index e60c003..7140f71 100644 --- a/product/notes/open-questions.md +++ b/product/notes/open-questions.md @@ -45,3 +45,41 @@ research and built into the model. See also the launch-blocking items to confirm with counsel / providers: [research open questions](../research/go-to-market.md) and [data-model open items](../data-model/index.md). + +--- + +## Decided — public guest-browse depth (ui-phase-13, 2026-07-19) + +**Question:** how deep should the anonymous (logged-out) web experience go, now that the entire +public surface is a bare `/login`? + +Three tiers were framed: +- **(a) Landing only** — a marketing page at `/`; zero data, zero new endpoints. +- **(b) Landing + static public pages** — (a) plus category/how-it-works/trust content; still zero + data (a static i18n category grid, not the live catalog). +- **(c) Guest search + public nurse profiles** — read-only anonymous variants of the search-results + and nurse-profile screens. Requires **public read endpoints that don't exist today** (search and + profile reads sit behind the auth middleware + cookie-bearing `clientFetch`) plus a privacy review + of which nurse fields may be shown logged-out (never phone, exact address, or document data). + +**Decision: ship (a)+(b) now; tier (c) is deferred as a REQ-gated follow-up**, not built against +placeholder endpoints. Rationale: +- (a)+(b) already close the biggest gap — a family can see the brand, the trust story (identity/ + license/INO/bank verification), the escrow guarantee, and the service categories before creating + an account, all with **zero new backend surface**. +- (c) is a materially bigger commitment: a new anonymous, rate-limited search read and a + privacy-reviewed public nurse-profile shape are backend work with real abuse/PII exposure + considerations (scraping, competitor intel, nurse safety) — the kind of decision that shouldn't be + made by inferring it from a frontend phase. +- What (c) would need, if approved later: an anonymous `nurse_search` read (verified-only invariant + preserved, rate-limited, no customer-context fields) and a privacy-reviewed public nurse-profile + read (display name, photo, verified-badge state, rating aggregate, service/price rows only — never + phone, exact coverage area, or document data). Filed as REQ-066/REQ-067 in + [for-backend.md](../../dev/shared-working-context/frontend/requests/for-backend.md); guest-search + routes and the guest→login handoff at the booking CTA stay unbuilt until those land. + +Implemented as: a public landing rewritten in at `/` (middleware rewrite, not a redirect, so the URL/ +canonical stays `/`) with hero, static category grid, how-it-works, a trust/verification explainer, +a nurse-recruitment CTA, and a footer — see +[ui-phase-13-report.md](../../dev/shared-working-context/reports/ui-phase-13-report.md) for the +full build record. diff --git a/server/src/API/Baya.Web.Api/appsettings.Development.json b/server/src/API/Baya.Web.Api/appsettings.Development.json index 2ca0064..70778fe 100644 --- a/server/src/API/Baya.Web.Api/appsettings.Development.json +++ b/server/src/API/Baya.Web.Api/appsettings.Development.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "SqlServer": "Server=87.107.152.16,1433;Database=Baya;User Id=sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;", - "logDb":"Server=87.107.152.16,1433;Database=Baya_Logs;User Id=sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;" + "SqlServer": "Server=87.107.152.16,1433;Database=Baya;User Id=hamid_root_un_sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;", + "logDb":"Server=87.107.152.16,1433;Database=Baya_Logs;User Id=hamid_root_un_sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;" }, "IdentitySettings": { "SecretKey": "SET_VIA_USER_SECRETS_OR_ENV",/(middleware rewrite, not a redirect, so the URL/ canonical stays/) with hero, static category grid, how-it-works, a trust/verification explainer, a nurse-recruitment CTA, and a footer — see ui-phase-13-report.md for the full build record.