Add website schema settings, SEO meta overrides, and resilient bulk SMS.

Business admins can configure site-wide Schema.org and optional per-item SEO title/description; mass SMS continues on failures with an end report.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-09 23:30:23 +03:30
co-authored by Cursor
parent 15aad9f9d8
commit c2ede07e73
37 changed files with 1217 additions and 89 deletions
@@ -11,6 +11,7 @@ import { CitySearchSelect } from '../components/CitySearchSelect'
import { CategorySearchSelect } from '../components/CategorySearchSelect'
import { ImageCropper } from '../components/ImageCropper'
import { ImageUploader } from '../components/ImageUploader'
import { SeoMetaFields } from '../components/SeoMetaFields'
import { useT } from '../i18n/useT'
import { translate } from '../i18n/messages'
import { ApiError, isAbortError } from '../lib/api'
@@ -34,6 +35,7 @@ import {
} from '../services/userProductsService'
import { galleryMediaIdsAfterChange, resolveDataUrlToMediaId, resolveDataUrlsToMediaIds } from '../services/mediaService'
import { formatIrtInput, parseIrtInput } from '../utils/irtPrice'
import { seoMetaPayload } from '../utils/seoMeta'
import styles from './AddCustomerProductPage.module.css'
type StepId = 1 | 2 | 3
@@ -110,6 +112,8 @@ export function AddCustomerProductPage() {
const [titleFa, setTitleFa] = useState('')
const [titleEn, setTitleEn] = useState('')
const [description, setDescription] = useState('')
const [seoMetaTitle, setSeoMetaTitle] = useState('')
const [seoMetaDescription, setSeoMetaDescription] = useState('')
const [priceInput, setPriceInput] = useState('')
const [priceUnit, setPriceUnit] = useState<UserProductPriceCurrency>('IRT')
const [priceByExpert, setPriceByExpert] = useState(false)
@@ -207,6 +211,8 @@ export function AddCustomerProductPage() {
setTitleFa(product.titleFa ?? product.title ?? '')
setTitleEn(product.titleEn ?? '')
setDescription(product.description ?? '')
setSeoMetaTitle(product.seoMetaTitle ?? '')
setSeoMetaDescription(product.seoMetaDescription ?? '')
setPriceInput(
product.price != null ? formatIrtInput(String(product.price)) : '',
)
@@ -376,6 +382,7 @@ export function AddCustomerProductPage() {
titleFa: titleFa.trim(),
titleEn: titleEn.trim() || undefined,
description: description.trim() || undefined,
...seoMetaPayload(seoMetaTitle, seoMetaDescription),
categoryId,
price: price ?? undefined,
priceCurrency: priceUnit,
@@ -617,6 +624,19 @@ export function AddCustomerProductPage() {
/>
</div>
<SeoMetaFields
seoMetaTitle={seoMetaTitle}
seoMetaDescription={seoMetaDescription}
onTitleChange={setSeoMetaTitle}
onDescriptionChange={setSeoMetaDescription}
disabled={submitting}
hint={t('seo.meta.hint')}
titleLabel={t('seo.meta.title')}
descriptionLabel={t('seo.meta.description')}
titlePlaceholder={t('seo.meta.titlePlaceholder')}
descriptionPlaceholder={t('seo.meta.descriptionPlaceholder')}
/>
<div className={styles.priceRow}>
<label
className={`${styles.checkRow} ${styles.col6}`}