diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 083b949..646e63b 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -2,6 +2,7 @@ import type {Metadata} from 'next'; import './globals.css'; import { Toaster } from 'sonner'; import { Analytics } from '@vercel/analytics/next'; +import { SpeedInsights } from '@vercel/speed-insights/next'; import DesktopOnlyOverlay from '@/components/DesktopOnlyOverlay'; export const metadata: Metadata = { @@ -46,6 +47,7 @@ export default function RootLayout({children}: {children: React.ReactNode}) { {children} + = { +const TYPE_STYLES: Record< + string, + { + icon: React.ElementType; + accentBar: string; + bg: string; + border: string; + iconBg: string; + iconColor: string; + titleColor: string; + textColor: string; + button: string; + dismissHover: string; + } +> = { info: { icon: Info, - border: "border-blue-500/20", - gradient: "from-blue-500/5 via-blue-500/[0.03] to-transparent", + accentBar: "bg-blue-500", + bg: "bg-blue-500/[0.04]", + border: "border-blue-500/15", iconBg: "bg-blue-500/10", iconColor: "text-blue-400", - titleColor: "text-blue-300", - textColor: "text-blue-200/70", - button: "bg-blue-400/15 text-blue-300 hover:bg-blue-400/25", + titleColor: "text-blue-100", + textColor: "text-blue-200/60", + button: "bg-blue-500/15 text-blue-300 hover:bg-blue-500/25 border-blue-500/20", + dismissHover: "hover:bg-blue-500/10 hover:text-blue-300", }, warning: { icon: AlertTriangle, - border: "border-amber-500/20", - gradient: "from-amber-500/5 via-amber-500/[0.03] to-transparent", + accentBar: "bg-amber-500", + bg: "bg-amber-500/[0.04]", + border: "border-amber-500/15", iconBg: "bg-amber-500/10", iconColor: "text-amber-400", - titleColor: "text-amber-300", - textColor: "text-amber-200/70", - button: "bg-amber-400/15 text-amber-300 hover:bg-amber-400/25", + titleColor: "text-amber-100", + textColor: "text-amber-200/60", + button: "bg-amber-500/15 text-amber-300 hover:bg-amber-500/25 border-amber-500/20", + dismissHover: "hover:bg-amber-500/10 hover:text-amber-300", }, update: { icon: RefreshCw, - border: "border-emerald-500/20", - gradient: "from-emerald-500/5 via-emerald-500/[0.03] to-transparent", + accentBar: "bg-emerald-500", + bg: "bg-emerald-500/[0.04]", + border: "border-emerald-500/15", iconBg: "bg-emerald-500/10", iconColor: "text-emerald-400", - titleColor: "text-emerald-300", - textColor: "text-emerald-200/70", - button: "bg-emerald-400/15 text-emerald-300 hover:bg-emerald-400/25", + titleColor: "text-emerald-100", + textColor: "text-emerald-200/60", + button: "bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25 border-emerald-500/20", + dismissHover: "hover:bg-emerald-500/10 hover:text-emerald-300", }, new_feature: { icon: Sparkles, - border: "border-purple-500/20", - gradient: "from-purple-500/5 via-purple-500/[0.03] to-transparent", + accentBar: "bg-purple-500", + bg: "bg-purple-500/[0.04]", + border: "border-purple-500/15", iconBg: "bg-purple-500/10", iconColor: "text-purple-400", - titleColor: "text-purple-300", - textColor: "text-purple-200/70", - button: "bg-purple-400/15 text-purple-300 hover:bg-purple-400/25", + titleColor: "text-purple-100", + textColor: "text-purple-200/60", + button: "bg-purple-500/15 text-purple-300 hover:bg-purple-500/25 border-purple-500/20", + dismissHover: "hover:bg-purple-500/10 hover:text-purple-300", }, maintenance: { icon: Wrench, - border: "border-red-500/20", - gradient: "from-red-500/5 via-red-500/[0.03] to-transparent", + accentBar: "bg-red-500", + bg: "bg-red-500/[0.04]", + border: "border-red-500/15", iconBg: "bg-red-500/10", iconColor: "text-red-400", - titleColor: "text-red-300", - textColor: "text-red-200/70", - button: "bg-red-400/15 text-red-300 hover:bg-red-400/25", + titleColor: "text-red-100", + textColor: "text-red-200/60", + button: "bg-red-500/15 text-red-300 hover:bg-red-500/25 border-red-500/20", + dismissHover: "hover:bg-red-500/10 hover:text-red-300", }, announcement: { icon: Megaphone, - border: "border-sky-500/20", - gradient: "from-sky-500/5 via-sky-500/[0.03] to-transparent", + accentBar: "bg-sky-500", + bg: "bg-sky-500/[0.04]", + border: "border-sky-500/15", iconBg: "bg-sky-500/10", iconColor: "text-sky-400", - titleColor: "text-sky-300", - textColor: "text-sky-200/70", - button: "bg-sky-400/15 text-sky-300 hover:bg-sky-400/25", + titleColor: "text-sky-100", + textColor: "text-sky-200/60", + button: "bg-sky-500/15 text-sky-300 hover:bg-sky-500/25 border-sky-500/20", + dismissHover: "hover:bg-sky-500/10 hover:text-sky-300", }, }; -const PRIORITY_BADGES: Record = { - low: "border-blue-500/20 bg-blue-500/10 text-blue-400", - medium: "border-amber-500/20 bg-amber-500/10 text-amber-400", - high: "border-orange-500/20 bg-orange-500/10 text-orange-400", - critical: "border-red-500/20 bg-red-500/10 text-red-400", -}; - export default function BroadcastBanner() { const [broadcasts, setBroadcasts] = useState([]); const [dismissed, setDismissed] = useState>(new Set()); @@ -137,59 +156,66 @@ export default function BroadcastBanner() { if (visible.length === 0) return null; return ( -
+
{visible.map((broadcast) => { const style = TYPE_STYLES[broadcast.type] || TYPE_STYLES.info; const Icon = style.icon; + const hasMessage = broadcast.message && broadcast.message.trim().length > 0; return (
-
- +
+ +
+
+
+ +
+ +
+

+ {broadcast.title} +

+ {hasMessage && ( +

+ {broadcast.message} +

+ )} + {broadcast.action_label && broadcast.action_url && ( + + {broadcast.action_label} + + + )} +
+ + +
- - - {broadcast.title} - - - - {broadcast.priority} - - - Admin - - {broadcast.action_label && broadcast.action_url && ( - - {broadcast.action_label} - - - )} - -
); })} diff --git a/frontend/components/GoogleLinkBanner.tsx b/frontend/components/GoogleLinkBanner.tsx index fcc957d..6e0c424 100644 --- a/frontend/components/GoogleLinkBanner.tsx +++ b/frontend/components/GoogleLinkBanner.tsx @@ -32,45 +32,45 @@ export default function GoogleLinkBanner() { return (
-
- -
+
-
-
- Secure your account - - Recommended - -
-

- Link your Google account for seamless sign-in and automatic profile syncing across all your devices. -

-
+
+
+
+ +
-
- - - Link Google - - +
+

+ Secure your account +

+

+ Link your Google account for seamless sign-in and automatic profile syncing across all your devices. +

+ + + Link Google + +
+ + +
); diff --git a/frontend/components/best-practices/SolutionCard.tsx b/frontend/components/best-practices/SolutionCard.tsx index 5fcbb99..cda8ea7 100644 --- a/frontend/components/best-practices/SolutionCard.tsx +++ b/frontend/components/best-practices/SolutionCard.tsx @@ -114,9 +114,7 @@ export function SolutionCard({ {solution.module} )} - {!expanded && ( - setAnalysisOpen(true)} /> - )} + setAnalysisOpen(true)} />
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 626cb53..1a203de 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "@tanstack/react-virtual": "^3.14.4", "@untitledui/icons": "^0.0.22", "@vercel/analytics": "^2.0.1", + "@vercel/speed-insights": "^2.0.0", "autoprefixer": "^10.4.21", "canvas-confetti": "^1.9.4", "class-variance-authority": "^0.7.1", @@ -5309,6 +5310,44 @@ } } }, + "node_modules/@vercel/speed-insights": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vercel/speed-insights/-/speed-insights-2.0.0.tgz", + "integrity": "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==", + "license": "Apache-2.0", + "peerDependencies": { + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "nuxt": ">= 3", + "react": "^18 || ^19 || ^19.0.0-rc", + "svelte": ">= 4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "nuxt": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 344476a..c6488f6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "@tanstack/react-virtual": "^3.14.4", "@untitledui/icons": "^0.0.22", "@vercel/analytics": "^2.0.1", + "@vercel/speed-insights": "^2.0.0", "autoprefixer": "^10.4.21", "canvas-confetti": "^1.9.4", "class-variance-authority": "^0.7.1",