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
123 changes: 37 additions & 86 deletions frontend/components/BroadcastBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useCallback, useEffect, useState } from "react";
import { AlertTriangle, Info, Sparkles, RefreshCw, Wrench, Megaphone, X, ArrowRight } from "lucide-react";
import { X, ArrowRight } from "lucide-react";
import { fetchActiveBroadcasts, dismissBroadcast } from "@/lib/api";
import { useWebSocket } from "@/lib/event";
import { Broadcast } from "@/lib/types";
Expand All @@ -10,88 +10,46 @@ import { cn } from "@/lib/utils";
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,
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-100",
textColor: "text-blue-200/60",
button: "bg-blue-500/15 text-blue-300 hover:bg-blue-500/25 border-blue-500/20",
border: "border-l-blue-500",
textColor: "text-blue-100/70",
button: "bg-blue-500/15 text-blue-300 hover:bg-blue-500/25",
dismissHover: "hover:bg-blue-500/10 hover:text-blue-300",
},
warning: {
icon: AlertTriangle,
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-100",
textColor: "text-amber-200/60",
button: "bg-amber-500/15 text-amber-300 hover:bg-amber-500/25 border-amber-500/20",
border: "border-l-amber-500",
textColor: "text-amber-100/70",
button: "bg-amber-500/15 text-amber-300 hover:bg-amber-500/25",
dismissHover: "hover:bg-amber-500/10 hover:text-amber-300",
},
update: {
icon: RefreshCw,
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-100",
textColor: "text-emerald-200/60",
button: "bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25 border-emerald-500/20",
border: "border-l-emerald-500",
textColor: "text-emerald-100/70",
button: "bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25",
dismissHover: "hover:bg-emerald-500/10 hover:text-emerald-300",
},
new_feature: {
icon: Sparkles,
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-100",
textColor: "text-purple-200/60",
button: "bg-purple-500/15 text-purple-300 hover:bg-purple-500/25 border-purple-500/20",
border: "border-l-purple-500",
textColor: "text-purple-100/70",
button: "bg-purple-500/15 text-purple-300 hover:bg-purple-500/25",
dismissHover: "hover:bg-purple-500/10 hover:text-purple-300",
},
maintenance: {
icon: Wrench,
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-100",
textColor: "text-red-200/60",
button: "bg-red-500/15 text-red-300 hover:bg-red-500/25 border-red-500/20",
border: "border-l-red-500",
textColor: "text-red-100/70",
button: "bg-red-500/15 text-red-300 hover:bg-red-500/25",
dismissHover: "hover:bg-red-500/10 hover:text-red-300",
},
announcement: {
icon: Megaphone,
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-100",
textColor: "text-sky-200/60",
button: "bg-sky-500/15 text-sky-300 hover:bg-sky-500/25 border-sky-500/20",
border: "border-l-sky-500",
textColor: "text-sky-100/70",
button: "bg-sky-500/15 text-sky-300 hover:bg-sky-500/25",
dismissHover: "hover:bg-sky-500/10 hover:text-sky-300",
},
};
Expand Down Expand Up @@ -159,62 +117,55 @@ export default function BroadcastBanner() {
<div className="space-y-3 mb-5 max-w-3xl mx-auto">
{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 (
<div
key={broadcast.id}
className={cn(
"group relative overflow-hidden rounded-xl border transition-colors",
"group relative overflow-hidden rounded-lg border border-l-[3px] bg-[#1A1A1A] transition-colors",
"animate-in fade-in slide-in-from-top-2 duration-500",
style.bg,
style.border,
broadcast.priority === "critical" && "ring-1 ring-red-500/30",
broadcast.priority === "critical" && "border-l-red-500 ring-1 ring-red-500/20",
)}
>
<div className={cn("h-1 w-full", style.accentBar)} />

<div className="px-4 py-3">
<div className="flex items-start gap-3">
<div className={cn("mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg", style.iconBg)}>
<Icon className={cn("size-4", style.iconColor)} />
</div>

<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<h4 className={cn("text-sm font-semibold leading-snug", style.titleColor)}>
<h4 className="text-sm font-semibold text-foreground leading-snug">
{broadcast.title}
</h4>
{hasMessage && (
<p className={cn("mt-1 text-[13px] leading-relaxed", style.textColor)}>
{broadcast.message}
</p>
)}
{broadcast.action_label && broadcast.action_url && (
<a
href={broadcast.action_url}
className={cn(
"mt-3 inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-semibold transition-all",
style.button,
)}
>
{broadcast.action_label}
<ArrowRight size={11} />
</a>
)}
</div>

<button
onClick={() => handleDismiss(broadcast.id)}
className={cn(
"mt-1 flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground/50 transition-colors",
"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/50 transition-colors",
style.dismissHover,
)}
aria-label="Dismiss"
>
<X size={14} />
</button>
</div>

{broadcast.action_label && broadcast.action_url && (
<a
href={broadcast.action_url}
className={cn(
"mt-3 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-semibold transition-all",
style.button,
)}
>
{broadcast.action_label}
<ArrowRight size={11} />
</a>
)}
</div>
</div>
);
Expand Down
39 changes: 16 additions & 23 deletions frontend/components/GoogleLinkBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"use client";

import { useEffect, useState } from "react";
import { AlertTriangle } from "@untitledui/icons";
import { Chrome, X } from "lucide-react";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
import { fetchUser } from "@/lib/api";
import { User } from "@/lib/types";
Expand Down Expand Up @@ -32,45 +31,39 @@ export default function GoogleLinkBanner() {
return (
<div
className={cn(
"group relative overflow-hidden rounded-xl border border-amber-500/15 bg-amber-500/[0.04] transition-colors",
"group relative overflow-hidden rounded-lg border border-l-[3px] border-l-amber-500 bg-[#1A1A1A] transition-colors",
"animate-in fade-in slide-in-from-top-2 duration-500",
)}
>
<div className="h-1 w-full bg-amber-500" />

<div className="px-4 py-3">
<div className="flex items-start gap-3">
<div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-amber-500/10">
<AlertTriangle className="size-4 text-amber-400" />
</div>

<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<h4 className="text-sm font-semibold text-amber-100 leading-snug">
<h4 className="text-sm font-semibold text-foreground leading-snug">
Secure your account
</h4>
<p className="mt-1 text-[13px] leading-relaxed text-amber-200/60">
<p className="mt-1 text-[13px] leading-relaxed text-amber-100/70">
Link your Google account for seamless sign-in and automatic profile syncing across all your devices.
</p>
<a
href="/settings?tab=security"
className={cn(
"mt-3 inline-flex items-center gap-1.5 rounded-lg border border-amber-500/20 bg-amber-500/15 px-3 py-1.5 text-xs font-semibold text-amber-300 transition-all",
"hover:bg-amber-500/25",
)}
>
<Chrome size={13} />
Link Google
</a>
</div>

<button
onClick={handleDismiss}
className="mt-1 flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground/50 transition-colors hover:bg-amber-500/10 hover:text-amber-300"
className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/50 transition-colors hover:bg-amber-500/10 hover:text-amber-300"
aria-label="Dismiss"
>
<X size={14} />
</button>
</div>

<a
href="/settings?tab=security"
className={cn(
"mt-3 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-semibold transition-all",
"bg-amber-500/15 text-amber-300 hover:bg-amber-500/25",
)}
>
Link Google
</a>
</div>
</div>
);
Expand Down
Loading
Loading