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
@@ -6,7 +6,7 @@
.roleOption {
display: flex;
align-items: center;
align-items: flex-start;
gap: 10px;
padding: 10px 12px;
border-radius: 10px;
@@ -16,17 +16,37 @@
font-family: var(--font-ui);
}
.roleOption input[type='radio'] {
accent-color: var(--primary);
margin-top: 2px;
flex-shrink: 0;
}
.roleOption:has(input:checked) {
border-color: rgba(var(--primary-rgb) / 0.35);
background: rgba(var(--primary-rgb) / 0.08);
}
.roleOptionText {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.roleOptionLabel {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.roleOptionDesc {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
line-height: 1.45;
}
.roleHint {
margin: 12px 0 8px;
font-size: 12px;
@@ -36,12 +56,25 @@
}
.roleListNested {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
margin-top: 4px;
margin-inline-start: 12px;
padding-inline-start: 10px;
border-inline-start: 2px solid rgba(var(--primary-rgb) / 0.2);
}
@media (min-width: 560px) {
.roleListNested {
grid-template-columns: 1fr 1fr;
}
.roleListNested .roleOption:last-child:nth-child(odd) {
grid-column: 1 / -1;
}
}
.roleBadge {
display: inline-block;
margin-top: 4px;
@@ -23,6 +23,12 @@ const TEAM_ROLE_LABEL_KEYS: Record<(typeof ALL_TEAM_ROLES)[number], BusinessMess
viewer: 'customers.access.role.viewer',
}
const TEAM_ROLE_DESC_KEYS: Record<(typeof ALL_TEAM_ROLES)[number], BusinessMessageKey> = {
admin: 'customers.access.role.admin.desc',
editor: 'customers.access.role.editor.desc',
viewer: 'customers.access.role.viewer.desc',
}
export interface ChangeUserAccessModalProps {
open: boolean
user: BusinessCustomerListItem | null
@@ -185,27 +191,33 @@ export function ChangeUserAccessModal({
{access === 'staff' ? (
<>
<p className={styles.roleHint}>
{canAssignAdmin
? t('customers.access.hintSuperAdmin')
: t('customers.access.hintBusiness')}
</p>
{!canAssignAdmin ? (
<p className={styles.roleHint}>{t('customers.access.hintBusiness')}</p>
) : null}
<div className={`${styles.roleList} ${styles.roleListNested}`}>
{teamRoles.map((slug) => (
<label key={slug} className={styles.roleOption}>
<input
type="radio"
name="team-role"
value={slug}
checked={teamRole === slug}
onChange={() => setTeamRole(slug)}
disabled={submitting}
/>
<span className={styles.roleOptionLabel}>
{t(TEAM_ROLE_LABEL_KEYS[slug as (typeof ALL_TEAM_ROLES)[number]])}
</span>
</label>
))}
{teamRoles.map((slug) => {
const role = slug as (typeof ALL_TEAM_ROLES)[number]
return (
<label key={slug} className={styles.roleOption}>
<input
type="radio"
name="team-role"
value={slug}
checked={teamRole === slug}
onChange={() => setTeamRole(slug)}
disabled={submitting}
/>
<span className={styles.roleOptionText}>
<span className={styles.roleOptionLabel}>
{t(TEAM_ROLE_LABEL_KEYS[role])}
</span>
<span className={styles.roleOptionDesc}>
{t(TEAM_ROLE_DESC_KEYS[role])}
</span>
</span>
</label>
)
})}
</div>
</>
) : null}
@@ -10,10 +10,7 @@
}
.filtersTitle {
font-size: 13px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
display: none;
}
.filtersGrid {
@@ -8,14 +8,33 @@
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;
}
.welcomeTitle {
@@ -26,8 +45,14 @@
}
.pageSubtitle {
display: none;
}
.pageSubtitleContent {
display: block;
font-size: 15px;
color: var(--text-secondary);
margin-top: 6px;
}
.welcomeSubtitle {
@@ -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>
)
}