Add a Torob toggle in store settings and show parent category on product chips.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-03 15:26:23 +03:30
co-authored by Cursor
parent fde6a34e44
commit 8c187b18be
16 changed files with 181 additions and 16 deletions
@@ -7,6 +7,7 @@ import { ProductDetailsTabs } from '../components/ProductDetailsTabs'
import { useT } from '../i18n/useT'
import { ApiError } from '../lib/api'
import {
formatProductCategoryChip,
getProduct,
mapProductApiToUi,
} from '../services/productService'
@@ -101,7 +102,8 @@ export function ProductDetailsPage() {
const descriptionLocale = textLocaleAttrs(
product.description?.replace(/<[^>]+>/g, ' ') ?? '',
)
const categoryLocale = textLocaleAttrs(product.category)
const categoryLabel = formatProductCategoryChip(product, isFa)
const categoryLocale = textLocaleAttrs(categoryLabel)
return (
<main className={pageStyles.content}>
@@ -154,15 +156,15 @@ export function ProductDetailsPage() {
</div>
<div className={styles.details}>
{product.category && (
{categoryLabel ? (
<span
className={[styles.categoryChip, categoryLocale.className].filter(Boolean).join(' ')}
lang={categoryLocale.lang}
dir={categoryLocale.dir}
dir={isFa ? 'rtl' : 'ltr'}
>
{product.category}
{categoryLabel}
</span>
)}
) : null}
<h1
className={[styles.nameEn, primaryLocale.className].filter(Boolean).join(' ')}
@@ -119,6 +119,37 @@
cursor: not-allowed;
}
.torobLinkRow {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
.torobUrl {
flex: 1;
min-width: 0;
margin: 0;
padding: 8px 10px;
font-family: var(--font-en);
font-size: 12px;
line-height: 1.4;
color: var(--text-primary);
background: rgba(255, 255, 255, 0.55);
border: 1px solid rgba(148, 163, 184, 0.28);
border-radius: var(--radius-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transition: filter 0.2s ease;
}
.torobUrlBlurred {
filter: blur(6px);
user-select: none;
pointer-events: none;
}
@media (max-width: 768px) {
.stepRow {
flex-wrap: wrap;
+83 -4
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react'
import { ChevronDown, ChevronUp, Copy, Plus, Trash2 } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { StepColorPicker } from '../components/StepColorPicker'
@@ -16,6 +16,7 @@ import {
type StoreSettings,
} from '../services/settingsService'
import { createId } from '../utils/id'
import { getWebsiteOrigin } from '../utils/websiteLinks'
import {
defaultStepColorForId,
normalizeStepColor,
@@ -29,6 +30,7 @@ import styles from './StoreSettingsPage.module.css'
const DEFAULT_STORE_SETTINGS: StoreSettings = {
onlineSellEnabled: true,
torobEnabled: false,
orderProcessSteps: DEFAULT_ORDER_PROCESS_STEPS,
ePayment: {
enabled: false,
@@ -74,6 +76,14 @@ function stepsAreEqual(a: OrderProcessStep[], b: OrderProcessStep[]) {
})
}
function applyStore(store: StoreSettings): StoreSettings {
return {
...DEFAULT_STORE_SETTINGS,
...store,
torobEnabled: store.torobEnabled ?? false,
}
}
export function StoreSettingsPage() {
const t = useT()
const { showToast } = useToast()
@@ -97,7 +107,7 @@ export function StoreSettingsPage() {
try {
const data = await getSettings(signal)
setSettings(data.settings.store)
setSettings(applyStore(data.settings.store))
setDraftSteps(data.settings.store.orderProcessSteps)
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
@@ -117,7 +127,7 @@ export function StoreSettingsPage() {
try {
const data = await updateStoreSettings({ onlineSellEnabled: checked })
setSettings(data.settings.store)
setSettings(applyStore(data.settings.store))
showToast(t('productSettings.saved'), 'success')
} catch (err) {
if (err instanceof ApiError) {
@@ -130,6 +140,35 @@ export function StoreSettingsPage() {
}
}
async function handleTorobChange(checked: boolean) {
setSavingKey('torob')
setError('')
try {
const data = await updateStoreSettings({ torobEnabled: checked })
setSettings(applyStore(data.settings.store))
showToast(t('productSettings.saved'), 'success')
} catch (err) {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError(t('productSettings.errorSave'))
}
} finally {
setSavingKey(null)
}
}
async function handleCopyTorobUrl() {
if (!settings.torobEnabled) return
try {
await navigator.clipboard.writeText(torobUrl)
showToast(t('storeSettings.torobCopied'), 'success')
} catch {
setError(t('storeSettings.torobCopyError'))
}
}
function updateStepLabel(id: string, label: string) {
setDraftSteps((current) =>
current.map((step) => (step.id === id ? { ...step, label } : step)),
@@ -199,7 +238,7 @@ export function StoreSettingsPage() {
try {
const data = await updateStoreSettings({ orderProcessSteps: normalized })
setSettings(data.settings.store)
setSettings(applyStore(data.settings.store))
setDraftSteps(data.settings.store.orderProcessSteps)
showToast(t('storeSettings.stepsSaved'), 'success')
} catch (err) {
@@ -223,6 +262,7 @@ export function StoreSettingsPage() {
draftSteps.every(
(step) => step.label.trim().length > 0 && step.labelFa.trim().length > 0,
)
const torobUrl = `${getWebsiteOrigin()}/torob_api/v3/products`
return (
<main className={pageStyles.content}>
@@ -269,6 +309,45 @@ export function StoreSettingsPage() {
onChange={(checked) => void handleOnlineSellChange(checked)}
/>
</div>
<div className={sharedStyles.row}>
<div className={sharedStyles.rowText}>
<label htmlFor="torob-sync" className={sharedStyles.rowLabel}>
{t('storeSettings.torob')}
</label>
<p className={sharedStyles.rowDescription}>
{t('storeSettings.torobDesc')}
</p>
<div className={styles.torobLinkRow}>
<p
className={`${styles.torobUrl} ${settings.torobEnabled ? '' : styles.torobUrlBlurred}`}
dir="ltr"
lang="en"
aria-hidden={!settings.torobEnabled}
aria-label={t('storeSettings.torobLinkAria')}
>
{torobUrl}
</p>
<Tooltip label={t('storeSettings.torobCopy')}>
<button
type="button"
className={controlStyles.controlBtn}
onClick={() => void handleCopyTorobUrl()}
disabled={!settings.torobEnabled || savingKey === 'torob'}
aria-label={t('storeSettings.torobCopy')}
>
<Copy size={15} />
</button>
</Tooltip>
</div>
</div>
<Switch
id="torob-sync"
checked={settings.torobEnabled}
disabled={savingKey === 'torob'}
aria-label={t('storeSettings.torob')}
onChange={(checked) => void handleTorobChange(checked)}
/>
</div>
</div>
)}
</section>