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 { .roleOption {
display: flex; display: flex;
align-items: center; align-items: flex-start;
gap: 10px; gap: 10px;
padding: 10px 12px; padding: 10px 12px;
border-radius: 10px; border-radius: 10px;
@@ -16,17 +16,37 @@
font-family: var(--font-ui); font-family: var(--font-ui);
} }
.roleOption input[type='radio'] {
accent-color: var(--primary);
margin-top: 2px;
flex-shrink: 0;
}
.roleOption:has(input:checked) { .roleOption:has(input:checked) {
border-color: rgba(var(--primary-rgb) / 0.35); border-color: rgba(var(--primary-rgb) / 0.35);
background: rgba(var(--primary-rgb) / 0.08); background: rgba(var(--primary-rgb) / 0.08);
} }
.roleOptionText {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.roleOptionLabel { .roleOptionLabel {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
} }
.roleOptionDesc {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
line-height: 1.45;
}
.roleHint { .roleHint {
margin: 12px 0 8px; margin: 12px 0 8px;
font-size: 12px; font-size: 12px;
@@ -36,12 +56,25 @@
} }
.roleListNested { .roleListNested {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
margin-top: 4px; margin-top: 4px;
margin-inline-start: 12px; margin-inline-start: 12px;
padding-inline-start: 10px; padding-inline-start: 10px;
border-inline-start: 2px solid rgba(var(--primary-rgb) / 0.2); 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 { .roleBadge {
display: inline-block; display: inline-block;
margin-top: 4px; margin-top: 4px;
@@ -23,6 +23,12 @@ const TEAM_ROLE_LABEL_KEYS: Record<(typeof ALL_TEAM_ROLES)[number], BusinessMess
viewer: 'customers.access.role.viewer', 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 { export interface ChangeUserAccessModalProps {
open: boolean open: boolean
user: BusinessCustomerListItem | null user: BusinessCustomerListItem | null
@@ -185,27 +191,33 @@ export function ChangeUserAccessModal({
{access === 'staff' ? ( {access === 'staff' ? (
<> <>
<p className={styles.roleHint}> {!canAssignAdmin ? (
{canAssignAdmin <p className={styles.roleHint}>{t('customers.access.hintBusiness')}</p>
? t('customers.access.hintSuperAdmin') ) : null}
: t('customers.access.hintBusiness')}
</p>
<div className={`${styles.roleList} ${styles.roleListNested}`}> <div className={`${styles.roleList} ${styles.roleListNested}`}>
{teamRoles.map((slug) => ( {teamRoles.map((slug) => {
<label key={slug} className={styles.roleOption}> const role = slug as (typeof ALL_TEAM_ROLES)[number]
<input return (
type="radio" <label key={slug} className={styles.roleOption}>
name="team-role" <input
value={slug} type="radio"
checked={teamRole === slug} name="team-role"
onChange={() => setTeamRole(slug)} value={slug}
disabled={submitting} checked={teamRole === slug}
/> onChange={() => setTeamRole(slug)}
<span className={styles.roleOptionLabel}> disabled={submitting}
{t(TEAM_ROLE_LABEL_KEYS[slug as (typeof ALL_TEAM_ROLES)[number]])} />
</span> <span className={styles.roleOptionText}>
</label> <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> </div>
</> </>
) : null} ) : null}
@@ -10,10 +10,7 @@
} }
.filtersTitle { .filtersTitle {
font-size: 13px; display: none;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
} }
.filtersGrid { .filtersGrid {
@@ -8,14 +8,33 @@
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 24px; gap: 24px;
margin-bottom: 36px; margin-bottom: 12px;
} }
.pageTitle { .pageTitle {
font-size: 28px; font-size: 28px;
font-weight: 700; font-weight: 700;
color: var(--text-primary); 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 { .welcomeTitle {
@@ -26,8 +45,14 @@
} }
.pageSubtitle { .pageSubtitle {
display: none;
}
.pageSubtitleContent {
display: block;
font-size: 15px; font-size: 15px;
color: var(--text-secondary); color: var(--text-secondary);
margin-top: 6px;
} }
.welcomeSubtitle { .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>
)
}
+30 -18
View File
@@ -27,7 +27,7 @@ const en = {
'nav.store.shipping': 'Shipping Fees', 'nav.store.shipping': 'Shipping Fees',
'nav.store.cards': 'Shopping Cards', 'nav.store.cards': 'Shopping Cards',
'nav.store.settings': 'Settings', 'nav.store.settings': 'Settings',
'nav.customers': 'Customers', 'nav.customers': 'Users',
'nav.customerProducts': 'Customer products', 'nav.customerProducts': 'Customer products',
'nav.settings': 'Settings', 'nav.settings': 'Settings',
'nav.blog': 'Blog', 'nav.blog': 'Blog',
@@ -158,7 +158,7 @@ const en = {
'home.card.store.desc': 'Manage your store settings, pages and themes.', 'home.card.store.desc': 'Manage your store settings, pages and themes.',
'home.card.store.link': 'View store', 'home.card.store.link': 'View store',
'home.card.store.count': 'on sale', 'home.card.store.count': 'on sale',
'home.card.customers.title': 'Customers', 'home.card.customers.title': 'Users',
'home.card.customers.desc': 'View and manage your customers and their activity.', 'home.card.customers.desc': 'View and manage your customers and their activity.',
'home.card.customers.link': 'View customers', 'home.card.customers.link': 'View customers',
'home.card.customers.count': 'people', 'home.card.customers.count': 'people',
@@ -185,7 +185,7 @@ const en = {
'home.chart.orders.error': 'Unable to load order activity.', 'home.chart.orders.error': 'Unable to load order activity.',
'home.chart.orders.bar': '{day}: {count} orders', 'home.chart.orders.bar': '{day}: {count} orders',
'home.chart.orders.cartBar': '{day}: {count} added to basket', 'home.chart.orders.cartBar': '{day}: {count} added to basket',
'home.chart.customers.title': 'Customers', 'home.chart.customers.title': 'Users',
'home.chart.customers.subtitle': 'Registrations and active users in the last 30 days', 'home.chart.customers.subtitle': 'Registrations and active users in the last 30 days',
'home.chart.customers.legend': 'Registered ({count})', 'home.chart.customers.legend': 'Registered ({count})',
'home.chart.customers.activeLegend': 'Active ({count})', 'home.chart.customers.activeLegend': 'Active ({count})',
@@ -766,7 +766,7 @@ const en = {
'storeSettings.error.saveSteps': 'Unable to save order process steps.', 'storeSettings.error.saveSteps': 'Unable to save order process steps.',
'storeSettings.stepsSaved': 'Order process steps saved.', 'storeSettings.stepsSaved': 'Order process steps saved.',
'customers.title': 'Customers', 'customers.title': 'Users',
'customers.subtitle': 'customers.subtitle':
'View customers who have registered or ordered from your business.', 'View customers who have registered or ordered from your business.',
'customers.filters': 'Filters', 'customers.filters': 'Filters',
@@ -785,6 +785,7 @@ const en = {
'customers.loading': 'Loading customers...', 'customers.loading': 'Loading customers...',
'customers.pageMeta': 'Page {page} / {totalPages} · {pageSize} per page · {total} total', 'customers.pageMeta': 'Page {page} / {totalPages} · {pageSize} per page · {total} total',
'customers.col.name': 'Name', 'customers.col.name': 'Name',
'customers.col.role': 'Role',
'customers.col.cell': 'Cell number', 'customers.col.cell': 'Cell number',
'customers.col.email': 'Email', 'customers.col.email': 'Email',
'customers.col.orders': 'Orders', 'customers.col.orders': 'Orders',
@@ -839,11 +840,16 @@ const en = {
'customers.access.manager': 'Manager', 'customers.access.manager': 'Manager',
'customers.access.role.admin': 'Admin', 'customers.access.role.admin': 'Admin',
'customers.access.role.editor': 'Editor', 'customers.access.role.editor': 'Editor',
'customers.access.role.viewer': 'Viewer', 'customers.access.role.viewer': 'Observer',
'customers.access.role.admin.desc':
'Can view and edit content, and also assign editors and observers.',
'customers.access.role.editor.desc': 'Can view content and edit it.',
'customers.access.role.viewer.desc':
'Can only view content. Best for order management and content quality review.',
'customers.access.hintSuperAdmin': 'customers.access.hintSuperAdmin':
'Admin can manage other managers (except other admins). Editor and viewer manage content only.', 'Admin can manage other managers (except other admins). Editor and viewer manage content only.',
'customers.access.hintBusiness': 'customers.access.hintBusiness':
'Managers can be Editor or Viewer. Only a super admin can assign Admin.', 'Managers can be Editor or Observer. Only a super admin can assign Admin.',
'customers.access.cancel': 'Cancel', 'customers.access.cancel': 'Cancel',
'customers.access.save': 'Save', 'customers.access.save': 'Save',
'customers.access.error': 'Unable to update access.', 'customers.access.error': 'Unable to update access.',
@@ -853,7 +859,7 @@ const en = {
'customers.access.badge.owner': 'Owner', 'customers.access.badge.owner': 'Owner',
'customers.access.badge.admin': 'Admin', 'customers.access.badge.admin': 'Admin',
'customers.access.badge.editor': 'Editor', 'customers.access.badge.editor': 'Editor',
'customers.access.badge.viewer': 'Viewer', 'customers.access.badge.viewer': 'Observer',
'customers.access.badge.manager': 'Manager', 'customers.access.badge.manager': 'Manager',
'customers.toast.accessUpdated': 'Access updated to {role}.', 'customers.toast.accessUpdated': 'Access updated to {role}.',
@@ -1261,7 +1267,7 @@ const en = {
'title.storeItems': 'My Store Items', 'title.storeItems': 'My Store Items',
'title.orders': 'My Orders', 'title.orders': 'My Orders',
'title.shoppingCards': 'Shopping Cards', 'title.shoppingCards': 'Shopping Cards',
'title.customers': 'Customers', 'title.customers': 'Users',
'title.customerProducts': 'Customer products', 'title.customerProducts': 'Customer products',
'title.blog': 'Blog', 'title.blog': 'Blog',
'title.myBlogs': 'My Blogs', 'title.myBlogs': 'My Blogs',
@@ -1396,7 +1402,7 @@ const fa: Record<MessageKey, string> = {
'nav.store.shipping': 'هزینه ارسال', 'nav.store.shipping': 'هزینه ارسال',
'nav.store.cards': 'کارت‌های خرید', 'nav.store.cards': 'کارت‌های خرید',
'nav.store.settings': 'تنظیمات', 'nav.store.settings': 'تنظیمات',
'nav.customers': 'مشتریان', 'nav.customers': 'کاربران',
'nav.customerProducts': 'محصولات مشتریان', 'nav.customerProducts': 'محصولات مشتریان',
'nav.settings': 'تنظیمات', 'nav.settings': 'تنظیمات',
'nav.blog': 'بلاگ', 'nav.blog': 'بلاگ',
@@ -1527,7 +1533,7 @@ const fa: Record<MessageKey, string> = {
'home.card.store.desc': 'تنظیمات، صفحات و ظاهر فروشگاه را مدیریت کنید.', 'home.card.store.desc': 'تنظیمات، صفحات و ظاهر فروشگاه را مدیریت کنید.',
'home.card.store.link': 'مشاهده فروشگاه', 'home.card.store.link': 'مشاهده فروشگاه',
'home.card.store.count': 'محصول فروش', 'home.card.store.count': 'محصول فروش',
'home.card.customers.title': 'مشتریان', 'home.card.customers.title': 'کاربران',
'home.card.customers.desc': 'مشتریان و فعالیت آن‌ها را ببینید و مدیریت کنید.', 'home.card.customers.desc': 'مشتریان و فعالیت آن‌ها را ببینید و مدیریت کنید.',
'home.card.customers.link': 'مشاهده مشتریان', 'home.card.customers.link': 'مشاهده مشتریان',
'home.card.customers.count': 'نفر', 'home.card.customers.count': 'نفر',
@@ -1554,7 +1560,7 @@ const fa: Record<MessageKey, string> = {
'home.chart.orders.error': 'بارگذاری فعالیت سفارش‌ها ممکن نشد.', 'home.chart.orders.error': 'بارگذاری فعالیت سفارش‌ها ممکن نشد.',
'home.chart.orders.bar': '{day}: {count} سفارش', 'home.chart.orders.bar': '{day}: {count} سفارش',
'home.chart.orders.cartBar': '{day}: {count} افزودن به سبد', 'home.chart.orders.cartBar': '{day}: {count} افزودن به سبد',
'home.chart.customers.title': 'مشتریان', 'home.chart.customers.title': 'کاربران',
'home.chart.customers.subtitle': 'ثبت‌نام و کاربران فعال در ۳۰ روز گذشته', 'home.chart.customers.subtitle': 'ثبت‌نام و کاربران فعال در ۳۰ روز گذشته',
'home.chart.customers.legend': 'ثبت‌نام ({count})', 'home.chart.customers.legend': 'ثبت‌نام ({count})',
'home.chart.customers.activeLegend': 'فعال ({count})', 'home.chart.customers.activeLegend': 'فعال ({count})',
@@ -2133,7 +2139,7 @@ const fa: Record<MessageKey, string> = {
'storeSettings.error.saveSteps': 'ذخیره مراحل فرآیند سفارش ممکن نشد.', 'storeSettings.error.saveSteps': 'ذخیره مراحل فرآیند سفارش ممکن نشد.',
'storeSettings.stepsSaved': 'مراحل فرآیند سفارش ذخیره شد.', 'storeSettings.stepsSaved': 'مراحل فرآیند سفارش ذخیره شد.',
'customers.title': 'مشتریان', 'customers.title': 'کاربران',
'customers.subtitle': 'customers.subtitle':
'مشتریانی را ببینید که در کسب‌وکار شما ثبت‌نام کرده یا سفارش داده‌اند.', 'مشتریانی را ببینید که در کسب‌وکار شما ثبت‌نام کرده یا سفارش داده‌اند.',
'customers.filters': 'فیلترها', 'customers.filters': 'فیلترها',
@@ -2152,6 +2158,7 @@ const fa: Record<MessageKey, string> = {
'customers.loading': 'در حال بارگذاری مشتریان...', 'customers.loading': 'در حال بارگذاری مشتریان...',
'customers.pageMeta': 'صفحه {page} / {totalPages} · {pageSize} در هر صفحه · {total} کل', 'customers.pageMeta': 'صفحه {page} / {totalPages} · {pageSize} در هر صفحه · {total} کل',
'customers.col.name': 'نام', 'customers.col.name': 'نام',
'customers.col.role': 'نقش',
'customers.col.cell': 'شماره موبایل', 'customers.col.cell': 'شماره موبایل',
'customers.col.email': 'ایمیل', 'customers.col.email': 'ایمیل',
'customers.col.orders': 'سفارش‌ها', 'customers.col.orders': 'سفارش‌ها',
@@ -2206,21 +2213,26 @@ const fa: Record<MessageKey, string> = {
'customers.access.manager': 'مدیر', 'customers.access.manager': 'مدیر',
'customers.access.role.admin': 'ادمین', 'customers.access.role.admin': 'ادمین',
'customers.access.role.editor': 'ویرایشگر', 'customers.access.role.editor': 'ویرایشگر',
'customers.access.role.viewer': 'بیننده', 'customers.access.role.viewer': 'ناظر',
'customers.access.role.admin.desc':
'ادمین می‌تواند ببیند، ویرایش کند و همچنین دیگر ویرایشگران و ناظرین را مشخص کند.',
'customers.access.role.editor.desc': 'ویرایشگر می‌تواند محتواها را ببیند و آن‌ها را ویرایش کند.',
'customers.access.role.viewer.desc':
'ناظر می‌تواند فقط محتوا را ببیند. ناظر بیشتر برای مدیریت سفارشات و بررسی کیفی محتواها کاربرد دارد.',
'customers.access.hintSuperAdmin': 'customers.access.hintSuperAdmin':
'ادمین می‌تواند مدیران دیگر را مدیریت کند (به‌جز ادمین‌های دیگر). ویرایشگر و بیننده فقط محتوا را مدیریت می‌کنند.', 'ادمین می‌تواند مدیران دیگر را مدیریت کند (به‌جز ادمین‌های دیگر). ویرایشگر و ناظر محتوا را مدیریت می‌کنند.',
'customers.access.hintBusiness': 'customers.access.hintBusiness':
'مدیران می‌توانند ویرایشگر یا بیننده باشند. فقط سوپرادمین می‌تواند ادمین تعیین کند.', 'مدیران می‌توانند ویرایشگر یا ناظر باشند. فقط سوپرادمین می‌تواند ادمین تعیین کند.',
'customers.access.cancel': 'انصراف', 'customers.access.cancel': 'انصراف',
'customers.access.save': 'ذخیره', 'customers.access.save': 'ذخیره',
'customers.access.error': 'به‌روزرسانی دسترسی ممکن نشد.', 'customers.access.error': 'به‌روزرسانی دسترسی ممکن نشد.',
'customers.access.change': 'تغییر دسترسی', 'customers.access.change': 'تغییر دسترسی',
'customers.access.ownerLocked': 'نقش صاحب کسب‌وکار قابل تغییر نیست', 'customers.access.ownerLocked': 'نقش صاحب کسب‌وکار قابل تغییر نیست',
'customers.access.adminLocked': 'فقط سوپرادمین می‌تواند ادمین را تغییر دهد', 'customers.access.adminLocked': 'فقط سوپرادمین می‌تواند ادمین را تغییر دهد',
'customers.access.badge.owner': 'صاحب', 'customers.access.badge.owner': 'مالک کسب و کار',
'customers.access.badge.admin': 'ادمین', 'customers.access.badge.admin': 'ادمین',
'customers.access.badge.editor': 'ویرایشگر', 'customers.access.badge.editor': 'ویرایشگر',
'customers.access.badge.viewer': 'بیننده', 'customers.access.badge.viewer': 'ناظر',
'customers.access.badge.manager': 'مدیر', 'customers.access.badge.manager': 'مدیر',
'customers.toast.accessUpdated': 'دسترسی به {role} به‌روزرسانی شد.', 'customers.toast.accessUpdated': 'دسترسی به {role} به‌روزرسانی شد.',
@@ -2627,7 +2639,7 @@ const fa: Record<MessageKey, string> = {
'title.storeItems': 'اقلام فروشگاه', 'title.storeItems': 'اقلام فروشگاه',
'title.orders': 'سفارش‌های من', 'title.orders': 'سفارش‌های من',
'title.shoppingCards': 'کارت‌های خرید', 'title.shoppingCards': 'کارت‌های خرید',
'title.customers': 'مشتریان', 'title.customers': 'کاربران',
'title.customerProducts': 'محصولات مشتریان', 'title.customerProducts': 'محصولات مشتریان',
'title.blog': 'بلاگ', 'title.blog': 'بلاگ',
'title.myBlogs': 'بلاگ‌های من', 'title.myBlogs': 'بلاگ‌های من',
@@ -3,6 +3,7 @@ import { Plus } from 'lucide-react'
import { CategoryModal } from '../components/CategoryModal' import { CategoryModal } from '../components/CategoryModal'
import { BlogCategoryTree } from '../components/BlogCategoryTree' import { BlogCategoryTree } from '../components/BlogCategoryTree'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import type { Category, CategoryFormData } from '../types/category' import type { Category, CategoryFormData } from '../types/category'
import { ApiError } from '../lib/api' import { ApiError } from '../lib/api'
@@ -159,7 +160,7 @@ export function BlogCategoriesPage() {
/> />
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('blog.categories.title')}</h2> <PageTitle en="BLOG CATEGORIES">{t('blog.categories.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('blog.categories.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('blog.categories.subtitle')}</p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -117,7 +117,7 @@ export function BlogDetailsPage() {
</h2> </h2>
{blog.abstract ? ( {blog.abstract ? (
<p <p
className={[pageStyles.pageSubtitle, abstractLocale.className] className={[pageStyles.pageSubtitleContent, abstractLocale.className]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
lang={abstractLocale.lang} lang={abstractLocale.lang}
+2 -1
View File
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { FileText, Plus } from 'lucide-react' import { FileText, Plus } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { BlogCard } from '../components/BlogCard' import { BlogCard } from '../components/BlogCard'
import { BlogCommentsModal } from '../components/BlogCommentsModal' import { BlogCommentsModal } from '../components/BlogCommentsModal'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
@@ -164,7 +165,7 @@ export function BlogListPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.myBlogs')}</h2> <PageTitle en="BLOGS">{t('title.myBlogs')}</PageTitle>
<p className={pageStyles.pageSubtitle}> <p className={pageStyles.pageSubtitle}>
{t('blog.list.subtitle', { count: totalBlogs })} {t('blog.list.subtitle', { count: totalBlogs })}
</p> </p>
+2 -1
View File
@@ -1,6 +1,7 @@
import { FileText, PlusCircle, FolderTree, Settings } from 'lucide-react' import { FileText, PlusCircle, FolderTree, Settings } from 'lucide-react'
import { SectionCard } from '../components/SectionCard' import { SectionCard } from '../components/SectionCard'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import type { BusinessMessageKey } from '../i18n/messages' import type { BusinessMessageKey } from '../i18n/messages'
import styles from '../components/PageContent.module.css' import styles from '../components/PageContent.module.css'
@@ -55,7 +56,7 @@ export function BlogPage() {
/> />
<div className={styles.pageHeader}> <div className={styles.pageHeader}>
<div> <div>
<h2 className={styles.pageTitle}>{t('title.blog')}</h2> <PageTitle en="BLOG">{t('title.blog')}</PageTitle>
<p className={styles.pageSubtitle}>{t('blog.page.subtitle')}</p> <p className={styles.pageSubtitle}>{t('blog.page.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { Switch } from '../components/Switch' import { Switch } from '../components/Switch'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -83,7 +84,7 @@ export function BlogSettingsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('blog.settings.title')}</h2> <PageTitle en="BLOG SETTINGS">{t('blog.settings.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('blog.settings.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('blog.settings.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -4,6 +4,7 @@ import { useToast } from '../context/ToastContext'
import { BrandModal } from '../components/BrandModal' import { BrandModal } from '../components/BrandModal'
import { BrandRow } from '../components/BrandRow' import { BrandRow } from '../components/BrandRow'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import type { Brand, BrandFormData } from '../types/brand' import type { Brand, BrandFormData } from '../types/brand'
import { ApiError } from '../lib/api' import { ApiError } from '../lib/api'
@@ -142,7 +143,7 @@ export function BrandsPage() {
/> />
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.brands')}</h2> <PageTitle en="BRANDS">{t('title.brands')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('brands.page.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('brands.page.subtitle')}</p>
</div> </div>
</div> </div>
@@ -10,6 +10,7 @@ import {
type AddressListItem, type AddressListItem,
} from '@meshkee/dashboard-ui' } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ImageCropper } from '../components/ImageCropper' import { ImageCropper } from '../components/ImageCropper'
import { MultiSelectDropdown } from '../components/MultiSelectDropdown' import { MultiSelectDropdown } from '../components/MultiSelectDropdown'
import { RichTextEditor } from '../components/RichTextEditor' import { RichTextEditor } from '../components/RichTextEditor'
@@ -340,7 +341,7 @@ export function BusinessProfilePage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.businessProfile')}</h2> <PageTitle en="BUSINESS PROFILE">{t('title.businessProfile')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('bizProfile.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('bizProfile.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -5,6 +5,7 @@ import { CategoryModal } from '../components/CategoryModal'
import { CategoryAiPromptModal } from '../components/CategoryAiPromptModal' import { CategoryAiPromptModal } from '../components/CategoryAiPromptModal'
import { CategoryTree } from '../components/CategoryTree' import { CategoryTree } from '../components/CategoryTree'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { VariationsModal } from '../components/VariationsModal' import { VariationsModal } from '../components/VariationsModal'
import { TechnicalFormModal } from '../components/TechnicalFormModal' import { TechnicalFormModal } from '../components/TechnicalFormModal'
@@ -427,7 +428,7 @@ export function CategoriesPage() {
/> />
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.categories')}</h2> <PageTitle en="CATEGORIES">{t('title.categories')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('categories.page.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('categories.page.subtitle')}</p>
</div> </div>
</div> </div>
@@ -3,6 +3,7 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
import { ImageOff, MapPin, Pencil, User } from 'lucide-react' import { ImageOff, MapPin, Pencil, User } from 'lucide-react'
import { useLocale } from '@meshkee/dashboard-ui' import { useLocale } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { UserProductStatusModal } from '../components/UserProductStatusModal' import { UserProductStatusModal } from '../components/UserProductStatusModal'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -197,8 +198,8 @@ export function CustomerProductDetailsPage() {
<div className={styles.headerRow}> <div className={styles.headerRow}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('customerProducts.detailsTitle')}</h2> <PageTitle en="CUSTOMER PRODUCT">{t('customerProducts.detailsTitle')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('customerProducts.detailsSubtitle')}</p> <p className={pageStyles.pageSubtitleContent}>{t('customerProducts.detailsSubtitle')}</p>
</div> </div>
<div className={styles.actions}> <div className={styles.actions}>
<button <button
@@ -3,6 +3,7 @@ import { Link, useNavigate } from 'react-router-dom'
import { Package, Plus } from 'lucide-react' import { Package, Plus } from 'lucide-react'
import { Pagination } from '@meshkee/dashboard-ui' import { Pagination } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { UserProductCard } from '../components/UserProductCard' import { UserProductCard } from '../components/UserProductCard'
import { UserProductStatusModal } from '../components/UserProductStatusModal' import { UserProductStatusModal } from '../components/UserProductStatusModal'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
@@ -174,7 +175,7 @@ export function CustomerProductsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('customerProducts.title')}</h2> <PageTitle en="CUSTOMER PRODUCTS">{t('customerProducts.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}> <p className={pageStyles.pageSubtitle}>
{t('customerProducts.subtitle', { count: total })} {t('customerProducts.subtitle', { count: total })}
</p> </p>
@@ -1,11 +1,3 @@
.pageHeaderCompact {
margin-bottom: 12px;
}
.pageHeaderCompact :global(h2) {
margin-bottom: 0;
}
.tablePanel { .tablePanel {
margin-top: 12px; margin-top: 12px;
padding: 0; padding: 0;
@@ -26,7 +18,7 @@
padding: 12px 16px; padding: 12px 16px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: flex-end;
border-bottom: 1px solid rgba(148, 163, 184, 0.2); border-bottom: 1px solid rgba(148, 163, 184, 0.2);
} }
@@ -49,23 +41,27 @@
} }
.colName { .colName {
width: 22%; width: 18%;
}
.colRole {
width: 16%;
} }
.colCell { .colCell {
width: 14%; width: 13%;
} }
.colOrders { .colOrders {
width: 12%; width: 10%;
} }
.colTransactions { .colTransactions {
width: 14%; width: 12%;
} }
.colDate { .colDate {
width: 12%; width: 11%;
} }
.colActions { .colActions {
+13 -13
View File
@@ -6,6 +6,7 @@ import { Breadcrumbs } from '../components/Breadcrumbs'
import { ChangeUserAccessModal } from '../components/ChangeUserAccessModal' import { ChangeUserAccessModal } from '../components/ChangeUserAccessModal'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { EditCustomerModal } from '../components/EditCustomerModal' import { EditCustomerModal } from '../components/EditCustomerModal'
import { PageTitle } from '../components/PageTitle'
import { Pagination } from '../components/Pagination' import { Pagination } from '../components/Pagination'
import { ToggleSwitch } from '../components/ToggleSwitch' import { ToggleSwitch } from '../components/ToggleSwitch'
import { Tooltip } from '../components/Tooltip' import { Tooltip } from '../components/Tooltip'
@@ -32,7 +33,7 @@ import accessStyles from '../components/ChangeUserAccessModal.module.css'
import styles from './CustomersPage.module.css' import styles from './CustomersPage.module.css'
const PAGE_SIZE = 24 const PAGE_SIZE = 24
const COLUMN_COUNT = 6 const COLUMN_COUNT = 7
function formatDate(value: string, locale: 'en' | 'fa') { function formatDate(value: string, locale: 'en' | 'fa') {
const d = new Date(value) const d = new Date(value)
@@ -53,13 +54,13 @@ function formatTransactionTotal(total: number | null | undefined) {
return formatIrtPrice(total) return formatIrtPrice(total)
} }
function accessBadgeKey(user: BusinessCustomerListItem): BusinessMessageKey | null { function accessBadgeKey(user: BusinessCustomerListItem): BusinessMessageKey {
if (user.isBusinessOwner) return 'customers.access.badge.owner' if (user.isBusinessOwner) return 'customers.access.badge.owner'
if (user.teamRole === 'admin') return 'customers.access.badge.admin' if (user.teamRole === 'admin') return 'customers.access.badge.admin'
if (user.teamRole === 'editor') return 'customers.access.badge.editor' if (user.teamRole === 'editor') return 'customers.access.badge.editor'
if (user.teamRole === 'viewer') return 'customers.access.badge.viewer' if (user.teamRole === 'viewer') return 'customers.access.badge.viewer'
if (user.businessMemberId) return 'customers.access.badge.manager' if (user.businessMemberId) return 'customers.access.badge.manager'
return null return 'customers.access.customer'
} }
function teamRoleMessageKey(teamRole: string): BusinessMessageKey { function teamRoleMessageKey(teamRole: string): BusinessMessageKey {
@@ -310,15 +311,13 @@ export function CustomersPage() {
<main className={pageStyles.content}> <main className={pageStyles.content}>
<Breadcrumbs <Breadcrumbs
items={[ items={[
{ label: 'Home', href: '/' }, { label: t('nav.home'), href: '/' },
{ label: 'Customers' }, { label: t('customers.title') },
]} ]}
/> />
<div className={`${pageStyles.pageHeader} ${styles.pageHeaderCompact}`}> <div className={pageStyles.pageHeader}>
<div> <PageTitle en="USERS">{t('customers.title')}</PageTitle>
<h2 className={pageStyles.pageTitle}>{t('customers.title')}</h2>
</div>
</div> </div>
<div className={filterStyles.filtersPanel}> <div className={filterStyles.filtersPanel}>
@@ -384,7 +383,6 @@ export function CustomersPage() {
<div className={styles.tablePanel}> <div className={styles.tablePanel}>
<div className={styles.tableWrap}> <div className={styles.tableWrap}>
<div className={styles.tableHeader}> <div className={styles.tableHeader}>
<div className={styles.tableHeaderTitle}>{t('customers.listTitle')}</div>
<div className={styles.meta}> <div className={styles.meta}>
{data ? ( {data ? (
data.total > 0 ? ( data.total > 0 ? (
@@ -410,6 +408,7 @@ export function CustomersPage() {
> >
<colgroup> <colgroup>
<col className={styles.colName} /> <col className={styles.colName} />
<col className={styles.colRole} />
<col className={styles.colCell} /> <col className={styles.colCell} />
<col className={styles.colOrders} /> <col className={styles.colOrders} />
<col className={styles.colTransactions} /> <col className={styles.colTransactions} />
@@ -419,6 +418,7 @@ export function CustomersPage() {
<thead> <thead>
<tr> <tr>
<th className={styles.th}>{t('customers.col.name')}</th> <th className={styles.th}>{t('customers.col.name')}</th>
<th className={styles.th}>{t('customers.col.role')}</th>
<th className={styles.th}>{t('customers.col.cell')}</th> <th className={styles.th}>{t('customers.col.cell')}</th>
<th className={styles.th}>{t('customers.col.orders')}</th> <th className={styles.th}>{t('customers.col.orders')}</th>
<th className={styles.th}>{t('customers.col.transactions')}</th> <th className={styles.th}>{t('customers.col.transactions')}</th>
@@ -468,13 +468,13 @@ export function CustomersPage() {
> >
{name} {name}
</div> </div>
{badgeKey ? (
<span className={accessStyles.roleBadge}>{t(badgeKey)}</span>
) : null}
{!customer.isEnabled && ( {!customer.isEnabled && (
<div className={styles.statusDisabled}>{t('customers.disabled')}</div> <div className={styles.statusDisabled}>{t('customers.disabled')}</div>
)} )}
</td> </td>
<td className={styles.td}>
<span className={accessStyles.roleBadge}>{t(badgeKey)}</span>
</td>
<td className={styles.td}>{formatCellForDisplay(customer.cellNumber)}</td> <td className={styles.td}>{formatCellForDisplay(customer.cellNumber)}</td>
<td className={styles.td}> <td className={styles.td}>
{customer.orderCount == null || customer.orderCount === 0 ? ( {customer.orderCount == null || customer.orderCount === 0 ? (
+2 -1
View File
@@ -9,6 +9,7 @@ import { ProductVariantsModal } from '../components/ProductVariantsModal'
import { ProductTechnicalInfoModal } from '../components/ProductTechnicalInfoModal' import { ProductTechnicalInfoModal } from '../components/ProductTechnicalInfoModal'
import { ProductCommentsModal } from '../components/ProductCommentsModal' import { ProductCommentsModal } from '../components/ProductCommentsModal'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import { ApiError } from '../lib/api' import { ApiError } from '../lib/api'
import { import {
@@ -201,7 +202,7 @@ export function MyProductsPage() {
/> />
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.myProducts')}</h2> <PageTitle en="PRODUCTS">{t('title.myProducts')}</PageTitle>
<p className={pageStyles.pageSubtitle}> <p className={pageStyles.pageSubtitle}>
{t('products.list.subtitle', { count: totalProducts })} {t('products.list.subtitle', { count: totalProducts })}
</p> </p>
+2 -1
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { CreditCard, Eye, RotateCcw, Search, Trash2 } from 'lucide-react' import { CreditCard, Eye, RotateCcw, Search, Trash2 } from 'lucide-react'
import { useLocale } from '@meshkee/dashboard-ui' import { useLocale } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { OrderItemsModal } from '../components/OrderItemsModal' import { OrderItemsModal } from '../components/OrderItemsModal'
import { OrderStepModal } from '../components/OrderStepModal' import { OrderStepModal } from '../components/OrderStepModal'
@@ -314,7 +315,7 @@ export function OrdersPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('orders.title')}</h2> <PageTitle en="ORDERS">{t('orders.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('orders.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('orders.subtitle')}</p>
</div> </div>
</div> </div>
@@ -3,6 +3,7 @@ import { Plus } from 'lucide-react'
import { CategoryModal } from '../components/CategoryModal' import { CategoryModal } from '../components/CategoryModal'
import { BlogCategoryTree } from '../components/BlogCategoryTree' import { BlogCategoryTree } from '../components/BlogCategoryTree'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import type { Category, CategoryFormData } from '../types/category' import type { Category, CategoryFormData } from '../types/category'
import { ApiError } from '../lib/api' import { ApiError } from '../lib/api'
@@ -159,7 +160,7 @@ export function PortfolioCategoriesPage() {
/> />
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('portfolio.categories.title')}</h2> <PageTitle en="PORTFOLIO CATEGORIES">{t('portfolio.categories.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('portfolio.categories.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('portfolio.categories.subtitle')}</p>
</div> </div>
</div> </div>
@@ -101,7 +101,7 @@ export function PortfolioDetailsPage() {
<div> <div>
<h2 className={pageStyles.pageTitle}>{portfolio.title}</h2> <h2 className={pageStyles.pageTitle}>{portfolio.title}</h2>
{portfolio.abstract && ( {portfolio.abstract && (
<p className={pageStyles.pageSubtitle}>{portfolio.abstract}</p> <p className={pageStyles.pageSubtitleContent}>{portfolio.abstract}</p>
)} )}
</div> </div>
</div> </div>
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { PortfolioCard } from '../components/PortfolioCard' import { PortfolioCard } from '../components/PortfolioCard'
import { PortfolioCommentsModal } from '../components/PortfolioCommentsModal' import { PortfolioCommentsModal } from '../components/PortfolioCommentsModal'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
@@ -172,7 +173,7 @@ export function PortfolioListPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.myPortfolios')}</h2> <PageTitle en="PORTFOLIOS">{t('title.myPortfolios')}</PageTitle>
<p className={pageStyles.pageSubtitle}> <p className={pageStyles.pageSubtitle}>
{t('portfolio.list.subtitle', { count: totalPortfolios })} {t('portfolio.list.subtitle', { count: totalPortfolios })}
</p> </p>
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { Switch } from '../components/Switch' import { Switch } from '../components/Switch'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -83,7 +84,7 @@ export function PortfolioSettingsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('portfolio.settings.title')}</h2> <PageTitle en="PORTFOLIO SETTINGS">{t('portfolio.settings.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('portfolio.settings.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('portfolio.settings.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,6 +1,7 @@
import { Briefcase, PlusCircle, FolderTree, Settings } from 'lucide-react' import { Briefcase, PlusCircle, FolderTree, Settings } from 'lucide-react'
import { SectionCard } from '../components/SectionCard' import { SectionCard } from '../components/SectionCard'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import type { BusinessMessageKey } from '../i18n/messages' import type { BusinessMessageKey } from '../i18n/messages'
import styles from '../components/PageContent.module.css' import styles from '../components/PageContent.module.css'
@@ -55,7 +56,7 @@ export function PortfoliosPage() {
/> />
<div className={styles.pageHeader}> <div className={styles.pageHeader}>
<div> <div>
<h2 className={styles.pageTitle}>{t('title.portfolios')}</h2> <PageTitle en="PORTFOLIOS">{t('title.portfolios')}</PageTitle>
<p className={styles.pageSubtitle}>{t('portfolio.page.subtitle')}</p> <p className={styles.pageSubtitle}>{t('portfolio.page.subtitle')}</p>
</div> </div>
</div> </div>
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { Switch } from '../components/Switch' import { Switch } from '../components/Switch'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -104,7 +105,7 @@ export function ProductSettingsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('productSettings.title')}</h2> <PageTitle en="PRODUCT SETTINGS">{t('productSettings.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('productSettings.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('productSettings.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -2,6 +2,7 @@ import { FolderTree, PlusCircle, Package, Settings, Tag } from 'lucide-react'
import { SectionCard } from '../components/SectionCard' import { SectionCard } from '../components/SectionCard'
import { ProductActivityChart } from '../components/ProductActivityChart' import { ProductActivityChart } from '../components/ProductActivityChart'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import type { BusinessMessageKey } from '../i18n/messages' import type { BusinessMessageKey } from '../i18n/messages'
import styles from '../components/PageContent.module.css' import styles from '../components/PageContent.module.css'
@@ -57,7 +58,7 @@ export function ProductsPage() {
/> />
<div className={styles.pageHeader}> <div className={styles.pageHeader}>
<div> <div>
<h2 className={styles.pageTitle}>{t('title.products')}</h2> <PageTitle en="PRODUCTS">{t('title.products')}</PageTitle>
<p className={styles.pageSubtitle}>{t('products.overview.subtitle')}</p> <p className={styles.pageSubtitle}>{t('products.overview.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -10,6 +10,7 @@ import {
type AddressListItem, type AddressListItem,
} from '@meshkee/dashboard-ui' } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import pageStyles from '../components/PageContent.module.css' import pageStyles from '../components/PageContent.module.css'
import { useAuth } from '../context/AuthContext' import { useAuth } from '../context/AuthContext'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
@@ -289,7 +290,7 @@ export function ProfilePage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('profile.title')}</h2> <PageTitle en="PROFILE">{t('profile.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('profile.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('profile.subtitle')}</p>
</div> </div>
</div> </div>
@@ -3,6 +3,7 @@ import { Play, RotateCcw, Search, Trash2 } from 'lucide-react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { useLocale } from '@meshkee/dashboard-ui' import { useLocale } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { Pagination } from '../components/Pagination' import { Pagination } from '../components/Pagination'
import { Tooltip } from '../components/Tooltip' import { Tooltip } from '../components/Tooltip'
@@ -202,7 +203,7 @@ export function ShoppingCardsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('shoppingCards.title')}</h2> <PageTitle en="SHOPPING CARDS">{t('shoppingCards.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('shoppingCards.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('shoppingCards.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { useSearchParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
import { Plus, RotateCcw, Search } from 'lucide-react' import { Plus, RotateCcw, Search } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { CreateStoreItemsModal } from '../components/CreateStoreItemsModal' import { CreateStoreItemsModal } from '../components/CreateStoreItemsModal'
import { EditStoreItemsModal } from '../components/EditStoreItemsModal' import { EditStoreItemsModal } from '../components/EditStoreItemsModal'
@@ -282,7 +283,7 @@ function StoreItemsPageContent() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.storeItems')}</h2> <PageTitle en="STORE ITEMS">{t('title.storeItems')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('storeItems.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('storeItems.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -7,6 +7,7 @@ import {
} from 'lucide-react' } from 'lucide-react'
import { SectionCard } from '../components/SectionCard' import { SectionCard } from '../components/SectionCard'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import type { BusinessMessageKey } from '../i18n/messages' import type { BusinessMessageKey } from '../i18n/messages'
import styles from '../components/PageContent.module.css' import styles from '../components/PageContent.module.css'
@@ -62,7 +63,7 @@ export function StorePage() {
/> />
<div className={styles.pageHeader}> <div className={styles.pageHeader}>
<div> <div>
<h2 className={styles.pageTitle}>{t('title.store')}</h2> <PageTitle en="STORE">{t('title.store')}</PageTitle>
<p className={styles.pageSubtitle}>{t('store.page.subtitle')}</p> <p className={styles.pageSubtitle}>{t('store.page.subtitle')}</p>
</div> </div>
</div> </div>
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react' import { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { StepColorPicker } from '../components/StepColorPicker' import { StepColorPicker } from '../components/StepColorPicker'
import { Switch } from '../components/Switch' import { Switch } from '../components/Switch'
import { Tooltip } from '../components/Tooltip' import { Tooltip } from '../components/Tooltip'
@@ -215,7 +216,7 @@ export function StoreSettingsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('storeSettings.title')}</h2> <PageTitle en="STORE SETTINGS">{t('storeSettings.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('storeSettings.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('storeSettings.subtitle')}</p>
</div> </div>
</div> </div>
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { EditStoreItemsModal } from '../components/EditStoreItemsModal' import { EditStoreItemsModal } from '../components/EditStoreItemsModal'
import { PickStoreSpecialItemsModal } from '../components/PickStoreSpecialItemsModal' import { PickStoreSpecialItemsModal } from '../components/PickStoreSpecialItemsModal'
@@ -311,7 +312,7 @@ export function StoreSpecialsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.specialItems')}</h2> <PageTitle en="SPECIAL ITEMS">{t('title.specialItems')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('website.specialItems.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('website.specialItems.subtitle')}</p>
</div> </div>
</div> </div>
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { RotateCcw, Search } from 'lucide-react' import { RotateCcw, Search } from 'lucide-react'
import { useLocale } from '@meshkee/dashboard-ui' import { useLocale } from '@meshkee/dashboard-ui'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ContactSubmissionDetailModal } from '../components/ContactSubmissionDetailModal' import { ContactSubmissionDetailModal } from '../components/ContactSubmissionDetailModal'
import { Pagination } from '../components/Pagination' import { Pagination } from '../components/Pagination'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
@@ -112,7 +113,7 @@ export function WebsiteContactPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.contactForm')}</h2> <PageTitle en="CONTACT">{t('title.contactForm')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('website.contact.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('website.contact.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -11,6 +11,7 @@ import {
} from 'lucide-react' } from 'lucide-react'
import { SectionCard } from '../components/SectionCard' import { SectionCard } from '../components/SectionCard'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import type { BusinessMessageKey } from '../i18n/messages' import type { BusinessMessageKey } from '../i18n/messages'
import styles from '../components/PageContent.module.css' import styles from '../components/PageContent.module.css'
@@ -100,7 +101,7 @@ export function WebsitePage() {
/> />
<div className={styles.pageHeader}> <div className={styles.pageHeader}>
<div> <div>
<h2 className={styles.pageTitle}>{t('title.website')}</h2> <PageTitle en="WEBSITE">{t('title.website')}</PageTitle>
<p className={styles.pageSubtitle}>{t('website.page.subtitle')}</p> <p className={styles.pageSubtitle}>{t('website.page.subtitle')}</p>
</div> </div>
</div> </div>
@@ -4,6 +4,7 @@ import {
type WebsiteSliderSlideFormData, type WebsiteSliderSlideFormData,
} from '../components/AddWebsiteSliderSlideModal' } from '../components/AddWebsiteSliderSlideModal'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { WebsiteSliderGallery } from '../components/WebsiteSliderGallery' import { WebsiteSliderGallery } from '../components/WebsiteSliderGallery'
import { useToast } from '../context/ToastContext' import { useToast } from '../context/ToastContext'
@@ -138,7 +139,7 @@ export function WebsiteSlidersPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.sliders')}</h2> <PageTitle en="SLIDERS">{t('title.sliders')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('website.sliders.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('website.sliders.subtitle')}</p>
</div> </div>
</div> </div>
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { PickWebsiteBrandsModal } from '../components/PickWebsiteBrandsModal' import { PickWebsiteBrandsModal } from '../components/PickWebsiteBrandsModal'
import { StoreSpecialModal } from '../components/StoreSpecialModal' import { StoreSpecialModal } from '../components/StoreSpecialModal'
@@ -204,7 +205,7 @@ export function WebsiteSpecialBrandsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.specialBrands')}</h2> <PageTitle en="SPECIAL BRANDS">{t('title.specialBrands')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('website.specialBrands.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('website.specialBrands.subtitle')}</p>
</div> </div>
</div> </div>
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import { Breadcrumbs } from '../components/Breadcrumbs' import { Breadcrumbs } from '../components/Breadcrumbs'
import { PageTitle } from '../components/PageTitle'
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal' import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { PickWebsiteCategoriesModal } from '../components/PickWebsiteCategoriesModal' import { PickWebsiteCategoriesModal } from '../components/PickWebsiteCategoriesModal'
import { StoreSpecialModal } from '../components/StoreSpecialModal' import { StoreSpecialModal } from '../components/StoreSpecialModal'
@@ -204,7 +205,7 @@ export function WebsiteSpecialCategoriesPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('title.specialCategories')}</h2> <PageTitle en="SPECIAL CATEGORIES">{t('title.specialCategories')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('website.specialCategories.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('website.specialCategories.subtitle')}</p>
</div> </div>
</div> </div>
@@ -122,6 +122,28 @@
.profileWrap { .profileWrap {
position: relative; 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 { .profile {
+26 -1
View File
@@ -1,10 +1,12 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom' 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 { LanguageSelect, PasswordResetModal, useLocale } from '@meshkee/dashboard-ui'
import { useAuth } from '../context/AuthContext' import { useAuth } from '../context/AuthContext'
import { useTenantBranding } from '../context/TenantBrandingContext' import { useTenantBranding } from '../context/TenantBrandingContext'
import { useT } from '../i18n/useT' import { useT } from '../i18n/useT'
import { getActiveBusinessId } from '../lib/businessContext'
import { getBusinessDashboardUrl } from '../lib/config'
import { changePassword } from '../services/authService' import { changePassword } from '../services/authService'
import meshkeeLogo from '../assets/meshkee-logo.png' import meshkeeLogo from '../assets/meshkee-logo.png'
import styles from './Header.module.css' import styles from './Header.module.css'
@@ -57,6 +59,17 @@ export function Header({ hideMenu = false, showBrand = false }: HeaderProps) {
? (businessName && businessName !== brandTitle ? businessName : '') ? (businessName && businessName !== brandTitle ? businessName : '')
: (businessNameEn && businessNameEn !== brandTitle ? businessNameEn : '') : (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(() => { useEffect(() => {
if (!menuOpen) return if (!menuOpen) return
@@ -128,6 +141,18 @@ export function Header({ hideMenu = false, showBrand = false }: HeaderProps) {
</button> </button>
<div className={styles.profileWrap} ref={menuRef}> <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 <button
type="button" type="button"
className={`${styles.profile} ${menuOpen ? styles.profileOpen : ''}`} className={`${styles.profile} ${menuOpen ? styles.profileOpen : ''}`}
@@ -8,19 +8,44 @@
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 24px; gap: 24px;
margin-bottom: 36px; margin-bottom: 12px;
} }
.pageTitle { .pageTitle {
font-size: 28px; font-size: 28px;
font-weight: 700; font-weight: 700;
color: var(--text-primary); 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 { .pageSubtitle {
display: none;
}
.pageSubtitleContent {
display: block;
font-size: 15px; font-size: 15px;
color: var(--text-secondary); color: var(--text-secondary);
margin-top: 6px;
} }
.dateBadge { .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.notifications': 'Notifications',
'header.changePassword': 'Change password', 'header.changePassword': 'Change password',
'header.myProfile': 'My Profile', 'header.myProfile': 'My Profile',
'header.openBusinessDashboard': 'Open business dashboard',
'home.welcome': 'Welcome, dear {name}.', 'home.welcome': 'Welcome, dear {name}.',
'home.welcomeFallback': 'there', 'home.welcomeFallback': 'there',
@@ -406,6 +407,7 @@ const fa: Record<MessageKey, string> = {
'header.notifications': 'اعلان‌ها', 'header.notifications': 'اعلان‌ها',
'header.changePassword': 'تغییر رمز عبور', 'header.changePassword': 'تغییر رمز عبور',
'header.myProfile': 'پروفایل من', 'header.myProfile': 'پروفایل من',
'header.openBusinessDashboard': 'رفتن به داشبورد کسب‌وکار',
'home.welcome': '{name} عزیز، خوش آمدی.', 'home.welcome': '{name} عزیز، خوش آمدی.',
'home.welcomeFallback': 'کاربر', 'home.welcomeFallback': 'کاربر',
+10
View File
@@ -1,6 +1,7 @@
import { import {
CUSTOMER_SUBDOMAIN_PREFIX, CUSTOMER_SUBDOMAIN_PREFIX,
getBaseDomainFromHost, getBaseDomainFromHost,
getBusinessDashboardHost,
getCustomerDashboardHost, getCustomerDashboardHost,
isCustomerDashboardHost, isCustomerDashboardHost,
} from '@meshkee/dashboard-core' } from '@meshkee/dashboard-core'
@@ -31,3 +32,12 @@ export function isAllowedCustomerHost(hostname = window.location.hostname): bool
export function getTenantDomain(): string { export function getTenantDomain(): string {
return getBaseBusinessDomain() 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 { useCallback, useEffect, useState } from 'react'
import { MapPin, Pencil, Plus, Trash2 } from 'lucide-react' import { MapPin, Pencil, Plus, Trash2 } from 'lucide-react'
import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui' import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui'
@@ -107,7 +108,7 @@ export function AddressesPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('addresses.title')}</h2> <PageTitle en="ADDRESSES">{t('addresses.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('addresses.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('addresses.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Heart } from 'lucide-react' import { Heart } from 'lucide-react'
import { Breadcrumbs, Pagination, useToast } from '@meshkee/dashboard-ui' import { Breadcrumbs, Pagination, useToast } from '@meshkee/dashboard-ui'
@@ -82,7 +83,7 @@ export function FavoritesPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('favorites.title')}</h2> <PageTitle en="FAVORITES">{t('favorites.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('favorites.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('favorites.subtitle')}</p>
</div> </div>
</div> </div>
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom' import { Link, useNavigate, useParams } from 'react-router-dom'
import { ImageOff, MapPin, Pencil } from 'lucide-react' import { ImageOff, MapPin, Pencil } from 'lucide-react'
@@ -172,8 +173,8 @@ export function MyProductDetailsPage() {
<div className={styles.headerRow}> <div className={styles.headerRow}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('myProducts.detailsTitle')}</h2> <PageTitle en="PRODUCT">{t('myProducts.detailsTitle')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('myProducts.detailsSubtitle')}</p> <p className={pageStyles.pageSubtitleContent}>{t('myProducts.detailsSubtitle')}</p>
</div> </div>
<button <button
type="button" type="button"
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Package, Plus } from 'lucide-react' import { Package, Plus } from 'lucide-react'
@@ -126,7 +127,7 @@ export function MyProductsPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <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> <p className={pageStyles.pageSubtitle}>{t('myProducts.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { Breadcrumbs, Pagination, useLocale } from '@meshkee/dashboard-ui' import { Breadcrumbs, Pagination, useLocale } from '@meshkee/dashboard-ui'
import { OrderItemsModal } from '../components/OrderItemsModal' import { OrderItemsModal } from '../components/OrderItemsModal'
@@ -71,7 +72,7 @@ export function OrdersPage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('orders.title')}</h2> <PageTitle en="ORDERS">{t('orders.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('orders.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('orders.subtitle')}</p>
</div> </div>
</div> </div>
+2 -1
View File
@@ -1,3 +1,4 @@
import { PageTitle } from '../components/PageTitle'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui' import { Breadcrumbs, useToast } from '@meshkee/dashboard-ui'
import { useAuth } from '../context/AuthContext' import { useAuth } from '../context/AuthContext'
@@ -81,7 +82,7 @@ export function ProfilePage() {
<div className={pageStyles.pageHeader}> <div className={pageStyles.pageHeader}>
<div> <div>
<h2 className={pageStyles.pageTitle}>{t('profile.title')}</h2> <PageTitle en="PROFILE">{t('profile.title')}</PageTitle>
<p className={pageStyles.pageSubtitle}>{t('profile.subtitle')}</p> <p className={pageStyles.pageSubtitle}>{t('profile.subtitle')}</p>
</div> </div>
</div> </div>
@@ -8,19 +8,44 @@
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 16px;
margin-bottom: 24px; margin-bottom: 12px;
} }
.pageTitle { .pageTitle {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
color: var(--text-primary); color: var(--text-primary);
margin-bottom: 4px; 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-secondary);
font-family: var(--font-en), var(--font-ui);
}
.pageTitleEn::before {
content: '•';
margin-inline-end: 0.4em;
letter-spacing: 0;
} }
.pageSubtitle { .pageSubtitle {
display: none;
}
.pageSubtitleContent {
display: block;
font-size: 14px; font-size: 14px;
color: var(--text-secondary); color: var(--text-secondary);
margin-top: 6px;
} }
.dateBadge { .dateBadge {
@@ -0,0 +1,17 @@
import type { ReactNode } from 'react'
import pageStyles from './PageContent.module.css'
interface PageTitleProps {
children: ReactNode
/** English accent shown after a bullet (e.g. USERS). */
en?: string
}
export function PageTitle({ children, en }: PageTitleProps) {
return (
<h2 className={pageStyles.pageTitle}>
{children}
{en ? <span className={pageStyles.pageTitleEn}>{en}</span> : null}
</h2>
)
}
@@ -9,10 +9,7 @@
} }
.filtersTitle { .filtersTitle {
font-size: 14px; display: none;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 10px;
} }
.filtersGrid { .filtersGrid {
@@ -68,6 +68,10 @@
cursor: pointer; cursor: pointer;
} }
.roleOption input[type='radio'] {
accent-color: var(--primary);
}
.roleOption:has(input:checked) { .roleOption:has(input:checked) {
border-color: rgba(var(--primary-rgb) / 0.35); border-color: rgba(var(--primary-rgb) / 0.35);
background: rgba(var(--primary-rgb) / 0.08); background: rgba(var(--primary-rgb) / 0.08);
+2 -3
View File
@@ -46,7 +46,7 @@ const TEAM_ROLE_ORDER = ['admin', 'editor', 'viewer'] as const
const TEAM_ROLE_LABELS: Record<string, string> = { const TEAM_ROLE_LABELS: Record<string, string> = {
admin: 'Admin', admin: 'Admin',
editor: 'Editor', editor: 'Editor',
viewer: 'Viewer', viewer: 'Observer',
} }
function teamRoleLabel(slug: string | null | undefined, teamRoles: RoleOption[]) { function teamRoleLabel(slug: string | null | undefined, teamRoles: RoleOption[]) {
@@ -1021,8 +1021,7 @@ export function UsersPage() {
{businessAccess === 'staff' ? ( {businessAccess === 'staff' ? (
<> <>
<p className={styles.roleHint}> <p className={styles.roleHint}>
Only a super admin can assign Admin. Admins can assign Editor or Viewer. Admins Only a super admin can assign Admin. Admins can assign Editor or Observer.
cannot change other admins.
</p> </p>
<div className={`${styles.roleList} ${styles.roleListNested}`}> <div className={`${styles.roleList} ${styles.roleListNested}`}>
{orderedTeamRoles.map((role) => ( {orderedTeamRoles.map((role) => (