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:
@@ -0,0 +1,73 @@
|
||||
import { sleep } from '@/utils';
|
||||
import type {
|
||||
AvatarUploadResult,
|
||||
CustomerProfile,
|
||||
NurseProfile,
|
||||
ProfilesApi,
|
||||
UpsertCustomerProfileInput,
|
||||
UpsertNurseProfileInput,
|
||||
} from '../types';
|
||||
|
||||
const MOCK_LATENCY_MS = 350;
|
||||
|
||||
// Both profiles start absent (a fresh user has none — the real GET would 404). Bootstrapping
|
||||
// via upsert creates them; `isVerified` and the aggregates stay server-owned defaults.
|
||||
let customerProfile: CustomerProfile | null = null;
|
||||
let nurseProfile: NurseProfile | null = null;
|
||||
|
||||
/**
|
||||
* In-memory mock behind the ProfilesApi seam. Mirrors the b3 shapes and keeps the guarded
|
||||
* read-only fields (`isVerified=false`, zero aggregates) exactly as the server would, so a
|
||||
* bootstrapped nurse is never presented as verified/bookable.
|
||||
*/
|
||||
export const profilesMockApi: ProfilesApi = {
|
||||
getCustomerProfile: async () => {
|
||||
await sleep(MOCK_LATENCY_MS);
|
||||
return customerProfile;
|
||||
},
|
||||
|
||||
upsertCustomerProfile: async (input: UpsertCustomerProfileInput) => {
|
||||
await sleep(MOCK_LATENCY_MS);
|
||||
customerProfile = {
|
||||
id: customerProfile?.id ?? 1,
|
||||
defaultEmergencyContactName: input.defaultEmergencyContactName,
|
||||
defaultEmergencyContactPhone: input.defaultEmergencyContactPhone,
|
||||
firstName: input.firstName ?? customerProfile?.firstName ?? null,
|
||||
lastName: input.lastName ?? customerProfile?.lastName ?? null,
|
||||
preferredLanguage: input.preferredLanguage ?? customerProfile?.preferredLanguage ?? null,
|
||||
};
|
||||
return customerProfile;
|
||||
},
|
||||
|
||||
getNurseProfile: async () => {
|
||||
await sleep(MOCK_LATENCY_MS);
|
||||
return nurseProfile;
|
||||
},
|
||||
|
||||
upsertNurseProfile: async (input: UpsertNurseProfileInput) => {
|
||||
await sleep(MOCK_LATENCY_MS);
|
||||
nurseProfile = {
|
||||
id: nurseProfile?.id ?? 1,
|
||||
bio: input.bio,
|
||||
yearsOfExperience: input.yearsOfExperience,
|
||||
educationLevel: input.educationLevel,
|
||||
educationField: input.educationField,
|
||||
specializationsJson: input.specializationsJson,
|
||||
// Server-owned, guarded — a bootstrapped profile is never verified or bookable.
|
||||
isVerified: false,
|
||||
isAcceptingBookings: nurseProfile?.isAcceptingBookings ?? false,
|
||||
averageRating: 0,
|
||||
totalReviews: 0,
|
||||
totalCompletedBookings: 0,
|
||||
avatarUrl: input.avatarUrl ?? nurseProfile?.avatarUrl ?? null,
|
||||
};
|
||||
return nurseProfile;
|
||||
},
|
||||
|
||||
uploadAvatar: async (file: File): Promise<AvatarUploadResult> => {
|
||||
await sleep(MOCK_LATENCY_MS);
|
||||
// Object URL reflects the actual picked image for the demo; the real impl returns an
|
||||
// object-storage URL (REQ-006).
|
||||
return { url: URL.createObjectURL(file) };
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user