Polish business FA layout and wire special-group keys plus SSL sync.

RTL carousels/FABs, special key+title fields, slider gallery fixes, and dashboard SSL sync agent for super-admin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-03 00:06:44 +03:30
co-authored by Cursor
parent 66004a0fba
commit 672091d1f5
113 changed files with 4154 additions and 1451 deletions
+35 -39
View File
@@ -5,6 +5,7 @@ import { StepColorPicker } from '../components/StepColorPicker'
import { Switch } from '../components/Switch'
import { Tooltip } from '../components/Tooltip'
import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT'
import { ApiError } from '../lib/api'
import {
getSettings,
@@ -53,6 +54,7 @@ function stepsAreEqual(a: OrderProcessStep[], b: OrderProcessStep[]) {
}
export function StoreSettingsPage() {
const t = useT()
const { showToast } = useToast()
const [settings, setSettings] = useState<StoreSettings>(DEFAULT_STORE_SETTINGS)
const [draftSteps, setDraftSteps] = useState<OrderProcessStep[]>(
@@ -81,7 +83,7 @@ export function StoreSettingsPage() {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError('Unable to load settings.')
setError(t('productSettings.errorLoad'))
}
} finally {
setIsLoading(false)
@@ -95,12 +97,12 @@ export function StoreSettingsPage() {
try {
const data = await updateStoreSettings({ onlineSellEnabled: checked })
setSettings(data.settings.store)
showToast('Settings saved.', 'success')
showToast(t('productSettings.saved'), 'success')
} catch (err) {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError('Unable to save settings.')
setError(t('productSettings.errorSave'))
}
} finally {
setSavingKey(null)
@@ -163,11 +165,11 @@ export function StoreSettingsPage() {
const normalized = normalizeSteps(draftSteps)
const hasEmptyLabel = normalized.some((step) => !step.label || !step.labelFa)
if (!normalized.length) {
setError('Add at least one order process step.')
setError(t('storeSettings.error.minSteps'))
return
}
if (hasEmptyLabel) {
setError('Every order step needs an English and Farsi label.')
setError(t('storeSettings.error.labels'))
return
}
@@ -178,12 +180,12 @@ export function StoreSettingsPage() {
const data = await updateStoreSettings({ orderProcessSteps: normalized })
setSettings(data.settings.store)
setDraftSteps(data.settings.store.orderProcessSteps)
showToast('Order process steps saved.', 'success')
showToast(t('storeSettings.stepsSaved'), 'success')
} catch (err) {
if (err instanceof ApiError) {
setError(err.message)
} else {
setError('Unable to save order process steps.')
setError(t('storeSettings.error.saveSteps'))
}
} finally {
setSavingKey(null)
@@ -213,10 +215,8 @@ export function StoreSettingsPage() {
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>Store settings</h2>
<p className={pageStyles.pageSubtitle}>
Control online sales and define how orders move through fulfillment.
</p>
<h2 className={pageStyles.pageTitle}>{t('storeSettings.title')}</h2>
<p className={pageStyles.pageSubtitle}>{t('storeSettings.subtitle')}</p>
</div>
</div>
@@ -227,27 +227,26 @@ export function StoreSettingsPage() {
)}
<section className={sharedStyles.panel}>
<h3 className={sharedStyles.sectionTitle}>Sales</h3>
<h3 className={sharedStyles.sectionTitle}>{t('storeSettings.sales')}</h3>
{isLoading ? (
<p className={sharedStyles.status}>Loading settings...</p>
<p className={sharedStyles.status}>{t('productSettings.loading')}</p>
) : (
<div className={sharedStyles.list}>
<div className={sharedStyles.row}>
<div className={sharedStyles.rowText}>
<label htmlFor="online-sell" className={sharedStyles.rowLabel}>
Online sell
{t('storeSettings.onlineSell')}
</label>
<p className={sharedStyles.rowDescription}>
When disabled, all sales on your website are turned off. Customers
will not be able to place new orders online.
{t('storeSettings.onlineSellDesc')}
</p>
</div>
<Switch
id="online-sell"
checked={settings.onlineSellEnabled}
disabled={savingKey === 'onlineSell'}
aria-label="Online sell"
aria-label={t('storeSettings.onlineSell')}
onChange={(checked) => void handleOnlineSellChange(checked)}
/>
</div>
@@ -258,16 +257,13 @@ export function StoreSettingsPage() {
<section className={`${sharedStyles.panel} ${styles.stepsPanel}`}>
<div className={styles.stepsHeader}>
<div>
<h3 className={sharedStyles.sectionTitle}>Order process</h3>
<p className={styles.stepsDescription}>
Define the steps an order can move through for example: under
processing, ready for shipping, shipped, delivered.
</p>
<h3 className={sharedStyles.sectionTitle}>{t('storeSettings.orderProcess')}</h3>
<p className={styles.stepsDescription}>{t('storeSettings.orderProcessDesc')}</p>
</div>
</div>
{isLoading ? (
<p className={sharedStyles.status}>Loading settings...</p>
<p className={sharedStyles.status}>{t('productSettings.loading')}</p>
) : (
<>
<div className={styles.stepsList}>
@@ -277,14 +273,14 @@ export function StoreSettingsPage() {
<StepColorPicker
value={normalizeStepColor(step.color, defaultStepColorForId(step.id, index))}
onChange={(color) => updateStepColor(step.id, color)}
ariaLabel={`Color for step ${index + 1}`}
ariaLabel={t('storeSettings.stepColorAria', { index: index + 1 })}
/>
<input
type="text"
className={styles.stepInput}
value={step.label}
placeholder="Label (EN)"
aria-label={`Order step ${index + 1} English label`}
placeholder={t('storeSettings.labelEn')}
aria-label={t('storeSettings.stepEnAria', { index: index + 1 })}
dir="ltr"
lang="en"
onChange={(e) => updateStepLabel(step.id, e.target.value)}
@@ -293,42 +289,42 @@ export function StoreSettingsPage() {
type="text"
className={`${styles.stepInput} ${styles.stepInputFa}`}
value={step.labelFa ?? ''}
placeholder="عنوان (فارسی)"
aria-label={`Order step ${index + 1} Farsi label`}
placeholder={t('storeSettings.labelFa')}
aria-label={t('storeSettings.stepFaAria', { index: index + 1 })}
dir="rtl"
lang="fa"
onChange={(e) => updateStepLabelFa(step.id, e.target.value)}
/>
<div className={styles.stepControls}>
<Tooltip label="Move step up">
<Tooltip label={t('storeSettings.moveUp')}>
<button
type="button"
className={controlStyles.controlBtn}
onClick={() => moveStep(step.id, -1)}
disabled={index === 0}
aria-label="Move step up"
aria-label={t('storeSettings.moveUp')}
>
<ChevronUp size={16} />
</button>
</Tooltip>
<Tooltip label="Move step down">
<Tooltip label={t('storeSettings.moveDown')}>
<button
type="button"
className={controlStyles.controlBtn}
onClick={() => moveStep(step.id, 1)}
disabled={index === draftSteps.length - 1}
aria-label="Move step down"
aria-label={t('storeSettings.moveDown')}
>
<ChevronDown size={16} />
</button>
</Tooltip>
<Tooltip label="Remove step">
<Tooltip label={t('storeSettings.removeStep')}>
<button
type="button"
className={removeStyles.removeRowBtn}
onClick={() => removeStep(step.id)}
disabled={draftSteps.length <= 1}
aria-label="Remove step"
aria-label={t('storeSettings.removeStep')}
>
<Trash2 size={15} />
</button>
@@ -338,15 +334,13 @@ export function StoreSettingsPage() {
))}
{!draftSteps.length && (
<p className={styles.emptyText}>
No order steps yet. Add the first step to define your workflow.
</p>
<p className={styles.emptyText}>{t('storeSettings.emptySteps')}</p>
)}
</div>
<button type="button" className={styles.addStepBtn} onClick={addStep}>
<Plus size={18} />
Add step
{t('storeSettings.addStep')}
</button>
<div className={styles.stepsActions}>
@@ -356,7 +350,9 @@ export function StoreSettingsPage() {
onClick={() => void handleSaveSteps()}
disabled={!canSaveSteps || savingKey === 'orderSteps'}
>
{savingKey === 'orderSteps' ? 'Saving...' : 'Save steps'}
{savingKey === 'orderSteps'
? t('storeSettings.saving')
: t('storeSettings.saveSteps')}
</button>
</div>
</>