diff --git a/apps/business/src/context/BusinessThemeContext.tsx b/apps/business/src/context/BusinessThemeContext.tsx
index f1d79a9..2a83766 100644
--- a/apps/business/src/context/BusinessThemeContext.tsx
+++ b/apps/business/src/context/BusinessThemeContext.tsx
@@ -1,32 +1,24 @@
import { useEffect, type ReactNode } from 'react'
-import { useAuth } from './AuthContext'
-import { getActiveBusinessId } from '../lib/businessContext'
import { isAbortError } from '../lib/api'
-import { getSettings } from '../services/settingsService'
+import { getBusinessDomain } from '../lib/config'
+import { resolveTenantByDomain } from '../services/tenantService'
import { applyBusinessPrimaryColor, resetBusinessPrimaryColor } from '../utils/applyBusinessTheme'
import { normalizeBusinessPrimaryColorId } from '../utils/businessPrimaryColors'
export function BusinessThemeProvider({ children }: { children: ReactNode }) {
- const { user, isLoading } = useAuth()
-
useEffect(() => {
- if (isLoading) return
-
- if (!user || !getActiveBusinessId()) {
- resetBusinessPrimaryColor()
- return
- }
-
const controller = new AbortController()
+ const domain = getBusinessDomain()
async function loadTheme() {
try {
- const data = await getSettings(controller.signal)
+ const tenant = await resolveTenantByDomain(domain, controller.signal)
+ if (controller.signal.aborted) return
applyBusinessPrimaryColor(
- normalizeBusinessPrimaryColorId(data.settings.branding?.primaryColor),
+ normalizeBusinessPrimaryColorId(tenant.primaryColor),
)
} catch (err) {
- if (isAbortError(err)) return
+ if (isAbortError(err) || controller.signal.aborted) return
applyBusinessPrimaryColor(undefined)
}
}
@@ -35,8 +27,9 @@ export function BusinessThemeProvider({ children }: { children: ReactNode }) {
return () => {
controller.abort()
+ resetBusinessPrimaryColor()
}
- }, [user, isLoading])
+ }, [])
return children
}
diff --git a/apps/business/src/context/TenantBrandingContext.tsx b/apps/business/src/context/TenantBrandingContext.tsx
index c6d3371..ac4ddd5 100644
--- a/apps/business/src/context/TenantBrandingContext.tsx
+++ b/apps/business/src/context/TenantBrandingContext.tsx
@@ -18,6 +18,7 @@ import { resolveTenantByDomain } from '../services/tenantService'
interface TenantBrandingContextValue {
businessName: string
+ businessNameEn: string
logoUrl: string | null
faviconUrl: string | null
refreshBranding: () => void
@@ -135,9 +136,14 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
return nameEn.trim() || nameFa.trim() || getBusinessDomain()
}, [locale, nameEn, nameFa])
+ const businessNameEn = useMemo(
+ () => nameEn.trim() || nameFa.trim() || getBusinessDomain(),
+ [nameEn, nameFa],
+ )
+
const value = useMemo(
- () => ({ businessName, logoUrl, faviconUrl, refreshBranding }),
- [businessName, logoUrl, faviconUrl, refreshBranding],
+ () => ({ businessName, businessNameEn, logoUrl, faviconUrl, refreshBranding }),
+ [businessName, businessNameEn, logoUrl, faviconUrl, refreshBranding],
)
return (
diff --git a/apps/business/src/pages/LoginPage.module.css b/apps/business/src/pages/LoginPage.module.css
index b1e0c98..969236f 100644
--- a/apps/business/src/pages/LoginPage.module.css
+++ b/apps/business/src/pages/LoginPage.module.css
@@ -1,10 +1,12 @@
.page {
min-height: 100vh;
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
position: relative;
+ gap: 20px;
}
.card {
@@ -60,6 +62,18 @@
margin-top: 2px;
}
+.poweredBy {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-muted);
+ text-align: center;
+ transition: color 0.2s;
+}
+
+.poweredBy:hover {
+ color: var(--primary);
+}
+
.title {
font-size: 24px;
font-weight: 700;
diff --git a/apps/business/src/pages/LoginPage.tsx b/apps/business/src/pages/LoginPage.tsx
index cefe433..62d351e 100644
--- a/apps/business/src/pages/LoginPage.tsx
+++ b/apps/business/src/pages/LoginPage.tsx
@@ -23,7 +23,7 @@ type SmsStep = 'phone' | 'code'
export function LoginPage() {
const navigate = useNavigate()
const { login, loginWithOtp } = useAuth()
- const { businessName, logoUrl } = useTenantBranding()
+ const { businessName, businessNameEn, logoUrl } = useTenantBranding()
const businessDomain = getBusinessDomain()
const t = useT()
@@ -217,7 +217,7 @@ export function LoginPage() {
{logoUrl ?
: null}
{businessName || businessDomain}
- {t('app.poweredBy')}
+ {businessNameEn || businessDomain}
@@ -673,6 +673,14 @@ export function LoginPage() {
>
)}
+
+ Powered by Meshkee.app
+
)
}
diff --git a/apps/business/src/services/tenantService.ts b/apps/business/src/services/tenantService.ts
index 3ca5fe3..b54e997 100644
--- a/apps/business/src/services/tenantService.ts
+++ b/apps/business/src/services/tenantService.ts
@@ -1,5 +1,6 @@
import { apiRequest } from '../lib/api'
import type { DashboardLocale } from '@meshkee/dashboard-core'
+import type { BusinessPrimaryColorId } from '../utils/businessPrimaryColors'
export interface ResolvedTenant {
id: string
@@ -7,12 +8,13 @@ export interface ResolvedTenant {
nameFa: string | null
slug: string
domain: string
+ primaryColor: BusinessPrimaryColorId
defaultLocale?: DashboardLocale
logoUrl?: string | null
faviconUrl?: string | null
}
-export async function resolveTenantByDomain(host: string) {
+export async function resolveTenantByDomain(host: string, signal?: AbortSignal) {
const encodedHost = encodeURIComponent(host)
- return apiRequest(`/tenants/${encodedHost}`)
+ return apiRequest(`/tenants/${encodedHost}`, { signal })
}
diff --git a/apps/customer/src/context/TenantBrandingContext.tsx b/apps/customer/src/context/TenantBrandingContext.tsx
index 9a90078..8c0082d 100644
--- a/apps/customer/src/context/TenantBrandingContext.tsx
+++ b/apps/customer/src/context/TenantBrandingContext.tsx
@@ -16,6 +16,7 @@ import { resolveTenantByDomain } from '../services/tenantService'
interface TenantBrandingContextValue {
businessName: string
+ businessNameEn: string
logoUrl: string | null
faviconUrl: string | null
}
@@ -35,6 +36,7 @@ function pickBusinessName(
export function TenantBrandingProvider({ children }: { children: ReactNode }) {
const { setLocale } = useLocale()
const [businessName, setBusinessName] = useState('')
+ const [businessNameEn, setBusinessNameEn] = useState('')
const [logoUrl, setLogoUrl] = useState(null)
const [faviconUrl, setFaviconUrl] = useState(null)
const defaultLocaleAppliedRef = useRef(false)
@@ -59,6 +61,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
tenant.name,
domain,
)
+ const nameEn = pickBusinessName(info?.name, tenant.name, domain)
const nextLogo =
info?.logoUrl?.trim() || tenant.logoUrl?.trim() || null
@@ -69,6 +72,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
null
setBusinessName(name || domain)
+ setBusinessNameEn(nameEn || domain)
setLogoUrl(nextLogo)
setFaviconUrl(nextFavicon)
applyDocumentFavicon(nextFavicon)
@@ -84,6 +88,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
} catch (err) {
if (isAbortError(err) || controller.signal.aborted) return
setBusinessName(domain)
+ setBusinessNameEn(domain)
setLogoUrl(null)
setFaviconUrl(null)
applyDocumentFavicon(null)
@@ -98,8 +103,8 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) {
}, [domain, setLocale])
const value = useMemo(
- () => ({ businessName, logoUrl, faviconUrl }),
- [businessName, logoUrl, faviconUrl],
+ () => ({ businessName, businessNameEn, logoUrl, faviconUrl }),
+ [businessName, businessNameEn, logoUrl, faviconUrl],
)
return (
diff --git a/apps/customer/src/pages/LoginPage.module.css b/apps/customer/src/pages/LoginPage.module.css
index babdbf0..6b88696 100644
--- a/apps/customer/src/pages/LoginPage.module.css
+++ b/apps/customer/src/pages/LoginPage.module.css
@@ -1,10 +1,12 @@
.page {
min-height: 100vh;
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
position: relative;
+ gap: 20px;
}
.card {
@@ -60,6 +62,18 @@
margin-top: 2px;
}
+.poweredBy {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-muted);
+ text-align: center;
+ transition: color 0.2s;
+}
+
+.poweredBy:hover {
+ color: var(--primary);
+}
+
.title {
font-size: 24px;
font-weight: 700;
diff --git a/apps/customer/src/pages/LoginPage.tsx b/apps/customer/src/pages/LoginPage.tsx
index 1a4c9d0..ef801f9 100644
--- a/apps/customer/src/pages/LoginPage.tsx
+++ b/apps/customer/src/pages/LoginPage.tsx
@@ -35,7 +35,7 @@ export function LoginPage() {
const [searchParams] = useSearchParams()
const redirectTo = safeRedirectPath(searchParams.get('redirect'))
const { login, loginWithOtp } = useAuth()
- const { businessName, logoUrl } = useTenantBranding()
+ const { businessName, businessNameEn, logoUrl } = useTenantBranding()
const tenantDomain = getTenantDomain()
const t = useT()
@@ -247,7 +247,7 @@ export function LoginPage() {
{businessName || tenantDomain}
- {t('app.poweredBy')}
+ {businessNameEn || tenantDomain}
@@ -704,6 +704,14 @@ export function LoginPage() {
>
)}
+
+ Powered by Meshkee.app
+
)
}
diff --git a/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css b/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css
index 93cb4ac..f3d284d 100644
--- a/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css
+++ b/apps/super-admin/src/components/BusinessActionsOverflowMenu.module.css
@@ -31,11 +31,11 @@
min-width: 220px;
padding: 6px;
border-radius: 10px;
- border: 1px solid rgba(255, 255, 255, 0.55);
- background: rgba(255, 255, 255, 0.28);
- backdrop-filter: blur(48px) saturate(1.2);
- -webkit-backdrop-filter: blur(48px) saturate(1.2);
- box-shadow: var(--glass-shadow);
+ border: 1px solid rgba(255, 255, 255, 0.85);
+ background: rgba(255, 255, 255, 0.78);
+ backdrop-filter: blur(72px) saturate(1.35);
+ -webkit-backdrop-filter: blur(72px) saturate(1.35);
+ box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16);
animation: menuIn 0.14s ease;
}
@@ -86,6 +86,12 @@
color: #ef4444;
}
+.menuDivider {
+ height: 1px;
+ margin: 4px 6px;
+ background: rgba(148, 163, 184, 0.28);
+}
+
@keyframes menuIn {
from {
opacity: 0;
diff --git a/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx b/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx
index 4656d0b..86e72c2 100644
--- a/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx
+++ b/apps/super-admin/src/components/DomainLinksOverflowMenu.tsx
@@ -1,6 +1,13 @@
import { useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
-import { Building2, ExternalLink, Globe, MoreHorizontal, UserRound } from 'lucide-react'
+import {
+ Building2,
+ ExternalLink,
+ Globe,
+ MoreHorizontal,
+ Trash2,
+ UserRound,
+} from 'lucide-react'
import styles from './BusinessActionsOverflowMenu.module.css'
type PopoverPosition = {
@@ -10,6 +17,7 @@ type PopoverPosition = {
interface DomainLinksOverflowMenuProps {
host: string
+ onRemove: () => void
}
function apexHost(host: string) {
@@ -28,7 +36,7 @@ function openHost(host: string) {
window.open(siteUrl(host), '_blank', 'noopener,noreferrer')
}
-export function DomainLinksOverflowMenu({ host }: DomainLinksOverflowMenuProps) {
+export function DomainLinksOverflowMenu({ host, onRemove }: DomainLinksOverflowMenuProps) {
const [open, setOpen] = useState(false)
const [popoverPos, setPopoverPos] = useState(null)
const wrapRef = useRef(null)
@@ -126,8 +134,8 @@ export function DomainLinksOverflowMenu({ host }: DomainLinksOverflowMenuProps)
e.stopPropagation()
toggleOpen()
}}
- title="Open sites"
- aria-label={`Open sites for ${apex}`}
+ title="More actions"
+ aria-label={`More actions for ${apex}`}
aria-haspopup="menu"
aria-expanded={open}
>
@@ -141,7 +149,7 @@ export function DomainLinksOverflowMenu({ host }: DomainLinksOverflowMenuProps)
ref={popoverRef}
className={styles.menu}
role="menu"
- aria-label={`Open sites for ${apex}`}
+ aria-label={`More actions for ${apex}`}
style={{
top: popoverPos.top,
left: popoverPos.left,
@@ -168,6 +176,19 @@ export function DomainLinksOverflowMenu({ host }: DomainLinksOverflowMenuProps)
))}
+
+
,
document.body,
)}
diff --git a/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css b/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css
index 244a583..3d18fed 100644
--- a/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css
+++ b/apps/super-admin/src/components/PrimaryColorSwatchControl.module.css
@@ -50,11 +50,11 @@
max-width: 280px;
padding: 8px 10px;
border-radius: var(--radius-sm);
- border: 1px solid rgba(255, 255, 255, 0.55);
- background: rgba(255, 255, 255, 0.28);
- backdrop-filter: blur(48px) saturate(1.2);
- -webkit-backdrop-filter: blur(48px) saturate(1.2);
- box-shadow: var(--glass-shadow);
+ border: 1px solid rgba(255, 255, 255, 0.85);
+ background: rgba(255, 255, 255, 0.78);
+ backdrop-filter: blur(72px) saturate(1.35);
+ -webkit-backdrop-filter: blur(72px) saturate(1.35);
+ box-shadow: 0 12px 40px rgba(15, 23, 42, 0.16);
}
.option {
diff --git a/apps/super-admin/src/pages/WebsitesPage.tsx b/apps/super-admin/src/pages/WebsitesPage.tsx
index 2d9cf63..4f8a7c1 100644
--- a/apps/super-admin/src/pages/WebsitesPage.tsx
+++ b/apps/super-admin/src/pages/WebsitesPage.tsx
@@ -9,7 +9,6 @@ import {
Rocket,
RotateCcw,
Search,
- Trash2,
Unlock,
} from 'lucide-react'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
@@ -590,16 +589,10 @@ export function WebsitesPage() {
>
-
-
+ setRemoveTarget(domain)}
+ />