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
@@ -5,6 +5,13 @@ import { resolveTenantByDomain } from '../services/tenantService'
import { applyBusinessPrimaryColor, resetBusinessPrimaryColor } from '../utils/applyBusinessTheme'
import { normalizeBusinessPrimaryColorId } from '../utils/businessPrimaryColors'
function applyThemeMode(mode: 'light' | 'dark' | undefined) {
document.documentElement.setAttribute(
'data-theme',
mode === 'dark' ? 'dark' : 'light',
)
}
export function CustomerThemeProvider({ children }: { children: ReactNode }) {
useEffect(() => {
const controller = new AbortController()
@@ -13,12 +20,15 @@ export function CustomerThemeProvider({ children }: { children: ReactNode }) {
async function loadTheme() {
try {
const tenant = await resolveTenantByDomain(domain, controller.signal)
if (controller.signal.aborted) return
applyBusinessPrimaryColor(
normalizeBusinessPrimaryColorId(tenant.primaryColor),
)
applyThemeMode(tenant.themeMode)
} catch (err) {
if (isAbortError(err)) return
applyBusinessPrimaryColor(undefined)
applyThemeMode('light')
}
}
@@ -13,12 +13,18 @@ import { isAbortError } from '../lib/api'
import { getTenantDomain } from '../lib/config'
import { getWebsiteBusinessInfo } from '../services/websiteService'
import { resolveTenantByDomain } from '../services/tenantService'
import {
DEFAULT_ENABLED_BUSINESS_MODULES,
normalizeEnabledBusinessModules,
type BusinessModuleId,
} from '../utils/businessModules'
interface TenantBrandingContextValue {
businessName: string
businessNameEn: string
logoUrl: string | null
faviconUrl: string | null
enabledModules: BusinessModuleId[]
}
const TenantBrandingContext = createContext<TenantBrandingContextValue | null>(null)
@@ -39,6 +45,9 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
const [businessNameEn, setBusinessNameEn] = useState('')
const [logoUrl, setLogoUrl] = useState<string | null>(null)
const [faviconUrl, setFaviconUrl] = useState<string | null>(null)
const [enabledModules, setEnabledModules] = useState<BusinessModuleId[]>(
DEFAULT_ENABLED_BUSINESS_MODULES,
)
const defaultLocaleAppliedRef = useRef(false)
const domain = getTenantDomain()
@@ -75,6 +84,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
setBusinessNameEn(nameEn || domain)
setLogoUrl(nextLogo)
setFaviconUrl(nextFavicon)
setEnabledModules(normalizeEnabledBusinessModules(tenant.enabledModules))
applyDocumentFavicon(nextFavicon)
if (!defaultLocaleAppliedRef.current) {
@@ -91,6 +101,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
setBusinessNameEn(domain)
setLogoUrl(null)
setFaviconUrl(null)
setEnabledModules([...DEFAULT_ENABLED_BUSINESS_MODULES])
applyDocumentFavicon(null)
}
}
@@ -103,8 +114,8 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
}, [domain, setLocale])
const value = useMemo(
() => ({ businessName, businessNameEn, logoUrl, faviconUrl }),
[businessName, businessNameEn, logoUrl, faviconUrl],
() => ({ businessName, businessNameEn, logoUrl, faviconUrl, enabledModules }),
[businessName, businessNameEn, logoUrl, faviconUrl, enabledModules],
)
return (