mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Add FA/EN locale, home activity charts, and theme-aware polish.
Ship shared LocaleProvider, business/customer i18n, branding defaultLocale, curated home tiles with dual 30-day charts, and chart/page aura tokens; refresh PROJECT_CONTEXT. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f5b2193ba1
commit
66004a0fba
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
|
||||
@@ -89,13 +89,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.profileInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -18,8 +18,6 @@ export function Header() {
|
||||
user?.cellNumber ||
|
||||
'Super Admin'
|
||||
|
||||
const avatarSeed = encodeURIComponent(user?.cellNumber ?? 'admin')
|
||||
|
||||
useEffect(() => {
|
||||
if (!menuOpen) return
|
||||
|
||||
@@ -88,11 +86,6 @@ export function Header() {
|
||||
aria-expanded={menuOpen}
|
||||
aria-haspopup="menu"
|
||||
>
|
||||
<img
|
||||
src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${avatarSeed}`}
|
||||
alt={displayName}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
<div className={styles.profileInfo}>
|
||||
<span className={styles.name}>{displayName}</span>
|
||||
<span className={styles.role}>Super Administrator</span>
|
||||
|
||||
@@ -71,3 +71,12 @@
|
||||
transform: translateX(2px);
|
||||
box-shadow: 0 4px 14px rgba(var(--primary-rgb) / 0.35);
|
||||
}
|
||||
|
||||
:global([dir='rtl']) .arrowBtn {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
:global([dir='rtl']) .card:hover .arrowBtn {
|
||||
transform: scaleX(-1) translateX(2px);
|
||||
box-shadow: 0 4px 14px rgba(var(--primary-rgb) / 0.35);
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@
|
||||
--field-padding-y: 9px;
|
||||
--field-padding-x: 12px;
|
||||
--field-height: 38px;
|
||||
--font-en: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-en: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-fa: 'IRANYekan', 'IranYekan', 'Yekan', Tahoma, sans-serif;
|
||||
--font-ui: var(--font-en), var(--font-fa);
|
||||
--card-hover-lift: -4px;
|
||||
--card-hover-shadow: 0 16px 48px rgba(var(--primary-rgb) / 0.14);
|
||||
--card-hover-transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
@@ -50,7 +51,7 @@ body,
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-en);
|
||||
font-family: var(--font-ui);
|
||||
color: var(--text-primary);
|
||||
background:
|
||||
radial-gradient(ellipse 80% 60% at 10% 0%, rgba(99, 102, 241, 0.18) 0%, transparent 55%),
|
||||
@@ -126,7 +127,7 @@ select[multiple] option {
|
||||
|
||||
input:not([type='checkbox']):not([type='radio']):not([type='file']):not([type='range']):not([type='hidden']),
|
||||
textarea {
|
||||
font-family: var(--font-fa), var(--font-en);
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--field-font-size);
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
@@ -147,12 +148,12 @@ textarea:focus {
|
||||
|
||||
input:not([type='checkbox']):not([type='radio']):not([type='file']):not([type='range']):not([type='hidden'])::placeholder,
|
||||
textarea::placeholder {
|
||||
font-family: var(--font-en);
|
||||
font-family: var(--font-ui);
|
||||
}
|
||||
|
||||
[dir='rtl'],
|
||||
:lang(fa),
|
||||
.faText {
|
||||
font-family: var(--font-fa), var(--font-en);
|
||||
font-family: var(--font-ui);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@@ -402,6 +402,47 @@
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.thLocale,
|
||||
.tdLocale {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tdLocale {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.localeSelect {
|
||||
min-height: var(--field-height);
|
||||
padding: var(--field-padding-y) var(--select-padding-end) var(--field-padding-y)
|
||||
var(--field-padding-x);
|
||||
font-size: var(--field-font-size);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
color: var(--text-primary);
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right var(--select-arrow-offset) center;
|
||||
background-size: var(--select-arrow-size);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.localeSelect:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.localeSelect:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(var(--primary-rgb) / 0.12);
|
||||
}
|
||||
|
||||
.td {
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
|
||||
@@ -55,7 +55,10 @@ import {
|
||||
} from '../utils/businessPrimaryColors'
|
||||
import {
|
||||
getBusinessSettings,
|
||||
updateBusinessBranding,
|
||||
updateBusinessDefaultLocale,
|
||||
updateBusinessPrimaryColor,
|
||||
type DashboardLocale,
|
||||
} from '../services/businessSettingsService'
|
||||
import { flattenBusinessCategories } from '../utils/categories'
|
||||
import { Pagination } from '@meshkee/dashboard-ui'
|
||||
@@ -63,6 +66,11 @@ import pageStyles from '../components/PageContent.module.css'
|
||||
import styles from './BusinessesPage.module.css'
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10
|
||||
const DEFAULT_LOCALE: DashboardLocale = 'fa'
|
||||
|
||||
function normalizeDefaultLocale(value: unknown): DashboardLocale {
|
||||
return value === 'en' || value === 'fa' ? value : DEFAULT_LOCALE
|
||||
}
|
||||
|
||||
function formatDate(value: string) {
|
||||
const d = new Date(value)
|
||||
@@ -190,6 +198,7 @@ export function BusinessesPage() {
|
||||
const [editPrimaryColor, setEditPrimaryColor] = useState<BusinessPrimaryColorId>(
|
||||
DEFAULT_BUSINESS_PRIMARY_COLOR_ID,
|
||||
)
|
||||
const [editDefaultLocale, setEditDefaultLocale] = useState<DashboardLocale>(DEFAULT_LOCALE)
|
||||
const [editLoadingSettings, setEditLoadingSettings] = useState(false)
|
||||
const [editSubmitting, setEditSubmitting] = useState(false)
|
||||
const [editError, setEditError] = useState('')
|
||||
@@ -216,6 +225,7 @@ export function BusinessesPage() {
|
||||
const [removeTarget, setRemoveTarget] = useState<BusinessListItem | null>(null)
|
||||
const [togglingId, setTogglingId] = useState<string | null>(null)
|
||||
const [savingColorId, setSavingColorId] = useState<string | null>(null)
|
||||
const [savingLocaleId, setSavingLocaleId] = useState<string | null>(null)
|
||||
|
||||
const [migrateOpen, setMigrateOpen] = useState(false)
|
||||
const [migrateBusiness, setMigrateBusiness] = useState<BusinessListItem | null>(null)
|
||||
@@ -327,6 +337,7 @@ export function BusinessesPage() {
|
||||
setEditBusiness(b)
|
||||
setEditName(b.name)
|
||||
setEditPrimaryColor(normalizeBusinessPrimaryColorId(b.primaryColor))
|
||||
setEditDefaultLocale(normalizeDefaultLocale(b.defaultLocale))
|
||||
setEditError('')
|
||||
setEditOpen(true)
|
||||
setEditLoadingSettings(true)
|
||||
@@ -335,6 +346,7 @@ export function BusinessesPage() {
|
||||
void getBusinessSettings(b.id, controller.signal)
|
||||
.then((data) => {
|
||||
setEditPrimaryColor(data.settings.branding.primaryColor)
|
||||
setEditDefaultLocale(normalizeDefaultLocale(data.settings.branding.defaultLocale))
|
||||
})
|
||||
.catch((err) => {
|
||||
if (isAbortError(err)) return
|
||||
@@ -405,20 +417,76 @@ export function BusinessesPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDefaultLocaleChange(b: BusinessListItem, defaultLocale: DashboardLocale) {
|
||||
const currentLocale = normalizeDefaultLocale(b.defaultLocale)
|
||||
if (currentLocale === defaultLocale) return
|
||||
|
||||
setSavingLocaleId(b.id)
|
||||
setError('')
|
||||
|
||||
setData((prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
item.id === b.id ? { ...item, defaultLocale } : item,
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
if (editBusiness?.id === b.id) {
|
||||
setEditDefaultLocale(defaultLocale)
|
||||
}
|
||||
|
||||
try {
|
||||
await updateBusinessDefaultLocale(b.id, defaultLocale)
|
||||
showToast(`Default language updated for "${b.name}".`, 'success')
|
||||
} catch (err) {
|
||||
setData((prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
item.id === b.id ? { ...item, defaultLocale: currentLocale } : item,
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
if (editBusiness?.id === b.id) {
|
||||
setEditDefaultLocale(currentLocale)
|
||||
}
|
||||
|
||||
showToast(
|
||||
err instanceof ApiError ? err.message : 'Unable to update default language.',
|
||||
'error',
|
||||
)
|
||||
} finally {
|
||||
setSavingLocaleId(null)
|
||||
}
|
||||
}
|
||||
|
||||
async function submitEdit() {
|
||||
if (!editBusiness) return
|
||||
setEditSubmitting(true)
|
||||
setEditError('')
|
||||
try {
|
||||
await updateBusiness(editBusiness.id, { name: editName })
|
||||
await updateBusinessPrimaryColor(editBusiness.id, editPrimaryColor)
|
||||
await updateBusinessBranding(editBusiness.id, {
|
||||
primaryColor: editPrimaryColor,
|
||||
defaultLocale: editDefaultLocale,
|
||||
})
|
||||
setData((prev) => {
|
||||
if (!prev) return prev
|
||||
return {
|
||||
...prev,
|
||||
items: prev.items.map((item) =>
|
||||
item.id === editBusiness.id
|
||||
? { ...item, name: editName.trim(), primaryColor: editPrimaryColor }
|
||||
? {
|
||||
...item,
|
||||
name: editName.trim(),
|
||||
primaryColor: editPrimaryColor,
|
||||
defaultLocale: editDefaultLocale,
|
||||
}
|
||||
: item,
|
||||
),
|
||||
}
|
||||
@@ -828,13 +896,14 @@ export function BusinessesPage() {
|
||||
<th className={styles.th}>Domain</th>
|
||||
<th className={styles.th}>Owner</th>
|
||||
<th className={`${styles.th} ${styles.thTheme}`}>Theme</th>
|
||||
<th className={`${styles.th} ${styles.thLocale}`}>Lang</th>
|
||||
<th className={`${styles.th} ${styles.thActions}`}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading && (
|
||||
<tr>
|
||||
<td className={styles.td} colSpan={6}>
|
||||
<td className={styles.td} colSpan={7}>
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
@@ -842,7 +911,7 @@ export function BusinessesPage() {
|
||||
|
||||
{!loading && data?.items?.length === 0 && (
|
||||
<tr>
|
||||
<td className={styles.td} colSpan={6}>
|
||||
<td className={styles.td} colSpan={7}>
|
||||
No results found.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -900,6 +969,23 @@ export function BusinessesPage() {
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className={`${styles.td} ${styles.tdLocale}`}>
|
||||
<select
|
||||
className={styles.localeSelect}
|
||||
value={normalizeDefaultLocale(b.defaultLocale)}
|
||||
disabled={savingLocaleId === b.id}
|
||||
aria-label={`Default language for ${b.name}`}
|
||||
onChange={(e) =>
|
||||
void handleDefaultLocaleChange(
|
||||
b,
|
||||
normalizeDefaultLocale(e.target.value),
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="fa">FA</option>
|
||||
<option value="en">EN</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className={`${styles.td} ${styles.tdActions}`}>
|
||||
<div className={styles.rowActions}>
|
||||
<span className={styles.toggleInActions}>
|
||||
@@ -1058,6 +1144,26 @@ export function BusinessesPage() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.field}>
|
||||
<label htmlFor="edit-default-locale">Default dashboard language</label>
|
||||
{editLoadingSettings ? (
|
||||
<p className={styles.helperText}>Loading language...</p>
|
||||
) : (
|
||||
<select
|
||||
id="edit-default-locale"
|
||||
className={styles.localeSelect}
|
||||
value={editDefaultLocale}
|
||||
disabled={editSubmitting}
|
||||
onChange={(e) => setEditDefaultLocale(normalizeDefaultLocale(e.target.value))}
|
||||
>
|
||||
<option value="fa">Farsi (FA)</option>
|
||||
<option value="en">English (EN)</option>
|
||||
</select>
|
||||
)}
|
||||
<p className={styles.helperText}>
|
||||
Business and customer dashboards open in this language.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ height: 12 }} />
|
||||
<div className={styles.actionsRow}>
|
||||
<button
|
||||
|
||||
@@ -13,6 +13,8 @@ function buildFormData(user: ReturnType<typeof useAuth>['user']): ProfileFormDat
|
||||
return {
|
||||
firstName: user?.firstName ?? '',
|
||||
lastName: user?.lastName ?? '',
|
||||
firstNameEn: user?.firstNameEn ?? '',
|
||||
lastNameEn: user?.lastNameEn ?? '',
|
||||
cellNumber: user ? formatCellForDisplay(user.cellNumber) : '',
|
||||
email: user?.email ?? '',
|
||||
about: user?.profile?.about ?? emptyProfile.about,
|
||||
@@ -85,23 +87,51 @@ export function ProfilePage() {
|
||||
<h3 className={styles.sectionTitle}>General</h3>
|
||||
<div className={styles.grid}>
|
||||
<div className={`${styles.field} ${styles.col3}`}>
|
||||
<label htmlFor="firstName">Name</label>
|
||||
<label htmlFor="firstName">First name (FA)</label>
|
||||
<input
|
||||
id="firstName"
|
||||
type="text"
|
||||
value={form.firstName}
|
||||
onChange={(e) => updateField('firstName', e.target.value)}
|
||||
placeholder="First name"
|
||||
dir="rtl"
|
||||
lang="fa"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles.field} ${styles.col3}`}>
|
||||
<label htmlFor="lastName">Last name</label>
|
||||
<label htmlFor="lastName">Last name (FA)</label>
|
||||
<input
|
||||
id="lastName"
|
||||
type="text"
|
||||
value={form.lastName}
|
||||
onChange={(e) => updateField('lastName', e.target.value)}
|
||||
placeholder="Last name"
|
||||
dir="rtl"
|
||||
lang="fa"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles.field} ${styles.col3}`}>
|
||||
<label htmlFor="firstNameEn">First name (EN)</label>
|
||||
<input
|
||||
id="firstNameEn"
|
||||
type="text"
|
||||
value={form.firstNameEn}
|
||||
onChange={(e) => updateField('firstNameEn', e.target.value)}
|
||||
placeholder="First name"
|
||||
dir="ltr"
|
||||
lang="en"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles.field} ${styles.col3}`}>
|
||||
<label htmlFor="lastNameEn">Last name (EN)</label>
|
||||
<input
|
||||
id="lastNameEn"
|
||||
type="text"
|
||||
value={form.lastNameEn}
|
||||
onChange={(e) => updateField('lastNameEn', e.target.value)}
|
||||
placeholder="Last name"
|
||||
dir="ltr"
|
||||
lang="en"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${styles.field} ${styles.col3}`}>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { apiRequest } from '../lib/api'
|
||||
import type { BusinessPrimaryColorId } from '../utils/businessPrimaryColors'
|
||||
|
||||
export type DashboardLocale = 'en' | 'fa'
|
||||
|
||||
export interface BrandingSettings {
|
||||
primaryColor: BusinessPrimaryColorId
|
||||
defaultLocale: DashboardLocale
|
||||
}
|
||||
|
||||
export interface BusinessSettings {
|
||||
@@ -41,3 +44,27 @@ export async function updateBusinessPrimaryColor(
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateBusinessDefaultLocale(
|
||||
businessId: string,
|
||||
defaultLocale: DashboardLocale,
|
||||
) {
|
||||
return apiRequest<BusinessSettingsResponse>(`/businesses/${businessId}/settings`, {
|
||||
method: 'PATCH',
|
||||
auth: true,
|
||||
body: {
|
||||
branding: { defaultLocale },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function updateBusinessBranding(
|
||||
businessId: string,
|
||||
branding: Partial<BrandingSettings>,
|
||||
) {
|
||||
return apiRequest<BusinessSettingsResponse>(`/businesses/${businessId}/settings`, {
|
||||
method: 'PATCH',
|
||||
auth: true,
|
||||
body: { branding },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ export async function updateProfile(data: ProfileFormData) {
|
||||
body: {
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
firstNameEn: data.firstNameEn,
|
||||
lastNameEn: data.lastNameEn,
|
||||
email: data.email || null,
|
||||
about: data.about,
|
||||
city: data.city,
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface AuthUser {
|
||||
email: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
firstNameEn: string | null
|
||||
lastNameEn: string | null
|
||||
cellVerifiedAt: string | null
|
||||
roles: string[]
|
||||
dashboard: DashboardType
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { BusinessPrimaryColorId } from '../utils/businessPrimaryColors'
|
||||
import type { DashboardLocale } from '../services/businessSettingsService'
|
||||
|
||||
export interface BusinessOwnerInfo {
|
||||
name: string | null
|
||||
@@ -21,6 +22,7 @@ export interface BusinessListItem {
|
||||
ownerCellNumber: string | null
|
||||
isActive: boolean
|
||||
primaryColor: BusinessPrimaryColorId
|
||||
defaultLocale: DashboardLocale
|
||||
oldBusinessId: string | null
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface UserProfile {
|
||||
export interface ProfileFormData {
|
||||
firstName: string
|
||||
lastName: string
|
||||
firstNameEn: string
|
||||
lastNameEn: string
|
||||
cellNumber: string
|
||||
email: string
|
||||
about: string
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import {
|
||||
DEFAULT_BUSINESS_PRIMARY_COLOR_ID,
|
||||
getBusinessPrimaryColorTokens,
|
||||
type BusinessPrimaryColorId,
|
||||
} from './businessPrimaryColors'
|
||||
|
||||
const CSS_VAR_DEFAULTS: Record<string, string> = {
|
||||
'--primary': '#3b82f6',
|
||||
'--primary-glow': '#3b82f6',
|
||||
'--primary-light': '#dbeafe',
|
||||
'--primary-dark': '#2563eb',
|
||||
'--primary-rgb': '59 130 246',
|
||||
'--primary-dark-rgb': '37 99 235',
|
||||
'--chart-accent': '#06b6d4',
|
||||
'--chart-accent-dark': '#0891b2',
|
||||
'--chart-accent-rgb': '6 182 212',
|
||||
'--chart-accent-dark-rgb': '8 145 178',
|
||||
}
|
||||
|
||||
export function applyBusinessPrimaryColor(colorId?: BusinessPrimaryColorId | null) {
|
||||
const root = document.documentElement
|
||||
const tokens = getBusinessPrimaryColorTokens(colorId ?? DEFAULT_BUSINESS_PRIMARY_COLOR_ID)
|
||||
|
||||
root.style.setProperty('--primary', tokens.primary)
|
||||
root.style.setProperty('--primary-glow', tokens.primaryGlow)
|
||||
root.style.setProperty('--primary-light', tokens.primaryLight)
|
||||
root.style.setProperty('--primary-dark', tokens.primaryDark)
|
||||
root.style.setProperty('--primary-rgb', tokens.primaryRgb)
|
||||
root.style.setProperty('--primary-dark-rgb', tokens.primaryDarkRgb)
|
||||
root.style.setProperty('--chart-accent', tokens.chartAccent)
|
||||
root.style.setProperty('--chart-accent-dark', tokens.chartAccentDark)
|
||||
root.style.setProperty('--chart-accent-rgb', tokens.chartAccentRgb)
|
||||
root.style.setProperty('--chart-accent-dark-rgb', tokens.chartAccentDarkRgb)
|
||||
}
|
||||
|
||||
export function resetBusinessPrimaryColor() {
|
||||
const root = document.documentElement
|
||||
|
||||
for (const [name, value] of Object.entries(CSS_VAR_DEFAULTS)) {
|
||||
root.style.setProperty(name, value)
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,28 @@ export type BusinessPrimaryColorTokens = {
|
||||
primaryLight: string
|
||||
primaryGlow: string
|
||||
primaryRgb: string
|
||||
primaryDarkRgb: string
|
||||
/** Second chart series color (red→purple, blue→cyan, …). */
|
||||
chartAccent: string
|
||||
chartAccentDark: string
|
||||
chartAccentRgb: string
|
||||
chartAccentDarkRgb: string
|
||||
}
|
||||
|
||||
const PURPLE_ACCENT = {
|
||||
chartAccent: '#a855f7',
|
||||
chartAccentDark: '#9333ea',
|
||||
chartAccentRgb: '168 85 247',
|
||||
chartAccentDarkRgb: '147 51 234',
|
||||
} as const
|
||||
|
||||
const CYAN_ACCENT = {
|
||||
chartAccent: '#06b6d4',
|
||||
chartAccentDark: '#0891b2',
|
||||
chartAccentRgb: '6 182 212',
|
||||
chartAccentDarkRgb: '8 145 178',
|
||||
} as const
|
||||
|
||||
export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
BusinessPrimaryColorId,
|
||||
BusinessPrimaryColorTokens
|
||||
@@ -32,6 +52,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#fee2e2',
|
||||
primaryGlow: '#ef4444',
|
||||
primaryRgb: '239 68 68',
|
||||
primaryDarkRgb: '220 38 38',
|
||||
...PURPLE_ACCENT,
|
||||
},
|
||||
yellow: {
|
||||
label: 'Yellow',
|
||||
@@ -40,6 +62,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#fef9c3',
|
||||
primaryGlow: '#eab308',
|
||||
primaryRgb: '234 179 8',
|
||||
primaryDarkRgb: '202 138 4',
|
||||
...PURPLE_ACCENT,
|
||||
},
|
||||
black: {
|
||||
label: 'Black',
|
||||
@@ -48,6 +72,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#e2e8f0',
|
||||
primaryGlow: '#334155',
|
||||
primaryRgb: '30 41 59',
|
||||
primaryDarkRgb: '15 23 42',
|
||||
...CYAN_ACCENT,
|
||||
},
|
||||
cyan: {
|
||||
label: 'Cyan',
|
||||
@@ -56,6 +82,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#cffafe',
|
||||
primaryGlow: '#06b6d4',
|
||||
primaryRgb: '6 182 212',
|
||||
primaryDarkRgb: '8 145 178',
|
||||
...PURPLE_ACCENT,
|
||||
},
|
||||
purple: {
|
||||
label: 'Purple',
|
||||
@@ -64,6 +92,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#f3e8ff',
|
||||
primaryGlow: '#a855f7',
|
||||
primaryRgb: '168 85 247',
|
||||
primaryDarkRgb: '147 51 234',
|
||||
...CYAN_ACCENT,
|
||||
},
|
||||
'light-blue': {
|
||||
label: 'Light Blue',
|
||||
@@ -72,6 +102,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#e0f2fe',
|
||||
primaryGlow: '#38bdf8',
|
||||
primaryRgb: '56 189 248',
|
||||
primaryDarkRgb: '14 165 233',
|
||||
...CYAN_ACCENT,
|
||||
},
|
||||
'dark-blue': {
|
||||
label: 'Dark Blue',
|
||||
@@ -80,6 +112,8 @@ export const BUSINESS_PRIMARY_COLOR_PALETTE: Record<
|
||||
primaryLight: '#dbeafe',
|
||||
primaryGlow: '#3b82f6',
|
||||
primaryRgb: '59 130 246',
|
||||
primaryDarkRgb: '37 99 235',
|
||||
...CYAN_ACCENT,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -93,3 +127,11 @@ export function normalizeBusinessPrimaryColorId(value: unknown): BusinessPrimary
|
||||
|
||||
return DEFAULT_BUSINESS_PRIMARY_COLOR_ID
|
||||
}
|
||||
|
||||
export function getBusinessPrimaryColorTokens(
|
||||
colorId: BusinessPrimaryColorId | undefined | null,
|
||||
): BusinessPrimaryColorTokens {
|
||||
return BUSINESS_PRIMARY_COLOR_PALETTE[
|
||||
normalizeBusinessPrimaryColorId(colorId ?? DEFAULT_BUSINESS_PRIMARY_COLOR_ID)
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user