import { useEffect, useMemo, useState } from 'react' import { Plus, Trash2 } 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 { ImageCropper } from '../components/ImageCropper' import { MultiSelectDropdown } from '../components/MultiSelectDropdown' import { RichTextEditor } from '../components/RichTextEditor' import { useToast } from '../context/ToastContext' import { useT } from '../i18n/useT' import { ApiError } from '../lib/api' import { dispatchBusinessProfileUpdated } from '../lib/businessContext' import { listBusinessActivityCategories } from '../services/businessActivityCategoryService' import type { BusinessActivityCategory } from '../services/businessActivityCategoryService' import { getBusinessProfile, updateBusinessProfile, type BusinessPhoneNumber, type BusinessSocialMedia, } from '../services/businessProfileService' import { listCitiesByProvinceSlug, listIranProvinces, type CityOption, } from '../services/citiesService' import { resolveDataUrlToMediaId } from '../services/mediaService' import { flattenBusinessActivityCategories } from '../utils/businessCategories' import pageStyles from '../components/PageContent.module.css' import formStyles from './AddNewProductPage.module.css' import styles from './BusinessProfilePage.module.css' type AddressDraft = AddressListItem const EMPTY_SOCIAL: BusinessSocialMedia = { whatsapp: '', telegram: '', instagram: '', linkedin: '', youtube: '', aparat: '', } const SOCIAL_FIELDS = [ ['whatsapp', 'WhatsApp'], ['telegram', 'Telegram'], ['instagram', 'Instagram'], ['linkedin', 'LinkedIn'], ['youtube', 'YouTube'], ['aparat', 'Aparat'], ] as const function createEmptyPhone(): BusinessPhoneNumber { return { type: 'cell', number: '' } } export function BusinessProfilePage() { const { showToast } = useToast() const { locale } = useLocale() const t = useT() const [isLoading, setIsLoading] = useState(true) const [isSaving, setIsSaving] = useState(false) const [error, setError] = useState('') const [provinces, setProvinces] = useState([]) const [citiesByProvince, setCitiesByProvince] = useState>({}) const [activityCategories, setActivityCategories] = useState([]) const [categoryIds, setCategoryIds] = useState([]) const [nameEn, setNameEn] = useState('') const [nameFa, setNameFa] = useState('') const [email, setEmail] = useState('') const [about, setAbout] = useState('') const [vision, setVision] = useState('') const [logo, setLogo] = useState(null) const [logoMediaId, setLogoMediaId] = useState(null) const [addresses, setAddresses] = useState([createEmptyAddressItem()]) const [phoneNumbers, setPhoneNumbers] = useState([ createEmptyPhone(), ]) const [socialMedia, setSocialMedia] = useState(EMPTY_SOCIAL) const categoryOptions = useMemo( () => flattenBusinessActivityCategories(activityCategories), [activityCategories], ) const addressLabels = useMemo( () => ({ title: t('bizProfile.addresses'), addLabel: t('bizProfile.addresses.add'), province: t('bizProfile.province'), city: t('bizProfile.city'), address: t('bizProfile.address'), postalCode: t('bizProfile.postalCode'), landline: t('bizProfile.landline'), selectProvince: t('bizProfile.selectProvince'), selectCity: t('bizProfile.selectCity'), streetPlaceholder: t('bizProfile.streetPlaceholder'), postalPlaceholder: t('bizProfile.postalCode'), landlinePlaceholder: t('bizProfile.landline'), removeAriaLabel: t('bizProfile.removeAddress'), }), [t], ) useEffect(() => { const controller = new AbortController() void loadData(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 loadData(signal?: AbortSignal) { setIsLoading(true) setError('') try { const [categories, profileData, provinceItems] = await Promise.all([ listBusinessActivityCategories(signal), getBusinessProfile(signal), listIranProvinces(signal), ]) setProvinces(provinceItems) setActivityCategories(categories) setCategoryIds(profileData.profile.categoryIds) setNameEn(profileData.profile.nameEn) setNameFa(profileData.profile.nameFa) setEmail(profileData.profile.emails[0] ?? '') setAbout(profileData.profile.about) setVision(profileData.profile.vision) setLogo(profileData.profile.logoUrl) setLogoMediaId(profileData.profile.logoMediaId) const nextAddresses = profileData.addresses.length > 0 ? profileData.addresses.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])) setAddresses( 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) setPhoneNumbers( profileData.profile.phoneNumbers.length > 0 ? profileData.profile.phoneNumbers : [createEmptyPhone()], ) setSocialMedia({ ...EMPTY_SOCIAL, ...profileData.profile.socialMedia }) } catch (err) { if (err instanceof DOMException && err.name === 'AbortError') return if (err instanceof ApiError) { setError(err.message) } else { setError(t('bizProfile.loadError')) } } finally { setIsLoading(false) } } function updateAddress(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) updateAddress(index, { provinceSlug, province: province ? getLocationOptionLabel(province, locale) : '', city: '', }) if (provinceSlug && !citiesByProvince[provinceSlug]) { const cities = await listCitiesByProvinceSlug(provinceSlug) setCitiesByProvince((prev) => ({ ...prev, [provinceSlug]: cities })) } } function addAddress() { setAddresses((prev) => [...prev, createEmptyAddressItem()]) } function removeAddress(index: number) { setAddresses((prev) => prev.length === 1 ? [createEmptyAddressItem()] : prev.filter((_, i) => i !== index), ) } function updatePhone(index: number, patch: Partial) { setPhoneNumbers((prev) => prev.map((item, i) => (i === index ? { ...item, ...patch } : item)), ) } function addPhone() { setPhoneNumbers((prev) => [...prev, createEmptyPhone()]) } function removePhone(index: number) { setPhoneNumbers((prev) => prev.length === 1 ? [createEmptyPhone()] : prev.filter((_, i) => i !== index), ) } function updateSocial(field: keyof BusinessSocialMedia, value: string) { setSocialMedia((prev) => ({ ...prev, [field]: value })) } async function handleSubmit(e: React.FormEvent) { e.preventDefault() setIsSaving(true) setError('') try { const nextLogoMediaId = await resolveDataUrlToMediaId( logo, 'business-logo.png', logoMediaId, ) const payloadAddresses = addresses .filter( (item) => item.province.trim() || item.city.trim() || item.address.trim() || item.postalCode.trim(), ) .map((item) => ({ id: item.id, province: item.province.trim(), city: item.city.trim(), address: item.address.trim(), postalCode: item.postalCode.trim(), landline: item.landline?.trim() || null, })) const payloadPhones = phoneNumbers .map((item) => ({ type: item.type, number: item.number.trim(), })) .filter((item) => item.number) const trimmedEmail = email.trim() await updateBusinessProfile({ nameEn: nameEn.trim(), nameFa: nameFa.trim(), about, vision, emails: trimmedEmail ? [trimmedEmail] : [], phoneNumbers: payloadPhones, socialMedia, logoMediaId: nextLogoMediaId, categoryIds, addresses: payloadAddresses, }) showToast(t('bizProfile.toast.saved'), 'success') dispatchBusinessProfileUpdated() await loadData() } catch (err) { if (err instanceof ApiError) { setError(err.message) } else { setError(t('bizProfile.saveError')) } } finally { setIsSaving(false) } } if (isLoading) { return (

{t('bizProfile.loading')}

) } return (
{t('title.businessProfile')}

{t('bizProfile.subtitle')}

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

{t('bizProfile.section.basic')}

setNameFa(e.target.value)} dir="rtl" lang="fa" className="faText" />
setNameEn(e.target.value)} dir="ltr" lang="en" />
setEmail(e.target.value)} placeholder="info@example.com" />

{t('bizProfile.section.contact')}

{t('bizProfile.phones')}

{t('bizProfile.phones.type')} {t('bizProfile.phones.number')}
{phoneNumbers.map((item, index) => (
updatePhone(index, { number: e.target.value })} placeholder={t('bizProfile.phones.number')} />
))}

{t('bizProfile.section.social')}

{SOCIAL_FIELDS.map(([key, label]) => (
updateSocial(key, e.target.value)} placeholder={t('bizProfile.social.placeholder', { name: label })} />
))}
) }