Add business dashboard dark mode from Super Admin theme setting.

Login and in-app surfaces follow tenant themeMode, matching customer dark tokens, with themed switches and tighter card/table chrome.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-31 13:40:40 +03:30
co-authored by Cursor
parent 65012717d0
commit e9400c2281
83 changed files with 586 additions and 193 deletions
@@ -2,12 +2,13 @@ import { useEffect, type ReactNode } from 'react'
import { isAbortError } from '../lib/api'
import { getBusinessDomain } from '../lib/config'
import { resolveTenantByDomain } from '../services/tenantService'
import { applyBusinessPrimaryColor } from '../utils/applyBusinessTheme'
import { applyBusinessPrimaryColor, applyThemeMode } from '../utils/applyBusinessTheme'
import { normalizeBusinessPrimaryColorId } from '../utils/businessPrimaryColors'
/**
* Applies the tenant primary color. Does not reset to blue on unmount —
* Strict Mode / HMR remounts were wiping the brand color back to the default.
* Applies the tenant primary color and dashboard theme mode.
* Does not reset to blue on unmount — Strict Mode / HMR remounts were
* wiping the brand color back to the default.
*/
export function BusinessThemeProvider({ children }: { children: ReactNode }) {
useEffect(() => {
@@ -21,9 +22,11 @@ export function BusinessThemeProvider({ children }: { children: ReactNode }) {
applyBusinessPrimaryColor(
normalizeBusinessPrimaryColorId(tenant.primaryColor),
)
applyThemeMode(tenant.themeMode)
} catch (err) {
if (isAbortError(err) || controller.signal.aborted) return
applyBusinessPrimaryColor(undefined)
applyThemeMode('light')
}
}
@@ -15,7 +15,12 @@ import { getBusinessDomain } from '../lib/config'
import { BUSINESS_PROFILE_UPDATED_EVENT } from '../lib/businessContext'
import { getBusinessProfile } from '../services/businessProfileService'
import { resolveTenantByDomain } from '../services/tenantService'
import { applyBusinessPrimaryColor } from '../utils/applyBusinessTheme'
import {
applyBusinessPrimaryColor,
applyThemeMode,
pickThemedLogoUrl,
type DashboardThemeMode,
} from '../utils/applyBusinessTheme'
import { normalizeBusinessPrimaryColorId } from '../utils/businessPrimaryColors'
import {
DEFAULT_ENABLED_BUSINESS_MODULES,
@@ -31,6 +36,7 @@ interface TenantBrandingContextValue {
businessNameEn: string
logoUrl: string | null
faviconUrl: string | null
themeMode: DashboardThemeMode
enabledModules: BusinessModuleId[]
homeCharts: [HomeChartId, HomeChartId]
refreshBranding: () => void
@@ -54,6 +60,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
const [nameFa, setNameFa] = useState('')
const [logoUrl, setLogoUrl] = useState<string | null>(null)
const [faviconUrl, setFaviconUrl] = useState<string | null>(null)
const [themeMode, setThemeMode] = useState<DashboardThemeMode>('light')
const [enabledModules, setEnabledModules] = useState<BusinessModuleId[]>(
DEFAULT_ENABLED_BUSINESS_MODULES,
)
@@ -78,7 +85,13 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
let nextNameEn = pickBusinessName(tenant.name, domain)
let nextNameFa = pickBusinessName(tenant.nameFa, tenant.name, domain)
let nextLogo = tenant.logoUrl?.trim() || null
const nextThemeMode: DashboardThemeMode =
tenant.themeMode === 'dark' ? 'dark' : 'light'
let nextLogo = pickThemedLogoUrl(
tenant.logoUrl,
tenant.logoDarkUrl,
nextThemeMode,
)
let nextFavicon =
tenant.faviconUrl?.trim() || tenant.logoUrl?.trim() || null
@@ -96,7 +109,12 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
nextNameFa,
domain,
)
nextLogo = profile.profile.logoUrl?.trim() || nextLogo
nextLogo =
pickThemedLogoUrl(
profile.profile.logoUrl,
profile.profile.logoDarkUrl,
nextThemeMode,
) || nextLogo
nextFavicon =
profile.profile.faviconUrl?.trim() ||
profile.profile.logoUrl?.trim() ||
@@ -113,12 +131,14 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
setNameFa(nextNameFa || nextNameEn || domain)
setLogoUrl(nextLogo)
setFaviconUrl(nextFavicon)
setThemeMode(nextThemeMode)
setEnabledModules(normalizeEnabledBusinessModules(tenant.enabledModules))
setHomeCharts(normalizeHomeCharts(tenant.homeCharts))
applyDocumentFavicon(nextFavicon)
applyBusinessPrimaryColor(
normalizeBusinessPrimaryColorId(tenant.primaryColor),
)
applyThemeMode(nextThemeMode)
if (!defaultLocaleAppliedRef.current) {
defaultLocaleAppliedRef.current = true
if (tenant.defaultLocale === 'en' || tenant.defaultLocale === 'fa') {
@@ -135,10 +155,12 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
setNameFa(domain)
setLogoUrl(null)
setFaviconUrl(null)
setThemeMode('light')
setEnabledModules([...DEFAULT_ENABLED_BUSINESS_MODULES])
setHomeCharts([...DEFAULT_HOME_CHARTS])
applyDocumentFavicon(null)
applyBusinessPrimaryColor(undefined)
applyThemeMode('light')
}
}
@@ -173,6 +195,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
businessNameEn,
logoUrl,
faviconUrl,
themeMode,
enabledModules,
homeCharts,
refreshBranding,
@@ -182,6 +205,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
businessNameEn,
logoUrl,
faviconUrl,
themeMode,
enabledModules,
homeCharts,
refreshBranding,