Polish dashboard page chrome and user access UI.

Rename Customers to Users with FA/EN titles, compact headers and filter bars across apps, and refine manager role copy plus a customer-header link to the business dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-11 00:00:09 +03:30
co-authored by Cursor
parent beec3035e5
commit 6c52ce1247
55 changed files with 395 additions and 122 deletions
@@ -122,6 +122,28 @@
.profileWrap {
position: relative;
display: flex;
align-items: center;
gap: 8px;
}
.businessDashBtn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
color: var(--primary);
background: rgba(var(--primary-rgb) / 0.1);
border: 1px solid rgba(var(--primary-rgb) / 0.18);
transition: background 0.15s, transform 0.15s, box-shadow 0.15s;
}
.businessDashBtn:hover {
background: rgba(var(--primary-rgb) / 0.16);
box-shadow: 0 4px 12px rgba(var(--primary-rgb) / 0.18);
transform: translateY(-1px);
}
.profile {
+26 -1
View File
@@ -1,10 +1,12 @@
import { useEffect, useRef, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Menu, Bell, MessageSquare, ChevronDown, User, KeyRound, LogOut } from 'lucide-react'
import { Menu, Bell, MessageSquare, ChevronDown, User, KeyRound, LogOut, LayoutDashboard } from 'lucide-react'
import { LanguageSelect, PasswordResetModal, useLocale } from '@meshkee/dashboard-ui'
import { useAuth } from '../context/AuthContext'
import { useTenantBranding } from '../context/TenantBrandingContext'
import { useT } from '../i18n/useT'
import { getActiveBusinessId } from '../lib/businessContext'
import { getBusinessDashboardUrl } from '../lib/config'
import { changePassword } from '../services/authService'
import meshkeeLogo from '../assets/meshkee-logo.png'
import styles from './Header.module.css'
@@ -57,6 +59,17 @@ export function Header({ hideMenu = false, showBrand = false }: HeaderProps) {
? (businessName && businessName !== brandTitle ? businessName : '')
: (businessNameEn && businessNameEn !== brandTitle ? businessNameEn : '')
const activeBusinessId = getActiveBusinessId()
const canOpenBusinessDashboard = Boolean(
user?.isSuperAdmin ||
user?.roles.includes('super_admin') ||
user?.businesses.some(
(business) =>
String(business.id) === String(activeBusinessId) &&
(business.isOwner || Boolean(business.teamRole)),
),
)
useEffect(() => {
if (!menuOpen) return
@@ -128,6 +141,18 @@ export function Header({ hideMenu = false, showBrand = false }: HeaderProps) {
</button>
<div className={styles.profileWrap} ref={menuRef}>
{canOpenBusinessDashboard ? (
<a
className={styles.businessDashBtn}
href={getBusinessDashboardUrl()}
target="_blank"
rel="noopener noreferrer"
aria-label={t('header.openBusinessDashboard')}
title={t('header.openBusinessDashboard')}
>
<LayoutDashboard size={18} />
</a>
) : null}
<button
type="button"
className={`${styles.profile} ${menuOpen ? styles.profileOpen : ''}`}
@@ -8,19 +8,44 @@
align-items: flex-start;
justify-content: space-between;
gap: 24px;
margin-bottom: 36px;
margin-bottom: 12px;
}
.pageTitle {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
margin-bottom: 0;
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.15em 0.4em;
}
.pageTitleEn {
font-size: 0.52em;
font-weight: 700;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--text-muted);
font-family: var(--font-en), var(--font-ui);
}
.pageTitleEn::before {
content: '•';
margin-inline-end: 0.4em;
letter-spacing: 0;
}
.pageSubtitle {
display: none;
}
.pageSubtitleContent {
display: block;
font-size: 15px;
color: var(--text-secondary);
margin-top: 6px;
}
.dateBadge {
@@ -0,0 +1,19 @@
import { useLocale } from '@meshkee/dashboard-ui'
import type { ReactNode } from 'react'
import pageStyles from './PageContent.module.css'
interface PageTitleProps {
children: ReactNode
/** English accent shown after a bullet in FA locale (e.g. USERS). */
en?: string
}
export function PageTitle({ children, en }: PageTitleProps) {
const { locale } = useLocale()
return (
<h2 className={pageStyles.pageTitle}>
{children}
{en && locale === 'fa' ? <span className={pageStyles.pageTitleEn}>{en}</span> : null}
</h2>
)
}
+2
View File
@@ -21,6 +21,7 @@ const en = {
'header.notifications': 'Notifications',
'header.changePassword': 'Change password',
'header.myProfile': 'My Profile',
'header.openBusinessDashboard': 'Open business dashboard',
'home.welcome': 'Welcome, dear {name}.',
'home.welcomeFallback': 'there',
@@ -406,6 +407,7 @@ const fa: Record<MessageKey, string> = {
'header.notifications': 'اعلان‌ها',
'header.changePassword': 'تغییر رمز عبور',
'header.myProfile': 'پروفایل من',
'header.openBusinessDashboard': 'رفتن به داشبورد کسب‌وکار',
'home.welcome': '{name} عزیز، خوش آمدی.',
'home.welcomeFallback': 'کاربر',
+10
View File
@@ -1,6 +1,7 @@
import {
CUSTOMER_SUBDOMAIN_PREFIX,
getBaseDomainFromHost,
getBusinessDashboardHost,
getCustomerDashboardHost,
isCustomerDashboardHost,
} from '@meshkee/dashboard-core'
@@ -31,3 +32,12 @@ export function isAllowedCustomerHost(hostname = window.location.hostname): bool
export function getTenantDomain(): string {
return getBaseBusinessDomain()
}
/** Open the matching business dashboard (local: port 5173). */
export function getBusinessDashboardUrl(): string {
const host = getBusinessDashboardHost(getBaseBusinessDomain())
const { protocol, port } = window.location
const businessPort = port === '5175' ? '5173' : port
const portSuffix = businessPort ? `:${businessPort}` : ''
return `${protocol}//${host}${portSuffix}/`
}
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useCallback, useEffect, useState } from 'react'
import { MapPin, Pencil, Plus, Trash2 } from 'lucide-react'
import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui'
@@ -107,7 +108,7 @@ export function AddressesPage() {
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>{t('addresses.title')}</h2>
<PageTitle en="ADDRESSES">{t('addresses.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('addresses.subtitle')}</p>
</div>
</div>
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react'
import { Heart } from 'lucide-react'
import { Breadcrumbs, Pagination, useToast } from '@meshkee/dashboard-ui'
@@ -82,7 +83,7 @@ export function FavoritesPage() {
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>{t('favorites.title')}</h2>
<PageTitle en="FAVORITES">{t('favorites.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('favorites.subtitle')}</p>
</div>
</div>
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useMemo, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { ImageOff, MapPin, Pencil } from 'lucide-react'
@@ -172,8 +173,8 @@ export function MyProductDetailsPage() {
<div className={styles.headerRow}>
<div>
<h2 className={pageStyles.pageTitle}>{t('myProducts.detailsTitle')}</h2>
<p className={pageStyles.pageSubtitle}>{t('myProducts.detailsSubtitle')}</p>
<PageTitle en="PRODUCT">{t('myProducts.detailsTitle')}</PageTitle>
<p className={pageStyles.pageSubtitleContent}>{t('myProducts.detailsSubtitle')}</p>
</div>
<button
type="button"
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Package, Plus } from 'lucide-react'
@@ -126,7 +127,7 @@ export function MyProductsPage() {
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>{t('myProducts.title')}</h2>
<PageTitle en="MY PRODUCTS">{t('myProducts.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('myProducts.subtitle')}</p>
</div>
</div>
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useMemo, useState } from 'react'
import { Breadcrumbs, Pagination, useLocale } from '@meshkee/dashboard-ui'
import { OrderItemsModal } from '../components/OrderItemsModal'
@@ -71,7 +72,7 @@ export function OrdersPage() {
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>{t('orders.title')}</h2>
<PageTitle en="ORDERS">{t('orders.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('orders.subtitle')}</p>
</div>
</div>
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react'
import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui'
import { useAuth } from '../context/AuthContext'
@@ -81,7 +82,7 @@ export function ProfilePage() {
<div className={pageStyles.pageHeader}>
<div>
<h2 className={pageStyles.pageTitle}>{t('profile.title')}</h2>
<PageTitle en="PROFILE">{t('profile.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('profile.subtitle')}</p>
</div>
</div>