Add customer and business user-product flows across dashboards.

Ship my-products / customer-products UI with gallery uploads, status controls, module gating, and related shared UI polish.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 00:23:03 +03:30
co-authored by Cursor
parent c5bdaad16b
commit 1271d96539
96 changed files with 10532 additions and 345 deletions
+106 -22
View File
@@ -53,11 +53,16 @@ import {
updateBusinessDefaultLocale,
updateBusinessModules,
updateBusinessPrimaryColor,
updateBusinessThemeMode,
type DashboardLocale,
type DashboardThemeMode,
} from '../services/businessSettingsService'
import {
BUSINESS_DASHBOARD_MODULE_IDS,
BUSINESS_DASHBOARD_MODULE_LABELS,
BUSINESS_MODULE_IDS,
BUSINESS_MODULE_LABELS,
CUSTOMER_MODULE_IDS,
CUSTOMER_MODULE_LABELS,
DEFAULT_ENABLED_BUSINESS_MODULES,
DEFAULT_HOME_CHARTS,
HOME_CHART_IDS,
@@ -74,11 +79,16 @@ import styles from './BusinessesPage.module.css'
const DEFAULT_PAGE_SIZE = 10
const DEFAULT_LOCALE: DashboardLocale = 'fa'
const DEFAULT_THEME_MODE: DashboardThemeMode = 'light'
function normalizeDefaultLocale(value: unknown): DashboardLocale {
return value === 'en' || value === 'fa' ? value : DEFAULT_LOCALE
}
function normalizeThemeMode(value: unknown): DashboardThemeMode {
return value === 'dark' || value === 'light' ? value : DEFAULT_THEME_MODE
}
function emptyModuleSelection(
enabled: BusinessModuleId[] = DEFAULT_ENABLED_BUSINESS_MODULES,
): Record<BusinessModuleId, boolean> {
@@ -400,7 +410,7 @@ export function BusinessesPage() {
try {
await updateBusinessPrimaryColor(b.id, primaryColor)
showToast(`Theme updated for "${b.name}".`, 'success')
showToast(`Theme color updated for "${b.name}".`, 'success')
} catch (err) {
setData((prev) => {
if (!prev) return prev
@@ -413,7 +423,53 @@ export function BusinessesPage() {
})
showToast(
err instanceof ApiError ? err.message : 'Unable to update theme.',
err instanceof ApiError ? err.message : 'Unable to update theme color.',
'error',
)
} finally {
setSavingColorId(null)
}
}
async function handleThemeModeChange(
b: BusinessListItem,
themeMode: DashboardThemeMode,
) {
const currentMode = normalizeThemeMode(b.themeMode)
if (currentMode === themeMode) return
setSavingColorId(b.id)
setError('')
setData((prev) => {
if (!prev) return prev
return {
...prev,
items: prev.items.map((item) =>
item.id === b.id ? { ...item, themeMode } : item,
),
}
})
try {
await updateBusinessThemeMode(b.id, themeMode)
showToast(
`${themeMode === 'dark' ? 'Dark' : 'Light'} theme set for "${b.name}".`,
'success',
)
} catch (err) {
setData((prev) => {
if (!prev) return prev
return {
...prev,
items: prev.items.map((item) =>
item.id === b.id ? { ...item, themeMode: currentMode } : item,
),
}
})
showToast(
err instanceof ApiError ? err.message : 'Unable to update theme mode.',
'error',
)
} finally {
@@ -1103,10 +1159,14 @@ export function BusinessesPage() {
<td className={`${styles.td} ${styles.tdTheme}`}>
<PrimaryColorSwatchControl
value={normalizeBusinessPrimaryColorId(b.primaryColor)}
themeMode={normalizeThemeMode(b.themeMode)}
disabled={savingColorId === b.id}
onChange={(primaryColor) =>
void handlePrimaryColorChange(b, primaryColor)
}
onThemeModeChange={(themeMode) =>
void handleThemeModeChange(b, themeMode)
}
/>
</td>
<td className={`${styles.td} ${styles.tdLocale}`}>
@@ -1384,26 +1444,50 @@ export function BusinessesPage() {
</p>
) : null}
<p className={styles.helperText}>
Choose which optional modules this business can use. Customers, website,
and other general sections stay available for every business.
Choose which optional modules this business can use. Home, profile,
website, and other general sections stay available for every business.
</p>
<div className={styles.modulesChecks}>
{BUSINESS_MODULE_IDS.map((id) => (
<label key={id} className={styles.entityCheck}>
<input
type="checkbox"
checked={modulesSelected[id]}
disabled={modulesSubmitting}
onChange={(e) =>
setModulesSelected((prev) => ({
...prev,
[id]: e.target.checked,
}))
}
/>
<span>{BUSINESS_MODULE_LABELS[id]}</span>
</label>
))}
<div className={styles.modulesSection}>
<p className={styles.entityGroupLabel}>Business modules</p>
<div className={styles.modulesChecks}>
{BUSINESS_DASHBOARD_MODULE_IDS.map((id) => (
<label key={id} className={styles.entityCheck}>
<input
type="checkbox"
checked={modulesSelected[id]}
disabled={modulesSubmitting}
onChange={(e) =>
setModulesSelected((prev) => ({
...prev,
[id]: e.target.checked,
}))
}
/>
<span>{BUSINESS_DASHBOARD_MODULE_LABELS[id]}</span>
</label>
))}
</div>
</div>
<div className={styles.modulesSection}>
<p className={styles.entityGroupLabel}>Customer modules</p>
<div className={styles.modulesChecks}>
{CUSTOMER_MODULE_IDS.map((id) => (
<label key={id} className={styles.entityCheck}>
<input
type="checkbox"
checked={modulesSelected[id]}
disabled={modulesSubmitting}
onChange={(e) =>
setModulesSelected((prev) => ({
...prev,
[id]: e.target.checked,
}))
}
/>
<span>{CUSTOMER_MODULE_LABELS[id]}</span>
</label>
))}
</div>
</div>
<div className={styles.modulesCharts}>
<p className={styles.entityGroupLabel}>Home charts</p>