refactor(quieter): tone down AdminPanel + AdminDashboard

Per /impeccable audit findings: admin overview pages concentrated
AI-aesthetic tells. Apply the 'quieter' refinement — strip the loudness,
keep the structure, preserve state-conveying motion.

AdminPanel.tsx
  • Hero h1 — drop bg-clip-text + 3-stop gradient. Solid text-dark-50
    (the absolute ban applied; type weight + size carry hierarchy now).
  • 'Online' dot — drop the 10px-radius glow shadow; the dot + pulse
    animation alone signal liveness.
  • GlassCard — drop the 3D perspective-tilt mouse-tracking (decorative
    motion that didn't convey state), drop the staggered enter
    animation (no orchestrated page-load), drop the top hairline-glow
    gradient, drop the inner shine overlay + drop-shadow-sm on the
    count badge. Keep backdrop-blur on the card body itself — the
    component is literally named GlassCard, the glass identity is the
    point. Hover state stays.
  • Stats-bar pills (2 places) — drop backdrop-blur. Tiny data cells
    don't need glass.
  • Search empty-state icon container — drop backdrop-blur. Decorative
    on a non-interactive surface.

AdminDashboard.tsx
  • Strip backdrop-blur from all 9 stat / node / panel cards. They
    were using it as ambient noise; solid bg-dark-800/{30,50} reads
    better and renders faster.
  • Daily-sales progress bar — drop the from-accent-600 to-accent-400
    gradient (and hover gradient swap). Solid bg-accent-500, hover
    bg-accent-400. Width still animates (functional state change).
  • Animate-pulse on the connected-node status dot kept — it conveys
    'live' state, which is exactly what motion is for in product UI.
This commit is contained in:
c0mrade
2026-05-26 19:11:17 +03:00
parent 9afa7f131f
commit b81346dbb2
2 changed files with 23 additions and 63 deletions

View File

@@ -178,7 +178,7 @@ function StatCard({ title, value, subtitle, icon, color, trend }: StatCardProps)
}; };
return ( return (
<div className="rounded-xl border border-dark-700 bg-dark-800/50 p-5 backdrop-blur transition-colors hover:border-dark-600"> <div className="rounded-xl border border-dark-700 bg-dark-800/50 p-5 transition-colors hover:border-dark-600">
<div className="mb-3 flex items-start justify-between"> <div className="mb-3 flex items-start justify-between">
<div className={`rounded-lg p-2.5 ${colorClasses[color]}`}>{icon}</div> <div className={`rounded-lg p-2.5 ${colorClasses[color]}`}>{icon}</div>
{trend && ( {trend && (
@@ -230,7 +230,7 @@ function NodeCard({ node, onRestart, onToggle, isLoading }: NodeCardProps) {
return ( return (
<div <div
className={`rounded-xl border bg-dark-800/50 backdrop-blur ${node.is_disabled ? 'border-dark-700' : node.is_connected ? 'border-success-500/30' : 'border-error-500/30'} p-4 transition-colors hover:border-dark-600`} className={`rounded-xl border bg-dark-800/50 ${node.is_disabled ? 'border-dark-700' : node.is_connected ? 'border-success-500/30' : 'border-error-500/30'} p-4 transition-colors hover:border-dark-600`}
> >
<div className="mb-3 flex items-start justify-between"> <div className="mb-3 flex items-start justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -499,7 +499,7 @@ export default function AdminDashboard() {
</div> </div>
{/* Nodes Section */} {/* Nodes Section */}
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5 backdrop-blur"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="rounded-lg bg-accent-500/20 p-2.5 text-accent-400"> <div className="rounded-lg bg-accent-500/20 p-2.5 text-accent-400">
@@ -568,7 +568,7 @@ export default function AdminDashboard() {
{/* Revenue and Subscriptions */} {/* Revenue and Subscriptions */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2"> <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
{/* Revenue Chart */} {/* Revenue Chart */}
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5 backdrop-blur"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5">
<div className="mb-4 flex items-center gap-3"> <div className="mb-4 flex items-center gap-3">
<div className="rounded-lg bg-warning-500/20 p-2.5 text-warning-400"> <div className="rounded-lg bg-warning-500/20 p-2.5 text-warning-400">
<ChartBarIcon /> <ChartBarIcon />
@@ -602,7 +602,7 @@ export default function AdminDashboard() {
</div> </div>
{/* Subscription Stats */} {/* Subscription Stats */}
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5 backdrop-blur"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5">
<div className="mb-4 flex items-center gap-3"> <div className="mb-4 flex items-center gap-3">
<div className="rounded-lg bg-accent-500/20 p-2.5 text-accent-400"> <div className="rounded-lg bg-accent-500/20 p-2.5 text-accent-400">
<SparklesIcon /> <SparklesIcon />
@@ -701,7 +701,7 @@ export default function AdminDashboard() {
{/* Tariff Stats */} {/* Tariff Stats */}
{stats?.tariff_stats && stats.tariff_stats.tariffs.length > 0 && ( {stats?.tariff_stats && stats.tariff_stats.tariffs.length > 0 && (
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5 backdrop-blur"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-5">
<div className="mb-4 flex items-center gap-3"> <div className="mb-4 flex items-center gap-3">
<div className="rounded-lg bg-success-500/20 p-2.5 text-success-400"> <div className="rounded-lg bg-success-500/20 p-2.5 text-success-400">
<TagIcon /> <TagIcon />
@@ -778,7 +778,7 @@ export default function AdminDashboard() {
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2"> <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
{/* Top Referrers */} {/* Top Referrers */}
{referrers && (referrers.by_earnings.length > 0 || referrers.by_invited.length > 0) && ( {referrers && (referrers.by_earnings.length > 0 || referrers.by_invited.length > 0) && (
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-4 backdrop-blur sm:p-5"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-4 sm:p-5">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2 sm:gap-3"> <div className="flex items-center gap-2 sm:gap-3">
<div className="rounded-lg bg-accent-500/20 p-2 text-accent-400 sm:p-2.5"> <div className="rounded-lg bg-accent-500/20 p-2 text-accent-400 sm:p-2.5">
@@ -918,7 +918,7 @@ export default function AdminDashboard() {
{/* Top Campaigns */} {/* Top Campaigns */}
{campaigns && campaigns.campaigns.length > 0 && ( {campaigns && campaigns.campaigns.length > 0 && (
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-4 backdrop-blur sm:p-5"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-4 sm:p-5">
<div className="mb-4 flex items-center gap-2 sm:gap-3"> <div className="mb-4 flex items-center gap-2 sm:gap-3">
<div className="rounded-lg bg-warning-500/20 p-2 text-warning-400 sm:p-2.5"> <div className="rounded-lg bg-warning-500/20 p-2 text-warning-400 sm:p-2.5">
<MegaphoneIcon /> <MegaphoneIcon />
@@ -981,7 +981,7 @@ export default function AdminDashboard() {
{/* Recent Payments */} {/* Recent Payments */}
{payments && payments.payments.length > 0 && ( {payments && payments.payments.length > 0 && (
<div className="rounded-xl border border-dark-700 bg-dark-800/30 p-4 backdrop-blur sm:p-5"> <div className="rounded-xl border border-dark-700 bg-dark-800/30 p-4 sm:p-5">
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="rounded-lg bg-success-500/20 p-2 text-success-400 sm:p-2.5"> <div className="rounded-lg bg-success-500/20 p-2 text-success-400 sm:p-2.5">

View File

@@ -690,7 +690,7 @@ const StatsBar = memo(function StatsBar({ systemInfo, dashboardStats, loading }:
<div <div
key={i} key={i}
className={cn( className={cn(
'flex min-w-0 flex-1 items-center gap-2 rounded-xl border border-dark-700/50 bg-dark-800/40 px-3 py-2 backdrop-blur-lg transition-all duration-200', 'flex min-w-0 flex-1 items-center gap-2 rounded-xl border border-dark-700/50 bg-dark-800/40 px-3 py-2 transition-all duration-200',
'light:border-champagne-300/50 light:bg-champagne-100/60', 'light:border-champagne-300/50 light:bg-champagne-100/60',
loading && 'animate-pulse', loading && 'animate-pulse',
)} )}
@@ -740,30 +740,9 @@ const GlassCard = memo(function GlassCard({ section, index, searchTerm }: GlassC
const { t } = useTranslation(); const { t } = useTranslation();
const hasPermission = usePermissionStore((state) => state.hasPermission); const hasPermission = usePermissionStore((state) => state.hasPermission);
const [hoveredItem, setHoveredItem] = useState<number | null>(null); const [hoveredItem, setHoveredItem] = useState<number | null>(null);
const cardRef = useRef<HTMLDivElement>(null); // 3D mouse-tracking tilt removed: decorative motion that didn't convey state.
const [tilt, setTilt] = useState({ x: 0, y: 0 }); // Hover styling alone signals interactivity.
const tiltRef = useRef({ x: 0, y: 0 }); void index;
const rafRef = useRef(0);
useEffect(() => () => cancelAnimationFrame(rafRef.current), []);
const onMouseMove = useCallback((e: React.MouseEvent) => {
if (!cardRef.current) return;
const rect = cardRef.current.getBoundingClientRect();
tiltRef.current = {
x: ((e.clientY - rect.top) / rect.height - 0.5) * -2.5,
y: ((e.clientX - rect.left) / rect.width - 0.5) * 2.5,
};
cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => {
setTilt({ ...tiltRef.current });
});
}, []);
const onMouseLeave = useCallback(() => {
cancelAnimationFrame(rafRef.current);
setTilt({ x: 0, y: 0 });
}, []);
const visibleItems = useMemo( const visibleItems = useMemo(
() => () =>
@@ -796,31 +775,14 @@ const GlassCard = memo(function GlassCard({ section, index, searchTerm }: GlassC
if (visibleItems.length === 0) return null; if (visibleItems.length === 0) return null;
return ( return (
<div <div className="group/card relative overflow-hidden rounded-2xl border border-dark-700/50 bg-dark-800/30 backdrop-blur-xl transition-colors duration-200 hover:border-dark-600/80 light:border-champagne-300/50 light:bg-champagne-100/40 light:hover:border-champagne-400/60">
ref={cardRef}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
className="group/card relative overflow-hidden rounded-2xl border border-dark-700/50 bg-dark-800/30 backdrop-blur-xl transition-all duration-300 hover:border-dark-600/80 hover:shadow-lg light:border-champagne-300/50 light:bg-champagne-100/40 light:hover:border-champagne-400/60"
style={{
transform: `perspective(800px) rotateX(${tilt.x}deg) rotateY(${tilt.y}deg)`,
animation: `adminCardEnter 0.5s cubic-bezier(0.22, 1, 0.36, 1) ${index * 60}ms both`,
}}
>
{/* Top glow line */}
<div
className="absolute left-0 right-0 top-0 h-px opacity-50 transition-all duration-300 group-hover/card:h-0.5 group-hover/card:opacity-100"
style={{ background: section.gradient }}
/>
{/* Header */} {/* Header */}
<div className="flex items-center gap-2.5 border-b border-dark-700/30 px-3.5 py-2.5 light:border-champagne-300/30"> <div className="flex items-center gap-2.5 border-b border-dark-700/30 px-3.5 py-2.5 light:border-champagne-300/30">
<div <div
className="relative flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-lg shadow-md" className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg"
style={{ background: section.gradient }} style={{ background: section.gradient }}
> >
{/* Shine overlay */} <span className="text-xs font-bold text-dark-50" aria-hidden="true">
<div className="absolute inset-0 bg-gradient-to-br from-transparent via-transparent to-white/25" />
<span className="relative text-xs font-bold text-white drop-shadow-sm" aria-hidden="true">
{visibleItems.length} {visibleItems.length}
</span> </span>
</div> </div>
@@ -836,16 +798,13 @@ const GlassCard = memo(function GlassCard({ section, index, searchTerm }: GlassC
key={item.to} key={item.to}
to={item.to} to={item.to}
className={cn( className={cn(
'group/item flex items-center gap-2.5 rounded-xl border border-transparent px-2 py-1.5 transition-all duration-150', 'group/item flex items-center gap-2.5 rounded-xl border border-transparent px-2 py-1.5 transition-colors duration-150',
hoveredItem === i hoveredItem === i
? 'border-dark-600/50 bg-dark-700/30 light:border-champagne-400/40 light:bg-champagne-200/50' ? 'border-dark-600/50 bg-dark-700/30 light:border-champagne-400/40 light:bg-champagne-200/50'
: 'hover:border-dark-600/50 hover:bg-dark-700/30 light:hover:border-champagne-400/40 light:hover:bg-champagne-200/50', : 'hover:border-dark-600/50 hover:bg-dark-700/30 light:hover:border-champagne-400/40 light:hover:bg-champagne-200/50',
)} )}
onMouseEnter={() => setHoveredItem(i)} onMouseEnter={() => setHoveredItem(i)}
onMouseLeave={() => setHoveredItem(null)} onMouseLeave={() => setHoveredItem(null)}
style={{
animation: `adminItemEnter 0.35s cubic-bezier(0.22, 1, 0.36, 1) ${index * 60 + i * 20}ms both`,
}}
> >
{/* Icon */} {/* Icon */}
<div <div
@@ -978,7 +937,7 @@ export default function AdminPanel() {
<div <div
key={i} key={i}
className={cn( className={cn(
'flex items-center gap-2 rounded-xl border border-dark-700/50 bg-dark-800/40 px-2.5 py-2 backdrop-blur-lg', 'flex items-center gap-2 rounded-xl border border-dark-700/50 bg-dark-800/40 px-2.5 py-2',
'light:border-champagne-300/50 light:bg-champagne-100/60', 'light:border-champagne-300/50 light:bg-champagne-100/60',
loading && 'animate-pulse', loading && 'animate-pulse',
)} )}
@@ -1003,12 +962,13 @@ export default function AdminPanel() {
{/* Hero + Search */} {/* Hero + Search */}
<div className="flex shrink-0 flex-wrap items-center gap-3"> <div className="flex shrink-0 flex-wrap items-center gap-3">
<h1 className="bg-gradient-to-r from-dark-50 via-dark-300 to-accent-400 bg-clip-text text-lg font-extrabold tracking-tight text-transparent light:from-champagne-900 light:via-champagne-600 light:to-accent-600 sm:text-xl"> <h1 className="text-lg font-bold tracking-tight text-dark-50 light:text-champagne-900 sm:text-xl">
{t('admin.panel.title')} {t('admin.panel.title')}
</h1> </h1>
<div className="flex items-center gap-1.5 text-xs text-dark-400 light:text-champagne-500"> <div className="flex items-center gap-1.5 text-xs text-dark-400 light:text-champagne-500">
<div <span
className="h-1.5 w-1.5 rounded-full bg-success-400 shadow-[0_0_10px_rgba(var(--color-success-400),0.6)]" aria-hidden="true"
className="h-1.5 w-1.5 rounded-full bg-success-400"
style={{ animation: 'adminPulse 2s ease-in-out infinite' }} style={{ animation: 'adminPulse 2s ease-in-out infinite' }}
/> />
{t('admin.panel.statsOnline')} {t('admin.panel.statsOnline')}
@@ -1060,7 +1020,7 @@ export default function AdminPanel() {
role="status" role="status"
aria-live="polite" aria-live="polite"
> >
<div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-dark-700/50 bg-dark-800/40 text-dark-500 backdrop-blur-lg [&>svg]:h-6 [&>svg]:w-6"> <div className="mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-dark-700/50 bg-dark-800/40 text-dark-500 [&>svg]:h-6 [&>svg]:w-6">
{icons.search} {icons.search}
</div> </div>
<h3 className="text-sm font-semibold text-dark-200 light:text-champagne-800"> <h3 className="text-sm font-semibold text-dark-200 light:text-champagne-800">