import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
import { X } from 'lucide-react'
import {
getLocationOptionLabel,
matchCityByName,
matchProvinceByName,
useLocale,
useToast,
} from '@meshkee/dashboard-ui'
import { useT } from '../i18n/useT'
import { ApiError } from '../lib/api'
import {
createAddress,
updateAddress,
type UserAddress,
} from '../services/addressService'
import {
listCitiesByProvinceSlug,
listIranProvinces,
type CityOption,
} from '../services/citiesService'
import modalStyles from './VariationsModal.module.css'
import panelStyles from './checkout/CheckoutAddAddressPanel.module.css'
import styles from './AddressFormModal.module.css'
interface AddressFormModalProps {
open: boolean
address: UserAddress | null
onClose: () => void
onSaved: (address: UserAddress) => void
}
const ANIMATION_MS = 220
export function AddressFormModal({ open, address, onClose, onSaved }: AddressFormModalProps) {
const t = useT()
const { locale, dir } = useLocale()
const { showToast } = useToast()
const [mounted, setMounted] = useState(open)
const [closing, setClosing] = useState(false)
const [formKey, setFormKey] = useState(0)
useEffect(() => {
if (open) {
setMounted(true)
setClosing(false)
setFormKey((key) => key + 1)
} else if (mounted) {
setClosing(true)
const timer = setTimeout(() => {
setMounted(false)
setClosing(false)
}, ANIMATION_MS)
return () => clearTimeout(timer)
}
}, [open, mounted])
useEffect(() => {
if (!mounted || closing) return
const prevOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', onKey)
return () => {
document.body.style.overflow = prevOverflow
document.removeEventListener('keydown', onKey)
}
}, [mounted, closing, onClose])
if (!mounted) return null
const isEdit = Boolean(address?.id)
return createPortal(
e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="address-form-title"
lang={locale}
dir={dir}
>
{t('addresses.modal.subtitle')}
{
showToast(
isEdit ? t('addresses.toast.updated') : t('addresses.toast.created'),
'success',
)
onSaved(saved)
onClose()
}}
/>
,
document.body,
)
}
function AddressFormFields({
address,
onCancel,
onSaved,
}: {
address: UserAddress | null
onCancel: () => void
onSaved: (address: UserAddress) => void
}) {
const t = useT()
const { locale } = useLocale()
const [provinces, setProvinces] = useState([])
const [cities, setCities] = useState([])
const [loadingLocations, setLoadingLocations] = useState(true)
const [saving, setSaving] = useState(false)
const [error, setError] = useState('')
const [label, setLabel] = useState(address?.label ?? '')
const [provinceSlug, setProvinceSlug] = useState('')
const [city, setCity] = useState('')
const [street, setStreet] = useState(address?.address ?? '')
const [postalCode, setPostalCode] = useState(address?.postalCode ?? '')
const [landline, setLandline] = useState(address?.landline ?? '')
useEffect(() => {
const controller = new AbortController()
async function load() {
setLoadingLocations(true)
try {
const items = await listIranProvinces(controller.signal)
if (controller.signal.aborted) return
setProvinces(items)
if (!address) return
const province = matchProvinceByName(address.province, items)
if (!province) return
setProvinceSlug(province.slug)
const cityItems = await listCitiesByProvinceSlug(province.slug, controller.signal)
if (controller.signal.aborted) return
setCities(cityItems)
const matchedCity = matchCityByName(address.city, cityItems)
setCity(
matchedCity ? getLocationOptionLabel(matchedCity, locale) : address.city,
)
} catch {
if (!controller.signal.aborted) setError(t('addresses.error.loadLocations'))
} finally {
if (!controller.signal.aborted) setLoadingLocations(false)
}
}
void load()
return () => controller.abort()
}, [address, locale, t])
async function handleProvinceChange(slug: string) {
setProvinceSlug(slug)
setCity('')
setCities([])
if (!slug) return
try {
const items = await listCitiesByProvinceSlug(slug)
setCities(items)
} catch {
setError(t('addresses.error.loadCities'))
}
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError('')
const province = provinces.find((item) => item.slug === provinceSlug)
if (!label.trim() || !province || !city.trim() || !street.trim()) {
setError(t('addresses.error.incompleteForm'))
return
}
const payload = {
label: label.trim(),
province: getLocationOptionLabel(province, locale),
city: city.trim(),
address: street.trim(),
postalCode: postalCode.trim() || undefined,
landline: landline.trim() || undefined,
}
setSaving(true)
try {
const result = address?.id
? await updateAddress(address.id, payload)
: await createAddress(payload)
onSaved(result.address)
} catch (err) {
setError(err instanceof ApiError ? err.message : t('addresses.error.save'))
} finally {
setSaving(false)
}
}
return (
)
}