frontend phase 2: onboarding & profiles — customer/patient, nurse profile & bank

Turns a logged-in user into a usable account, consuming the b3 identity-profiles
contract behind the services/{domain} seam.

Services (mock default true; real HTTP clients wired for a one-line flip):
- services/patients: rewritten to b3 PatientDto + client-augmented relation/conditions;
  full CRUD, optimistic soft-archive, cache-splice on create, age<->birthDate helper.
- services/profiles: customer + nurse profile get/upsert + avatar (404->null mapping).
- services/nurse: payout bank accounts + IBAN(Sheba) util + pending-only polling.

Screens: A3->A4 onboarding wizard, E1 patients list/CRUD, A5 home (first-login gate +
nudge), customer profile (no national-ID), nurse profile bootstrap (unverified
placeholder), nurse bank settings (pending/verified/mismatch + make-primary).

Shared composites (each tested): GenderToggle, ConditionChips, RelationSelect,
PatientForm, PatientCard, BankStatusPanel; reuses f0 StepperHeader/StatusChip/PhoneField.
Adds onboarding/home/profile/nurseProfile/bank i18n namespaces (both locales, in sync),
the --bal-primary-soft token, and nurse sidebar Profile + Bank entries.

Contract gaps filed: REQ-005 (patient relation/conditions), REQ-006 (avatar route),
REQ-007 (customer name/language). Gate: check + 112 tests + build all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamid
2026-07-02 22:04:38 +03:30
parent 82561c4cc6
commit 4b4243c451
70 changed files with 3111 additions and 190 deletions
+126 -7
View File
@@ -5,6 +5,7 @@
"patients": "بیماران",
"wallet": "کیف‌پول",
"profile": "پروفایل",
"bank": "حساب بانکی",
"dashboard": "داشبورد",
"verification": "احراز هویت",
"visits": "ویزیت‌ها",
@@ -25,6 +26,11 @@
"retry": "تلاش مجدد",
"add": "افزودن",
"cancel": "انصراف",
"save": "ذخیره",
"saving": "در حال ذخیره…",
"back": "بازگشت",
"close": "بستن",
"optional": "اختیاری",
"currency_toman": "تومان",
"brand": "بلینیار",
"brand_tagline": "مراقبت مطمئن در خانه"
@@ -35,16 +41,129 @@
"admin_console": "کنسول مدیریت",
"placeholder_body": "این بخش در فازهای بعدی تکمیل می‌شود."
},
"patients": {
"title": یماران",
"subtitle": "یک صفحهٔ مرجع که به الگوی services/{domain} و React Query متصل است (داده‌های آزمایشی).",
"add": "افزودن بیمار",
"empty": "هنوز بیماری ثبت نشده است. اولین بیمار را اضافه کنید.",
"name_label": "نام کامل",
"home": {
"greeting": ه بلینیار خوش آمدید",
"subtitle": "مراقبت از عزیزانتان را مدیریت کنید.",
"nudge_patient_title": "تکمیل پروندهٔ بیمار",
"nudge_patient_body": "وضعیت‌ها، داروها و روتین را اضافه کنید تا پرستار آماده حاضر شود.",
"nudge_patient_cta": "مشاهدهٔ بیماران",
"nudge_profile_title": "تکمیل پروفایل",
"nudge_profile_body": "برای رزرو سریع‌تر، تماس اضطراری را اضافه کنید.",
"nudge_profile_cta": "رفتن به پروفایل"
},
"onboarding": {
"step_relation": "برای چه کسی",
"step_patient": "بیمار",
"relation_title": "برای چه کسی مراقبت می‌خواهید؟",
"relation_subtitle": "این کمک می‌کند مراقبت مناسب را پیشنهاد دهیم. بعداً می‌توانید افراد دیگری هم اضافه کنید.",
"relation_parent": "پدر/مادر",
"relation_spouse": "همسر",
"relation_child": "فرزند",
"relation_self": "خودم",
"patient_title": "ثبت بیمار",
"patient_subtitle": "فردی که مراقبت می‌شود — لزوماً صاحب حساب نیست.",
"name_label": "نام و نام خانوادگی",
"name_required": "نام بیمار را وارد کنید",
"age_label": "سن",
"age_invalid": "سن معتبر وارد کنید",
"gender_label": "جنسیت",
"gender_male": "مرد",
"gender_female": "زن",
"added": "بیمار اضافه شد"
"gender_required": "انتخاب جنسیت بیمار الزامی است",
"conditions_label": "وضعیت‌ها",
"conditions_hint": "اختیاری — موارد مرتبط را انتخاب کنید.",
"condition_elderly": "سالمند",
"condition_post_surgery": "پس از جراحی",
"condition_diabetes": "دیابت",
"condition_mobility": "کم‌تحرک",
"condition_dementia": "آلزایمر/دمانس",
"continue": "ادامه",
"save_continue": "ذخیره و ادامه",
"saved": "بیمار ثبت شد"
},
"patients": {
"title": "بیماران",
"subtitle": "کسانی که برایشان مراقبت می‌گیرید.",
"add": "افزودن بیمار",
"add_title": "افزودن بیمار",
"edit_title": "ویرایش بیمار",
"edit": "ویرایش",
"archive": "آرشیو",
"empty_title": "هنوز بیماری ثبت نشده",
"empty_body": "برای شروع، اولین بیمار را اضافه کنید.",
"archive_title": "آرشیو بیمار؟",
"archive_body": "این بیمار از فهرست شما حذف می‌شود اما سوابق رزروهای گذشته حفظ می‌ماند.",
"archive_confirm": "آرشیو",
"archived": "بیمار آرشیو شد",
"age_years": "{age} سال",
"conditions_none": "وضعیت خاصی ثبت نشده",
"unavailable": "این بیمار در دسترس شما نیست."
},
"profile": {
"title": "پروفایل",
"subtitle": "اطلاعات شما و تماس اضطراری.",
"first_name": "نام",
"last_name": "نام خانوادگی",
"language": "زبان ترجیحی",
"language_fa": "فارسی",
"language_en": "انگلیسی",
"emergency_section": "تماس اضطراری",
"emergency_hint": "شماره‌ای که در مواقع ضروری با آن تماس می‌گیریم.",
"emergency_name": "نام تماس اضطراری",
"emergency_phone": "شماره تماس اضطراری",
"emergency_phone_invalid": "شماره موبایل معتبر وارد کنید",
"save": "ذخیره",
"saved": "پروفایل ذخیره شد",
"completion_done": "پروفایل شما کامل است.",
"completion_todo": "برای رزرو سریع‌تر، پروفایل خود را کامل کنید."
},
"nurseProfile": {
"title": "پروفایل پرستار",
"subtitle": "چیزی که خانواده‌ها هنگام یافتن شما می‌بینند.",
"photo": "عکس پروفایل",
"photo_hint": "یک عکس واضح از چهره‌تان.",
"upload": "بارگذاری عکس",
"uploading": "در حال بارگذاری…",
"bio": "درباره من",
"bio_hint": "یک معرفی کوتاه — تجربه، تخصص، رویکرد.",
"years": "سابقه کار (سال)",
"years_invalid": "عددی بین ۰ تا ۸۰ وارد کنید",
"save": "ذخیره پروفایل",
"saved": "پروفایل ذخیره شد",
"unverified_title": "پروفایل شما هنوز فعال نیست",
"unverified_body": "برای نمایش به خانواده‌ها و دریافت رزرو، احراز هویت را تکمیل کنید.",
"unverified_cta": "تکمیل احراز هویت",
"deferred_services": "خدمات و قیمت‌ها و روزهای کاری را در مرحله بعد اضافه می‌کنید."
},
"bank": {
"title": "حساب بانکی",
"subtitle": "مقصد واریز درآمد شما.",
"iban_label": "شماره شبا",
"iban_hint": "با IR شروع می‌شود و ۲۴ رقم دارد.",
"iban_invalid": "شماره شبا معتبر وارد کنید (IR و ۲۴ رقم)",
"holder_label": "نام صاحب حساب",
"holder_hint": "باید دقیقاً به نام خودتان باشد.",
"holder_required": "نام صاحب حساب را وارد کنید",
"submit": "ثبت حساب",
"submitting": "در حال ثبت…",
"added": "حساب ثبت شد — استعلام مالکیت آغاز شد",
"add_error": "ثبت این حساب ممکن نشد. شبا را بررسی کرده و دوباره تلاش کنید.",
"status_pending_chip": "در حال استعلام",
"status_pending_title": "در حال استعلام مالکیت حساب",
"status_pending_body": "استعلام شبا در حال انجام است؛ ممکن است چند لحظه طول بکشد.",
"status_verified_chip": "تاییدشد",
"status_verified_title": "حساب تایید شد",
"status_verified_body": "این حساب برای واریز درآمد شما آماده است.",
"status_mismatch_chip": "مغایرت مالکیت",
"status_mismatch_title": "حساب باید به نام خودتان باشد",
"status_mismatch_body": "نام صاحب این حساب با هویت شما مطابقت ندارد. لطفاً شبای حسابی به نام خودتان وارد کنید.",
"reenter": "ثبت حساب دیگر",
"make_primary": "انتخاب به‌عنوان حساب اصلی",
"primary_set": "حساب اصلی به‌روزرسانی شد",
"iban_masked_label": "شبا",
"primary": "حساب اصلی",
"empty_title": "هنوز حسابی ثبت نشده",
"empty_body": "برای دریافت درآمد، یک حساب بانکی به نام خودتان اضافه کنید."
},
"auth": {
"customer_title": "ورود به بلینیار",