Add website sitemap config sync to business dashboard settings.

Lets business owners import static pages and URL templates from the live storefront manifest before API sitemap generation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-22 15:15:32 +03:30
co-authored by Cursor
parent d5aca8bea7
commit 5926f772e2
5 changed files with 168 additions and 3 deletions
@@ -50,3 +50,23 @@
min-width: 0;
}
}
.sitemapRow {
align-items: flex-start;
}
.sitemapMeta {
margin: 8px 0 0;
font-size: 12px;
color: var(--text-secondary);
}
.spin {
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@@ -1,6 +1,8 @@
import { useEffect, useState } from 'react'
import { RefreshCw } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { Tooltip } from '../components/Tooltip'
import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT'
import { ApiError } from '../lib/api'
@@ -8,8 +10,11 @@ import {
getSettings,
updateWebsiteSettings,
type SpecialProductsSource,
type WebsiteSitemapConfig,
} from '../services/settingsService'
import { syncWebsiteSitemapConfig } from '../services/websiteSitemapService'
import { hasBusinessModule, type BusinessModuleId } from '../utils/businessModules'
import controlStyles from '../components/CategoryRow.module.css'
import pageStyles from '../components/PageContent.module.css'
import sharedStyles from './ProductSettingsPage.module.css'
import styles from './WebsiteSettingsPage.module.css'
@@ -27,12 +32,24 @@ function defaultSpecialProductsSource(
: 'product'
}
function formatSyncedAt(value: string | null | undefined, locale: string) {
if (!value) return null
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
return date.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-GB', {
dateStyle: 'medium',
timeStyle: 'short',
})
}
export function WebsiteSettingsPage() {
const t = useT()
const { showToast } = useToast()
const [source, setSource] = useState<SpecialProductsSource>('product')
const [sitemapConfig, setSitemapConfig] = useState<WebsiteSitemapConfig | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isSaving, setIsSaving] = useState(false)
const [isSyncingSitemap, setIsSyncingSitemap] = useState(false)
const [error, setError] = useState('')
useEffect(() => {
@@ -51,6 +68,7 @@ export function WebsiteSettingsPage() {
data.settings.website?.specialProductsSource ??
defaultSpecialProductsSource(data.settings.modules?.enabled),
)
setSitemapConfig(data.settings.website?.sitemapConfig ?? null)
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
if (err instanceof ApiError) {
@@ -73,9 +91,7 @@ export function WebsiteSettingsPage() {
try {
const data = await updateWebsiteSettings({ specialProductsSource: next })
setSource(
data.settings.website?.specialProductsSource ?? next,
)
setSource(data.settings.website?.specialProductsSource ?? next)
showToast(t('website.settings.saved'), 'success')
} catch (err) {
setSource(previous)
@@ -89,6 +105,36 @@ export function WebsiteSettingsPage() {
}
}
async function handleSyncSitemap() {
if (isSyncingSitemap) return
setIsSyncingSitemap(true)
setError('')
try {
const result = await syncWebsiteSitemapConfig()
setSitemapConfig(result.sitemapConfig)
showToast(t('website.settings.sitemapSynced'), 'success')
} catch (err) {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError(t('website.settings.sitemapSyncError'))
}
} finally {
setIsSyncingSitemap(false)
}
}
const syncedAtLabel = formatSyncedAt(
sitemapConfig?.syncedAt,
document.documentElement.lang || 'en',
)
const staticPageCount = sitemapConfig?.staticPages?.length ?? 0
const templateCount = sitemapConfig?.templates
? Object.keys(sitemapConfig.templates).length
: 0
return (
<main className={pageStyles.content}>
<Breadcrumbs
@@ -150,6 +196,45 @@ export function WebsiteSettingsPage() {
))}
</div>
</div>
<div className={`${sharedStyles.row} ${styles.sitemapRow}`}>
<div className={sharedStyles.rowText}>
<span className={sharedStyles.rowLabel}>
{t('website.settings.sitemapTitle')}
</span>
<p className={sharedStyles.rowDescription}>
{t('website.settings.sitemapDesc')}
</p>
{sitemapConfig ? (
<p className={styles.sitemapMeta}>
{syncedAtLabel
? t('website.settings.sitemapSyncedAt', { date: syncedAtLabel })
: t('website.settings.sitemapImported')}
{' · '}
{t('website.settings.sitemapSummary', {
pages: staticPageCount,
templates: templateCount,
})}
</p>
) : (
<p className={styles.sitemapMeta}>{t('website.settings.sitemapNotSynced')}</p>
)}
</div>
<Tooltip label={t('website.settings.sitemapSync')}>
<button
type="button"
className={controlStyles.controlBtn}
onClick={() => void handleSyncSitemap()}
disabled={isLoading || isSaving || isSyncingSitemap}
aria-label={t('website.settings.sitemapSync')}
>
<RefreshCw
size={16}
className={isSyncingSitemap ? styles.spin : undefined}
/>
</button>
</Tooltip>
</div>
</div>
)}
</section>