mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 18:18:43 +00:00
fix(ui): polish health page background and priority card layout
Iteration on top of the health redesign: - layout.tsx: drop backdrop-blur transparency on the global header (interfered with the new health page colored backdrop) - health.tsx: switch from fixed full-viewport background to scoped absolute positioning; replace unreliable external grain SVG (vercel app) with an inline data URI that always loads - health-priority-card.tsx: refine spacing, expansion behavior, and visual hierarchy
This commit is contained in:
@@ -1,4 +1,12 @@
|
|||||||
import { AlertCircle, AlertTriangle, Copy, Terminal, Wrench, ChevronRight } from 'lucide-react';
|
import {
|
||||||
|
AlertCircle,
|
||||||
|
AlertTriangle,
|
||||||
|
Copy,
|
||||||
|
Terminal,
|
||||||
|
Wrench,
|
||||||
|
ChevronRight,
|
||||||
|
RefreshCw,
|
||||||
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useFixHealth, type HealthCheck } from '@/hooks/use-health';
|
import { useFixHealth, type HealthCheck } from '@/hooks/use-health';
|
||||||
@@ -14,7 +22,8 @@ interface HealthPriorityCardProps {
|
|||||||
export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
|
export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fixMutation = useFixHealth();
|
const fixMutation = useFixHealth();
|
||||||
const [isExpanded, setIsExpanded] = useState(true);
|
const hasContent = !!(check.details || check.fix || check.fixable);
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
const isError = check.status === 'error';
|
const isError = check.status === 'error';
|
||||||
const Icon = isError ? AlertCircle : AlertTriangle;
|
const Icon = isError ? AlertCircle : AlertTriangle;
|
||||||
@@ -44,7 +53,10 @@ export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
|
|||||||
<div className="absolute inset-0 rounded-[calc(2rem-1px)] shadow-[inset_0_1px_1px_rgba(255,255,255,0.1)] pointer-events-none" />
|
<div className="absolute inset-0 rounded-[calc(2rem-1px)] shadow-[inset_0_1px_1px_rgba(255,255,255,0.1)] pointer-events-none" />
|
||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-start gap-4">
|
<div
|
||||||
|
className={cn('flex items-start gap-4', hasContent && 'cursor-pointer select-none')}
|
||||||
|
onClick={() => hasContent && setIsExpanded(!isExpanded)}
|
||||||
|
>
|
||||||
{/* Status Icon with Ring */}
|
{/* Status Icon with Ring */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -58,19 +70,20 @@ export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
|
|||||||
<div className="flex-1 min-w-0 space-y-1">
|
<div className="flex-1 min-w-0 space-y-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-bold tracking-tight">{check.name}</h3>
|
<h3 className="text-lg font-bold tracking-tight">{check.name}</h3>
|
||||||
<button
|
{hasContent && (
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
<div className="p-1 hover:bg-muted rounded-full transition-colors">
|
||||||
className="p-1 hover:bg-muted rounded-full transition-colors"
|
<ChevronRight
|
||||||
>
|
className={cn(
|
||||||
<ChevronRight
|
'w-5 h-5 text-muted-foreground transition-transform',
|
||||||
className={cn(
|
isExpanded && 'rotate-90'
|
||||||
'w-5 h-5 text-muted-foreground transition-transform',
|
)}
|
||||||
isExpanded && 'rotate-90'
|
/>
|
||||||
)}
|
</div>
|
||||||
/>
|
)}
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-medium leading-relaxed">{check.message}</p>
|
<p className="text-sm font-medium leading-relaxed text-muted-foreground/80">
|
||||||
|
{check.message}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -118,7 +131,11 @@ export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
|
|||||||
: 'bg-amber-500 hover:bg-amber-600'
|
: 'bg-amber-500 hover:bg-amber-600'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Wrench className="w-4 h-4 mr-2" />
|
{fixMutation.isPending ? (
|
||||||
|
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Wrench className="w-4 h-4 mr-2" />
|
||||||
|
)}
|
||||||
{t('health.applyFix')}
|
{t('health.applyFix')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function Layout() {
|
|||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<main className="flex-1 flex flex-col min-h-0 overflow-hidden bg-background">
|
<main className="flex-1 flex flex-col min-h-0 overflow-hidden bg-background">
|
||||||
<header className="flex h-14 items-center justify-between px-6 border-b shrink-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<header className="flex h-14 items-center justify-between px-6 border-b shrink-0 bg-background shadow-sm z-20">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<ClaudeKitBadge />
|
<ClaudeKitBadge />
|
||||||
<SponsorButton />
|
<SponsorButton />
|
||||||
|
|||||||
+11
-6
@@ -47,23 +47,28 @@ export function HealthPage() {
|
|||||||
const hasIssues = priorityChecks.length > 0;
|
const hasIssues = priorityChecks.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative min-h-[100dvh] pb-20">
|
<div className="relative pb-20 overflow-hidden">
|
||||||
{/* Dynamic Background */}
|
{/* Dynamic Background */}
|
||||||
<div className="fixed inset-0 pointer-events-none overflow-hidden">
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute -top-[20%] -left-[10%] w-[70%] h-[70%] blur-[120px] rounded-full opacity-[0.08] transition-colors duration-1000',
|
'absolute top-[5%] -left-[5%] w-[60%] h-[60%] blur-[100px] rounded-full opacity-[0.08] transition-colors duration-1000',
|
||||||
hasIssues ? 'bg-rose-500' : 'bg-emerald-500'
|
hasIssues ? 'bg-rose-500' : 'bg-emerald-500'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute -bottom-[20%] -right-[10%] w-[60%] h-[60%] blur-[120px] rounded-full opacity-[0.05] transition-colors duration-1000',
|
'absolute -bottom-[5%] -right-[5%] w-[50%] h-[50%] blur-[100px] rounded-full opacity-[0.06] transition-colors duration-1000',
|
||||||
hasIssues ? 'bg-amber-500' : 'bg-blue-500'
|
hasIssues ? 'bg-amber-500' : 'bg-blue-500'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{/* Grain overlay */}
|
{/* Grain overlay - Local SVG data URI for reliability */}
|
||||||
<div className="absolute inset-0 opacity-[0.02] mix-blend-overlay bg-[url('https://grainy-gradients.vercel.app/noise.svg')]" />
|
<div
|
||||||
|
className="absolute inset-0 opacity-[0.03] mix-blend-overlay"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative p-6 max-w-6xl mx-auto space-y-12">
|
<div className="relative p-6 max-w-6xl mx-auto space-y-12">
|
||||||
|
|||||||
Reference in New Issue
Block a user