'use client'; import { FunctionComponent, useState } from 'react'; import { useTranslations } from 'next-intl'; import FormControlLabel from '@mui/material/FormControlLabel'; import Stack from '@mui/material/Stack'; import Switch from '@mui/material/Switch'; import TextField from '@mui/material/TextField'; import { AppButton } from '@/components/common'; import { cityCentroid } from '@/services/geography/constants'; import type { CreateAddressInput, LatLng } from '@/services/addresses/types'; import CascadingRegionSelect, { type CascadingRegionValue } from './CascadingRegionSelect'; import AddressMapPicker from './AddressMapPicker'; /** Prefill for edit (or empty for add). `provinceId` is needed to prefill the cascade. */ export interface AddressFormInitial { title?: string; provinceId?: number | null; cityId?: number | null; districtId?: number | null; addressLine?: string | null; latitude?: number | null; longitude?: number | null; isPrimary?: boolean; } export interface AddressFormProps { initial?: AddressFormInitial; submitting?: boolean; onSubmit: (input: CreateAddressInput) => void; onCancel?: () => void; } // Only prefill the region when we have the province — a city id without its province can't drive // the (per-province) city query, so the cascade starts fresh instead of showing a dead value. function initialRegion(initial?: AddressFormInitial): CascadingRegionValue { if (initial?.provinceId == null) return { provinceId: null, cityId: null, districtId: null }; return { provinceId: initial.provinceId, cityId: initial.cityId ?? null, districtId: initial.districtId ?? null }; } function initialPin(initial?: AddressFormInitial): LatLng | null { return initial?.latitude != null && initial?.longitude != null ? { latitude: initial.latitude, longitude: initial.longitude } : null; } /** * The add/edit address form body — the cascading region dropdowns + the map-pin picker + a * title and street address + a "set as primary" toggle. Validation: **city required**, **pin * required** (surfaced inline), title + street required, **district optional**. Emits a * `CreateAddressInput` carrying the picked coordinates. Reused for create and edit. * @component AddressForm */ const AddressForm: FunctionComponent = ({ initial, submitting = false, onSubmit, onCancel }) => { const t = useTranslations('address'); const tc = useTranslations('common'); const [title, setTitle] = useState(initial?.title ?? ''); const [region, setRegion] = useState(() => initialRegion(initial)); const [addressLine, setAddressLine] = useState(initial?.addressLine ?? ''); const [pin, setPin] = useState(() => initialPin(initial)); const [isPrimary, setIsPrimary] = useState(initial?.isPrimary ?? false); const [titleError, setTitleError] = useState(false); const [cityError, setCityError] = useState(false); const [lineError, setLineError] = useState(false); const [pinError, setPinError] = useState(false); const handleSubmit = () => { const titleInvalid = title.trim().length === 0; const cityInvalid = region.cityId == null; const lineInvalid = addressLine.trim().length === 0; const pinInvalid = pin == null; setTitleError(titleInvalid); setCityError(cityInvalid); setLineError(lineInvalid); setPinError(pinInvalid); if (titleInvalid || cityInvalid || lineInvalid || pinInvalid) return; onSubmit({ title: title.trim(), provinceId: region.provinceId as number, cityId: region.cityId as number, districtId: region.districtId, addressLine: addressLine.trim(), latitude: pin!.latitude, longitude: pin!.longitude, isPrimary, }); }; return ( { setTitle(event.target.value); if (titleError) setTitleError(false); }} error={titleError} helperText={titleError ? t('title_required') : undefined} fullWidth /> { setRegion(next); if (cityError && next.cityId != null) setCityError(false); }} cityError={cityError} cityErrorText={t('city_required')} /> { setPin(next); if (pinError) setPinError(false); }} center={cityCentroid(region.cityId)} helperText={t('map_hint')} latLabel={t('map_lat')} lngLabel={t('map_lng')} error={pinError} errorText={t('map_required')} /> { setAddressLine(event.target.value); if (lineError) setLineError(false); }} error={lineError} helperText={lineError ? t('line_required') : t('line_hint')} multiline minRows={2} fullWidth /> setIsPrimary(event.target.checked)} />} label={t('set_primary_toggle')} /> {onCancel ? ( {tc('cancel')} ) : null} {submitting ? tc('saving') : tc('save')} ); }; export default AddressForm;