import { useEffect, useMemo, useState } from 'react' import { Save } from 'lucide-react' import { AddressListEditor, createEmptyAddressItem, getLocationOptionLabel, matchCityByName, matchProvinceByName, useLocale, type AddressListItem, } from '@meshkee/dashboard-ui' import { Breadcrumbs } from '../components/Breadcrumbs' import { PageTitle } from '../components/PageTitle' import pageStyles from '../components/PageContent.module.css' import { useAuth } from '../context/AuthContext' import { useToast } from '../context/ToastContext' import { useT } from '../i18n/useT' import { ApiError } from '../lib/api' import { formatCellForDisplay } from '../lib/cellNumber' import { createAddress, listAddresses, removeAddress, updateAddress, } from '../services/addressService' import { updateProfile } from '../services/authService' import { listCitiesByProvinceSlug, listIranProvinces, type CityOption, } from '../services/citiesService' import styles from './ProfilePage.module.css' type AddressDraft = AddressListItem function isAddressFilled(item: AddressDraft) { return Boolean( item.province.trim() && item.city.trim() && item.address.trim(), ) } export function ProfilePage() { const { user, setUser } = useAuth() const { showToast } = useToast() const { locale } = useLocale() const t = useT() const [firstName, setFirstName] = useState('') const [lastName, setLastName] = useState('') const [firstNameEn, setFirstNameEn] = useState('') const [lastNameEn, setLastNameEn] = useState('') const [email, setEmail] = useState('') const [about, setAbout] = useState('') const [instagram, setInstagram] = useState('') const [telegramId, setTelegramId] = useState('') const [linkedin, setLinkedin] = useState('') const [isSaving, setIsSaving] = useState(false) const [error, setError] = useState('') const [provinces, setProvinces] = useState([]) const [citiesByProvince, setCitiesByProvince] = useState>({}) const [addresses, setAddresses] = useState([createEmptyAddressItem()]) const [initialAddressIds, setInitialAddressIds] = useState([]) const [addressesLoading, setAddressesLoading] = useState(true) const addressLabels = useMemo( () => ({ title: t('profile.addresses'), addLabel: t('profile.addresses.add'), loading: t('profile.addresses.loading'), province: t('profile.province'), city: t('profile.city'), address: t('profile.address'), postalCode: t('profile.postalCode'), landline: t('profile.landline'), selectProvince: t('profile.selectProvince'), selectCity: t('profile.selectCity'), streetPlaceholder: t('profile.streetPlaceholder'), postalPlaceholder: t('profile.postalCode'), landlinePlaceholder: t('profile.landline'), removeAriaLabel: t('profile.addresses.remove'), }), [t], ) useEffect(() => { if (!user) return setFirstName(user.firstName ?? '') setLastName(user.lastName ?? '') setFirstNameEn(user.firstNameEn ?? '') setLastNameEn(user.lastNameEn ?? '') setEmail(user.email ?? '') setAbout(user.profile.about ?? '') setInstagram(user.profile.instagram ?? '') setTelegramId(user.profile.telegramId ?? '') setLinkedin(user.profile.linkedin ?? '') }, [user]) useEffect(() => { const controller = new AbortController() void loadAddresses(controller.signal) return () => controller.abort() }, []) useEffect(() => { setAddresses((prev) => prev.map((item) => { if (!item.provinceSlug) return item const province = provinces.find((p) => p.slug === item.provinceSlug) const cities = citiesByProvince[item.provinceSlug] ?? [] const city = matchCityByName(item.city, cities) return { ...item, province: province ? getLocationOptionLabel(province, locale) : item.province, city: city ? getLocationOptionLabel(city, locale) : item.city, } }), ) }, [locale, provinces, citiesByProvince]) async function loadAddresses(signal?: AbortSignal) { setAddressesLoading(true) setError('') try { const [provinceItems, addressData] = await Promise.all([ listIranProvinces(signal), listAddresses(signal), ]) setProvinces(provinceItems) const nextAddresses = addressData.items.length > 0 ? addressData.items.map((item) => { const province = matchProvinceByName(item.province, provinceItems) return { id: item.id, provinceSlug: province?.slug ?? '', province: province ? getLocationOptionLabel(province, locale) : item.province, city: item.city, address: item.address, postalCode: item.postalCode ?? '', landline: item.landline ?? '', } }) : [createEmptyAddressItem()] const slugs = [...new Set(nextAddresses.map((item) => item.provinceSlug).filter(Boolean))] const cityGroups = await Promise.all( slugs.map(async (slug) => ({ slug, cities: await listCitiesByProvinceSlug(slug, signal), })), ) const citiesMap = Object.fromEntries(cityGroups.map((group) => [group.slug, group.cities])) const localized = nextAddresses.map((item) => { if (!item.provinceSlug) return item const cities = citiesMap[item.provinceSlug] ?? [] const city = matchCityByName(item.city, cities) return { ...item, city: city ? getLocationOptionLabel(city, locale) : item.city, } }) setCitiesByProvince(citiesMap) setAddresses(localized) setInitialAddressIds( addressData.items.map((item) => item.id).filter(Boolean), ) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return setError(err instanceof ApiError ? err.message : t('profile.addresses.error.load')) } finally { setAddressesLoading(false) } } function updateAddressDraft(index: number, patch: Partial) { setAddresses((prev) => prev.map((item, i) => (i === index ? { ...item, ...patch } : item)), ) } async function handleProvinceChange(index: number, provinceSlug: string) { const province = provinces.find((item) => item.slug === provinceSlug) updateAddressDraft(index, { provinceSlug, province: province ? getLocationOptionLabel(province, locale) : '', city: '', }) if (provinceSlug && !citiesByProvince[provinceSlug]) { const cities = await listCitiesByProvinceSlug(provinceSlug) setCitiesByProvince((prev) => ({ ...prev, [provinceSlug]: cities })) } } function addAddressRow() { setAddresses((prev) => [...prev, createEmptyAddressItem()]) } function removeAddressRow(index: number) { setAddresses((prev) => prev.length === 1 ? [createEmptyAddressItem()] : prev.filter((_, i) => i !== index), ) } async function syncAddresses() { const filled = addresses.filter(isAddressFilled) const keptIds = new Set(filled.map((item) => item.id).filter(Boolean) as string[]) for (const id of initialAddressIds) { if (!keptIds.has(id)) { await removeAddress(id) } } for (const item of filled) { const payload = { province: item.province.trim(), city: item.city.trim(), address: item.address.trim(), postalCode: item.postalCode.trim() || undefined, landline: item.landline.trim() || undefined, } if (item.id) { await updateAddress(item.id, payload) } else { await createAddress(payload) } } } async function handleSubmit(e: React.FormEvent) { e.preventDefault() setError('') setIsSaving(true) const incomplete = addresses.some( (item) => (item.province.trim() || item.city.trim() || item.address.trim() || item.postalCode.trim() || item.landline.trim()) && !isAddressFilled(item), ) if (incomplete) { setError(t('profile.addresses.error.incomplete')) setIsSaving(false) return } try { const result = await updateProfile({ firstName: firstName.trim(), lastName: lastName.trim(), firstNameEn: firstNameEn.trim(), lastNameEn: lastNameEn.trim(), email: email.trim() || undefined, about: about.trim(), instagram: instagram.trim(), telegramId: telegramId.trim(), linkedin: linkedin.trim(), }) await syncAddresses() await loadAddresses() setUser(result.user) showToast(t('profile.toast.success'), 'success') } catch (err) { setError(err instanceof ApiError ? err.message : t('profile.error.update')) } finally { setIsSaving(false) } } return (
{t('profile.title')}

{t('profile.subtitle')}

void handleSubmit(e)}> {error && (
{error}
)}

{t('profile.section.general')}

setFirstName(e.target.value)} dir="rtl" lang="fa" className="faText" />
setLastName(e.target.value)} dir="rtl" lang="fa" className="faText" />
setFirstNameEn(e.target.value)} dir="ltr" lang="en" />
setLastNameEn(e.target.value)} dir="ltr" lang="en" />
setEmail(e.target.value)} placeholder={t('profile.emailPlaceholder')} />