e1d1c392eb
Audit & Quality: - RTL: replaced 121 LTR-only utilities (text-left, pl-, left-) with logical properties - A11y: focus traps + ARIA on Modal/SlidePanel/TabbedModal, clickable divs→buttons - Theming: bg-white→bg-surface (170 instances), text-gray→semantic tokens - Performance: useMemo on filters, loading="lazy" on 24 images - CSS: prefers-reduced-motion, removed dead animations Component Splits: - PostDetailPanel: 1332→623 lines + 4 sub-components - ArtefactDetailPanel: 972→590 lines + 1 sub-component Brand Identity — Rawaj (رواج): - New name, DM Sans font, deep teal palette (#0d9488) - Custom SVG logo, forest-tinted dark mode - All emails branded with app name in subject line Design Refinement: - Dashboard: merged posts+deadlines into tabbed ActivityFeed, inline stats - Quieter: removed card lift, brand glow, gradient text, mesh backgrounds - CampaignDetail: prominent budget card, compact team avatars, Lucide icons - Consistent page titles via Header.jsx, standardized section headers - Finance page fully i18n'd (20+ hardcoded strings replaced) Budget Allocation Redesign: - Single source of truth: BudgetEntries (Campaign.budget deprecated) - Validation at all levels: main→campaign→track, expenses blocked if insufficient - Budget request workflow with CEO approval via public link - BudgetRequests table, CRUD routes, public approval page - Budget mutex for race condition prevention - Idempotent migration for existing campaign budgets Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
64 lines
2.0 KiB
React
64 lines
2.0 KiB
React
import { useLanguage } from '../i18n/LanguageContext'
|
|
|
|
export default function EmptyState({
|
|
icon: Icon,
|
|
title,
|
|
description,
|
|
actionLabel,
|
|
onAction,
|
|
secondaryActionLabel,
|
|
onSecondaryAction,
|
|
compact = false
|
|
}) {
|
|
const { t } = useLanguage()
|
|
|
|
if (compact) {
|
|
return (
|
|
<div className="py-8 text-center">
|
|
{Icon && <Icon className="w-8 h-8 text-text-tertiary mx-auto mb-2" />}
|
|
<p className="text-sm text-text-secondary">{title}</p>
|
|
{description && <p className="text-xs text-text-tertiary mt-1">{description}</p>}
|
|
{actionLabel && (
|
|
<button
|
|
onClick={onAction}
|
|
className="mt-3 text-sm text-brand-primary hover:text-brand-primary-light font-medium transition-colors"
|
|
>
|
|
{actionLabel}
|
|
</button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="py-16 text-center">
|
|
{Icon && (
|
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-surface-tertiary mb-4">
|
|
<Icon className="w-8 h-8 text-text-tertiary" />
|
|
</div>
|
|
)}
|
|
<h3 className="text-lg font-semibold text-text-primary mb-2">{title}</h3>
|
|
{description && <p className="text-sm text-text-secondary max-w-md mx-auto mb-6">{description}</p>}
|
|
|
|
<div className="flex items-center justify-center gap-3">
|
|
{actionLabel && (
|
|
<button
|
|
onClick={onAction}
|
|
className="px-5 py-2.5 bg-brand-primary text-white rounded-lg text-sm font-medium hover:bg-brand-primary-light transition-colors"
|
|
>
|
|
{actionLabel}
|
|
</button>
|
|
)}
|
|
{secondaryActionLabel && (
|
|
<button
|
|
onClick={onSecondaryAction}
|
|
className="px-5 py-2.5 bg-surface border border-border text-text-primary rounded-lg text-sm font-medium hover:bg-surface-secondary transition-colors"
|
|
>
|
|
{secondaryActionLabel}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|