Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/components/performance/CoreWebVitals.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import React from 'react';
import type { PerformanceMetric } from '@/utils/performanceUtils';
import { formatMetricValue } from '@/utils/performanceUtils';
import { useInternationalization } from '@/hooks/useInternationalization';
import { translateWithFallback } from '@/components/dashboard/dashboardI18n';

export interface CoreWebVitalsProps {
metrics: Record<string, PerformanceMetric>;
Expand All @@ -15,16 +15,37 @@ const RATING_STYLES: Record<NonNullable<PerformanceMetric['rating']>, string> =
poor: 'border-red-500/40 bg-red-500/10 text-red-900 dark:text-red-100',
};

const RATING_KEYS: Record<NonNullable<PerformanceMetric['rating']>, string> = {
good: 'performance.dashboard.vitals.ratings.good',
'needs-improvement': 'performance.dashboard.vitals.ratings.needsImprovement',
poor: 'performance.dashboard.vitals.ratings.poor',
};

const RATING_FALLBACKS: Record<NonNullable<PerformanceMetric['rating']>, string> = {
good: 'Good',
'needs-improvement': 'Needs Improvement',
poor: 'Poor',
};

const ORDER = ['LCP', 'INP', 'CLS', 'FCP', 'TTFB'] as const;

/**
* Read-only grid of latest Core Web Vitals with rating-colored cards.
*/
export const CoreWebVitals: React.FC<CoreWebVitalsProps> = ({ metrics, className = '' }) => {
const { t } = useInternationalization();

return (
<section className={className} aria-label="Core Web Vitals">
<section
className={className}
aria-label={translateWithFallback(
t,
'performance.dashboard.vitals.ariaLabel',
'Core Web Vitals',
)}
>
<h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">
Core Web Vitals
{translateWithFallback(t, 'performance.dashboard.vitals.heading', 'Core Web Vitals')}
</h2>
<ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
{ORDER.map((name) => {
Expand All @@ -40,10 +61,12 @@ export const CoreWebVitals: React.FC<CoreWebVitalsProps> = ({ metrics, className
</div>
{m?.rating ? (
<div className="text-xs mt-1 capitalize opacity-90">
{m.rating.replace(/-/g, ' ')}
{translateWithFallback(t, RATING_KEYS[m.rating], RATING_FALLBACKS[m.rating])}
</div>
) : (
<div className="text-xs mt-1 opacity-70">Waiting…</div>
<div className="text-xs mt-1 opacity-70">
{translateWithFallback(t, 'performance.dashboard.vitals.waiting', 'Waiting…')}
</div>
)}
</li>
);
Expand Down
63 changes: 52 additions & 11 deletions src/components/performance/OptimizationSuggestions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import React from 'react';
import { Lightbulb } from 'lucide-react';
import type { OptimizationSuggestion } from '@/utils/performanceUtils';
import { useInternationalization } from '@/hooks/useInternationalization';
import { translateWithFallback } from '@/components/dashboard/dashboardI18n';

export interface OptimizationSuggestionsProps {
suggestions: OptimizationSuggestion[];
Expand All @@ -18,28 +18,46 @@ export const OptimizationSuggestions: React.FC<OptimizationSuggestionsProps> = (
suggestions,
className = '',
}) => {
const { t } = useInternationalization();
const sorted = [...suggestions].sort((a, b) => IMPACT_ORDER[a.impact] - IMPACT_ORDER[b.impact]);

const sectionAriaLabel = translateWithFallback(
t,
'performance.telemetry.suggestions.heading',
'Optimization suggestions',
);

if (sorted.length === 0) {
return (
<section className={className} aria-label="Optimization suggestions">
<section className={className} aria-label={sectionAriaLabel}>
<h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2 flex items-center gap-2">
<Lightbulb className="w-4 h-4" aria-hidden />
Suggestions
{translateWithFallback(
t,
'performance.telemetry.suggestions.headingFallback',
'Suggestions',
)}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
No issues detected from current Core Web Vitals. Keep monitoring as users interact with
the app.
{translateWithFallback(
t,
'performance.telemetry.suggestions.empty',
'No issues detected from current Core Web Vitals. Keep monitoring as users interact with the app.',
)}
</p>
</section>
);
}

return (
<section className={className} aria-label="Optimization suggestions">
<section className={className} aria-label={sectionAriaLabel}>
<h2 className="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center gap-2">
<Lightbulb className="w-4 h-4" aria-hidden />
Optimization suggestions
{translateWithFallback(
t,
'performance.telemetry.suggestions.heading',
'Optimization suggestions',
)}
</h2>
<ul className="space-y-3">
{sorted.map((s) => (
Expand All @@ -48,17 +66,40 @@ export const OptimizationSuggestions: React.FC<OptimizationSuggestionsProps> = (
className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-950/60 p-3"
>
<div className="flex flex-wrap items-center gap-2 mb-1">
<span className="font-medium text-gray-900 dark:text-gray-100">{s.title}</span>
<span className="font-medium text-gray-900 dark:text-gray-100">
{translateWithFallback(
t,
`performance.telemetry.suggestions.${s.id}.title`,
s.title,
)}
</span>
<span className="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-300">
{s.impact} impact
{translateWithFallback(
t,
'performance.telemetry.suggestions.impact',
`${s.impact} impact`,
{
impact: translateWithFallback(
t,
`performance.telemetry.suggestions.impactLevels.${s.impact}`,
s.impact,
),
},
)}
</span>
{s.metric ? (
<span className="text-[10px] font-mono text-gray-500 dark:text-gray-400">
{s.metric}
</span>
) : null}
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed">{s.detail}</p>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed">
{translateWithFallback(
t,
`performance.telemetry.suggestions.${s.id}.detail`,
s.detail,
)}
</p>
</li>
))}
</ul>
Expand Down
Loading
Loading