blocker phase 3 addresses
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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 | — | — |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user