blocker phase 3 addresses

This commit is contained in:
hamid
2026-08-02 21:15:57 +03:30
parent 2cd1075286
commit dd3e39dec5
8 changed files with 47 additions and 6 deletions
+4
View File
@@ -309,6 +309,10 @@
"line_hint": "Building, street, unit — the detail a nurse needs to find the door.", "line_hint": "Building, street, unit — the detail a nurse needs to find the door.",
"line_required": "Enter the street address", "line_required": "Enter the street address",
"city_required": "Select a city", "city_required": "Select a city",
"recipient_name_label": "Recipient name",
"recipient_name_required": "Enter who the nurse should ask for",
"recipient_phone_label": "Recipient phone",
"recipient_phone_invalid": "Enter a valid Iranian mobile number",
"set_primary_toggle": "Set as primary address", "set_primary_toggle": "Set as primary address",
"map_hint": "Tap or drag the marker to the patient's exact location.", "map_hint": "Tap or drag the marker to the patient's exact location.",
"map_required": "Drop a pin on the map", "map_required": "Drop a pin on the map",
+4
View File
@@ -309,6 +309,10 @@
"line_hint": "پلاک، خیابان، واحد — جزئیاتی که پرستار برای یافتن درِ منزل نیاز دارد.", "line_hint": "پلاک، خیابان، واحد — جزئیاتی که پرستار برای یافتن درِ منزل نیاز دارد.",
"line_required": "نشانی کامل را وارد کنید", "line_required": "نشانی کامل را وارد کنید",
"city_required": "شهر را انتخاب کنید", "city_required": "شهر را انتخاب کنید",
"recipient_name_label": "نام تحویل‌گیرنده",
"recipient_name_required": "نام فردی که پرستار باید بپرسد را وارد کنید",
"recipient_phone_label": "شماره تماس تحویل‌گیرنده",
"recipient_phone_invalid": "یک شماره موبایل معتبر ایرانی وارد کنید",
"set_primary_toggle": "به‌عنوان آدرس اصلی تنظیم شود", "set_primary_toggle": "به‌عنوان آدرس اصلی تنظیم شود",
"map_hint": "برای تعیین محل دقیق بیمار، نشانگر را بکشید یا روی نقشه بزنید.", "map_hint": "برای تعیین محل دقیق بیمار، نشانگر را بکشید یا روی نقشه بزنید.",
"map_required": "روی نقشه یک پین بگذارید", "map_required": "روی نقشه یک پین بگذارید",
@@ -172,6 +172,8 @@ export default function AddressesPage() {
latitude: editing.latitude, latitude: editing.latitude,
longitude: editing.longitude, longitude: editing.longitude,
isPrimary: editing.isPrimary, isPrimary: editing.isPrimary,
recipientName: editing.recipientName,
recipientPhone: editing.recipientPhone,
} }
: undefined : undefined
} }
@@ -51,6 +51,8 @@ describe('<AddressForm/> component', () => {
latitude: 35.7, latitude: 35.7,
longitude: 51.4, longitude: 51.4,
isPrimary: false, isPrimary: false,
recipientName: 'Sara',
recipientPhone: '09120001234',
}} }}
onSubmit={onSubmit} onSubmit={onSubmit}
/> />
@@ -67,6 +69,8 @@ describe('<AddressForm/> component', () => {
latitude: 35.7, latitude: 35.7,
longitude: 51.4, longitude: 51.4,
isPrimary: false, isPrimary: false,
recipientName: 'Sara',
recipientPhone: '09120001234',
}); });
}); });
}); });
@@ -7,6 +7,7 @@ import Stack from '@mui/material/Stack';
import Switch from '@mui/material/Switch'; import Switch from '@mui/material/Switch';
import { AppButton } from '@/components/common'; import { AppButton } from '@/components/common';
import { RhfControlGroup, RhfTextField } from '@/components/common/form'; import { RhfControlGroup, RhfTextField } from '@/components/common/form';
import PhoneNumberField, { isIranianMobile } from '@/components/PhoneNumberField';
import { cityCentroid } from '@/services/geography/constants'; import { cityCentroid } from '@/services/geography/constants';
import type { CreateAddressInput, LatLng } from '@/services/addresses/types'; import type { CreateAddressInput, LatLng } from '@/services/addresses/types';
import CascadingRegionSelect, { type CascadingRegionValue } from './CascadingRegionSelect'; import CascadingRegionSelect, { type CascadingRegionValue } from './CascadingRegionSelect';
@@ -22,6 +23,8 @@ export interface AddressFormInitial {
latitude?: number | null; latitude?: number | null;
longitude?: number | null; longitude?: number | null;
isPrimary?: boolean; isPrimary?: boolean;
recipientName?: string | null;
recipientPhone?: string | null;
} }
export interface AddressFormProps { export interface AddressFormProps {
@@ -39,6 +42,8 @@ interface AddressFormValues {
pin: LatLng | null; pin: LatLng | null;
addressLine: string; addressLine: string;
isPrimary: boolean; isPrimary: boolean;
recipientName: string;
recipientPhone: string;
} }
// Only prefill the region when we have the province — a city id without its province can't drive // Only prefill the region when we have the province — a city id without its province can't drive
@@ -78,6 +83,8 @@ const AddressForm: FunctionComponent<AddressFormProps> = ({ initial, submitting
pin: initialPin(initial), pin: initialPin(initial),
addressLine: initial?.addressLine ?? '', addressLine: initial?.addressLine ?? '',
isPrimary: initial?.isPrimary ?? false, isPrimary: initial?.isPrimary ?? false,
recipientName: initial?.recipientName ?? '',
recipientPhone: initial?.recipientPhone ?? '',
}, },
}); });
const { control, handleSubmit, formState } = form; const { control, handleSubmit, formState } = form;
@@ -98,6 +105,8 @@ const AddressForm: FunctionComponent<AddressFormProps> = ({ initial, submitting
latitude: (values.pin as LatLng).latitude, latitude: (values.pin as LatLng).latitude,
longitude: (values.pin as LatLng).longitude, longitude: (values.pin as LatLng).longitude,
isPrimary: values.isPrimary, isPrimary: values.isPrimary,
recipientName: values.recipientName.trim(),
recipientPhone: values.recipientPhone,
}); });
}; };
@@ -153,6 +162,29 @@ const AddressForm: FunctionComponent<AddressFormProps> = ({ initial, submitting
fullWidth fullWidth
/> />
<RhfTextField<AddressFormValues>
name="recipientName"
label={t('recipient_name_label')}
rules={{ validate: (value) => String(value ?? '').trim().length > 0 || t('recipient_name_required') }}
fullWidth
/>
<RhfControlGroup<AddressFormValues>
name="recipientPhone"
rules={{ validate: (value) => isIranianMobile(String(value ?? '')) }}
>
{({ field, hasError }) => (
<PhoneNumberField
label={t('recipient_phone_label')}
value={(field.value as string) ?? ''}
onChange={field.onChange}
error={hasError}
helperText={hasError ? t('recipient_phone_invalid') : undefined}
fullWidth
/>
)}
</RhfControlGroup>
<RhfControlGroup<AddressFormValues> name="isPrimary"> <RhfControlGroup<AddressFormValues> name="isPrimary">
{({ field }) => ( {({ field }) => (
<FormControlLabel <FormControlLabel
-4
View File
@@ -60,10 +60,6 @@ Effort is a rough size, not a schedule: **S** = small/contained, **M** = a real
isn't verified yet can still be opened directly and shown as "verified" if you know her profile link. isn't verified yet can still be opened directly and shown as "verified" if you know her profile link.
*(Effort: M)* *(Effort: M)*
### Data safety
- **Editing a saved home address can silently wipe out the recipient's name and phone number** on every save,
because the edit form never collects those fields but the save action clears them anyway. *(Effort: S)*
### Booking lifecycle ### Booking lifecycle
- **A booking whose remaining visits get automatically marked "missed" can get stuck forever** and never - **A booking whose remaining visits get automatically marked "missed" can get stuck forever** and never
reach a state where the nurse can actually be paid for the visits she did complete. The "today's visits" reach a state where the nurse can actually be paid for the visits she did complete. The "today's visits"
+1 -1
View File
@@ -18,7 +18,7 @@ whatever order you prefer.
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| 01 | [admin-rbac](blocker-phases/01-admin-rbac.md) | Admin can't do anything (the #1 leverage item) | — | ✅ Done | | 01 | [admin-rbac](blocker-phases/01-admin-rbac.md) | Admin can't do anything (the #1 leverage item) | — | ✅ Done |
| 02 | [reviews-moderation](blocker-phases/02-reviews-moderation.md) | Reviews can never go live | 01 | ✅ Done (no separate fix needed — unblocked by 01) | | 02 | [reviews-moderation](blocker-phases/02-reviews-moderation.md) | Reviews can never go live | 01 | ✅ Done (no separate fix needed — unblocked by 01) |
| 03 | [address-edit-bug](blocker-phases/03-address-edit-bug.md) | Editing an address wipes recipient name/phone | — | | | 03 | [address-edit-bug](blocker-phases/03-address-edit-bug.md) | Editing an address wipes recipient name/phone | — | ✅ Done |
| 04 | [payment-timezone](blocker-phases/04-payment-timezone.md) | The 30-minute payment countdown can lie | — | — | | 04 | [payment-timezone](blocker-phases/04-payment-timezone.md) | The 30-minute payment countdown can lie | — | — |
| 05 | [bnpl-setup](blocker-phases/05-bnpl-setup.md) | Installments (BNPL) don't work at all | — | — | | 05 | [bnpl-setup](blocker-phases/05-bnpl-setup.md) | Installments (BNPL) don't work at all | — | — |
| 06 | [catalog-admin-page](blocker-phases/06-catalog-admin-page.md) | No admin page for service categories/pricing | — | — | | 06 | [catalog-admin-page](blocker-phases/06-catalog-admin-page.md) | No admin page for service categories/pricing | — | — |
-1
View File
@@ -127,7 +127,6 @@ need to touch that directly except for one step below).
**What you should see:** adding, editing, setting-primary, and deleting addresses all work and save for real. The map for choosing a location is currently just a simple tap-to-place grid, not a real interactive street map yet. **What you should see:** adding, editing, setting-primary, and deleting addresses all work and save for real. The map for choosing a location is currently just a simple tap-to-place grid, not a real interactive street map yet.
**Known issues:** **Known issues:**
- Editing a saved address can silently erase the recipient's name and phone number (known blocker — see [blockers.md](blockers.md)) — don't edit the pre-existing "Home" address for this reason; test with a newly added one instead
- Outside Tehran, the map area often opens in the wrong location, far from the real city - Outside Tehran, the map area often opens in the wrong location, far from the real city
- The real interactive map (with street search and real tiles) isn't turned on in this test environment yet - The real interactive map (with street search and real tiles) isn't turned on in this test environment yet