feat(brand): AV BEAT 2026 rebrand — blue/navy/white identity

- New AvBeatLogo inline-SVG component (rounded-square emblem with
  forward-leaning AV monogram + horizontal beam detail) at
  src/components/AvBeatLogo.tsx, three variants (full/wordmark/icon).
- Header.tsx now uses the responsive AV BEAT logo lockup in the
  top-left, links to /, full lockup on desktop (emblem+wordmark+tagline),
  emblem+wordmark on tablet, emblem-only on mobile.
- Removed DualSpeedTicker (the 'RECENT FORUM POSTS' ticker strip);
  the news ticker / glow chrome does not match the new aesthetic and the
  forum row was the explicit removal target.
- Tailwind theme + globals.css now expose the AV BEAT 2026 palette as
  semantic tokens: --brand-blue (#1D4ED8), --brand-blue-bright (#38BDF8),
  --brand-navy (#0F172A), --brand-bg (#F8FAFC), --brand-surface, --brand-border,
  --brand-text, --brand-text-muted. Legacy --color-* aliases re-point to the
  new tokens so any inline-styled component re-themes for free.
- Site-wide hex sweep migrates 2,769 hardcoded color literals across 144
  files from the old dark-broadcast palette to the new tokens (orange
  -> blue, dark-brown -> white surface / navy text, cream -> navy).
- Layout body class flipped from 'bb-neon' to 'bg-brand-bg text-brand-text'
  so the dark glow chrome no longer leaks through the new light theme.
This commit is contained in:
2026-06-03 12:07:18 +00:00
parent 39d2bf9073
commit 8042024c4a
146 changed files with 2219 additions and 2081 deletions

View File

@@ -51,7 +51,7 @@ export default function ContactPage() {
<div className="max-w-container mx-auto px-4 py-12 text-[#e5e7eb]"> <div className="max-w-container mx-auto px-4 py-12 text-[#e5e7eb]">
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10"> <div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
<main> <main>
<header className="mb-8 border-b border-[#3a322b] pb-6"> <header className="mb-8 border-b border-[#DCE6F2] pb-6">
<div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2"> <div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2">
AV Beat AV Beat
</div> </div>
@@ -63,7 +63,7 @@ export default function ContactPage() {
<ul className="grid grid-cols-1 md:grid-cols-2 gap-4"> <ul className="grid grid-cols-1 md:grid-cols-2 gap-4">
{CONTACTS.map((c) => ( {CONTACTS.map((c) => (
<li key={c.title} className="rounded border border-[#3a322b] bg-[#0b0f17] p-5 hover:border-[var(--color-text-info,#60a5fa)]/60 transition-colors"> <li key={c.title} className="rounded border border-[#DCE6F2] bg-[#0b0f17] p-5 hover:border-[var(--color-text-info,#60a5fa)]/60 transition-colors">
<div className="text-[10px] font-mono uppercase tracking-[0.18em] text-[var(--color-text-info,#60a5fa)] mb-2"> <div className="text-[10px] font-mono uppercase tracking-[0.18em] text-[var(--color-text-info,#60a5fa)] mb-2">
{c.title} {c.title}
</div> </div>

View File

@@ -14,7 +14,7 @@ export default function AboutPage() {
<div className="max-w-container mx-auto px-4 py-12 text-[#e5e7eb]"> <div className="max-w-container mx-auto px-4 py-12 text-[#e5e7eb]">
<div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10"> <div className="lg:grid lg:grid-cols-[1fr,300px] lg:gap-10">
<main> <main>
<header className="mb-8 border-b border-[#3a322b] pb-6"> <header className="mb-8 border-b border-[#DCE6F2] pb-6">
<div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2"> <div className="text-[10px] font-mono uppercase tracking-[0.22em] text-[var(--color-text-info,#60a5fa)] mb-2">
AV Beat AV Beat
</div> </div>
@@ -79,7 +79,7 @@ export default function AboutPage() {
function Stat({ label, value }: { label: string; value: string }) { function Stat({ label, value }: { label: string; value: string }) {
return ( return (
<div className="rounded border border-[#3a322b] bg-[#0b0f17] p-4"> <div className="rounded border border-[#DCE6F2] bg-[#0b0f17] p-4">
<div className="font-serif text-3xl text-white">{value}</div> <div className="font-serif text-3xl text-white">{value}</div>
<div className="mt-1 text-[10px] font-mono uppercase tracking-[0.16em] text-[#6b7280]"> <div className="mt-1 text-[10px] font-mono uppercase tracking-[0.16em] text-[#6b7280]">
{label} {label}

View File

@@ -101,7 +101,7 @@ export default async function TeamPage() {
<div className="absolute inset-0 opacity-[0.04] bg-[radial-gradient(ellipse_at_top_right,#d60701,transparent_60%)]" aria-hidden /> <div className="absolute inset-0 opacity-[0.04] bg-[radial-gradient(ellipse_at_top_right,#d60701,transparent_60%)]" aria-hidden />
<div className="relative px-6 py-10 sm:px-10 sm:py-14"> <div className="relative px-6 py-10 sm:px-10 sm:py-14">
<div className="inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.22em] text-[#ff5a55] mb-3"> <div className="inline-flex items-center gap-2 text-[10px] font-mono uppercase tracking-[0.22em] text-[#ff5a55] mb-3">
<span className="w-6 h-px bg-[#D85A30]" /> <span className="w-6 h-px bg-[#1D4ED8]" />
AV Beat &middot; editorial AV Beat &middot; editorial
</div> </div>
<h1 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight"> <h1 className="font-serif text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight">
@@ -129,10 +129,10 @@ export default async function TeamPage() {
return ( return (
<li <li
key={p.slug} key={p.slug}
className="group relative rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 hover:border-[#D85A30]/50 hover:shadow-[0_0_0_1px_rgba(214,7,1,0.15)] transition-all" className="group relative rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 hover:border-[#1D4ED8]/50 hover:shadow-[0_0_0_1px_rgba(214,7,1,0.15)] transition-all"
> >
{/* Top accent */} {/* Top accent */}
<div className="absolute top-0 left-5 right-5 h-px bg-gradient-to-r from-transparent via-[#D85A30]/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" aria-hidden /> <div className="absolute top-0 left-5 right-5 h-px bg-gradient-to-r from-transparent via-[#1D4ED8]/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" aria-hidden />
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
{p.avatar_url ? ( {p.avatar_url ? (
@@ -142,7 +142,7 @@ export default async function TeamPage() {
alt={p.name} alt={p.name}
width={96} width={96}
height={96} height={96}
className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] group-hover:border-[#D85A30]/40 transition-colors object-cover" className="w-24 h-24 rounded-full border-2 border-[#1a1f2a] group-hover:border-[#1D4ED8]/40 transition-colors object-cover"
/> />
</a> </a>
) : ( ) : (
@@ -155,7 +155,7 @@ export default async function TeamPage() {
<h3 className="font-serif text-xl font-bold leading-tight">{p.name}</h3> <h3 className="font-serif text-xl font-bold leading-tight">{p.name}</h3>
</a> </a>
{(p.beat || p.slug === "ryan-salazar") && ( {(p.beat || p.slug === "ryan-salazar") && (
<div className="inline-block mt-1.5 px-2 py-0.5 rounded-sm bg-[#D85A30]/10 border border-[#D85A30]/25 text-[10px] font-mono uppercase tracking-[0.12em] text-[#ff6b66]"> <div className="inline-block mt-1.5 px-2 py-0.5 rounded-sm bg-[#1D4ED8]/10 border border-[#1D4ED8]/25 text-[10px] font-mono uppercase tracking-[0.12em] text-[#ff6b66]">
{displayBeat(p)} {displayBeat(p)}
</div> </div>
)} )}
@@ -209,7 +209,7 @@ export default async function TeamPage() {
{ops.map((p) => ( {ops.map((p) => (
<li <li
key={p.id} key={p.id}
className="rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 flex gap-4 hover:border-[#D85A30]/40 transition-colors" className="rounded-xl border border-[#1a1f2a] bg-[#0b0f17] p-5 flex gap-4 hover:border-[#1D4ED8]/40 transition-colors"
> >
{p.avatar_url ? ( {p.avatar_url ? (
<img <img
@@ -226,7 +226,7 @@ export default async function TeamPage() {
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="font-serif text-lg font-bold leading-tight">{p.display_name}</h3> <h3 className="font-serif text-lg font-bold leading-tight">{p.display_name}</h3>
<div className="inline-block mt-1 px-2 py-0.5 rounded-sm bg-[#231d18] border border-[#F0A623]/30 text-[10px] font-mono uppercase tracking-[0.12em] text-[#7ba0ff]"> <div className="inline-block mt-1 px-2 py-0.5 rounded-sm bg-[#FFFFFF] border border-[#1D4ED8]/30 text-[10px] font-mono uppercase tracking-[0.12em] text-[#7ba0ff]">
{p.role} {p.role}
</div> </div>
<div className="text-xs text-[#aab1bd] mt-3 space-y-1"> <div className="text-xs text-[#aab1bd] mt-3 space-y-1">

View File

@@ -284,7 +284,7 @@ export default function AccountPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#1c1815]"> <div className="min-h-screen bg-[#F8FAFC]">
<Header /> <Header />
<div className="max-w-container mx-auto px-4 py-12"> <div className="max-w-container mx-auto px-4 py-12">
<div className="max-w-3xl mx-auto space-y-4"> <div className="max-w-3xl mx-auto space-y-4">
@@ -298,21 +298,21 @@ export default function AccountPage() {
} }
return ( return (
<div className="min-h-screen bg-[#1c1815]"> <div className="min-h-screen bg-[#F8FAFC]">
<Header /> <Header />
{/* Toast */} {/* Toast */}
{toast && ( {toast && (
<div <div
className={`fixed bottom-6 left-1/2 -translate-x-1/2 z-50 border font-body text-sm px-5 py-3 rounded-sm shadow-lg flex items-center gap-2 animate-fade-in ${ className={`fixed bottom-6 left-1/2 -translate-x-1/2 z-50 border font-body text-sm px-5 py-3 rounded-sm shadow-lg flex items-center gap-2 animate-fade-in ${
toast.type === "success" ?"bg-[#231d18] border-[#F0A623] text-[#e0e0e0]" :"bg-[#1a0a0a] border-[#D85A30] text-[#e0e0e0]" toast.type === "success" ?"bg-[#FFFFFF] border-[#1D4ED8] text-[#e0e0e0]" :"bg-[#1a0a0a] border-[#1D4ED8] text-[#e0e0e0]"
}`}> }`}>
{toast.type === "success" ? ( {toast.type === "success" ? (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#F0A623" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1D4ED8" strokeWidth="2.5" aria-hidden="true">
<polyline points="20 6 9 17 4 12" /> <polyline points="20 6 9 17 4 12" />
</svg> </svg>
) : ( ) : (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D85A30" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#1D4ED8" strokeWidth="2.5" aria-hidden="true">
<circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" /> <circle cx="12" cy="12" r="10" /><line x1="15" y1="9" x2="9" y2="15" /><line x1="9" y1="9" x2="15" y2="15" />
</svg> </svg>
)} )}
@@ -321,10 +321,10 @@ export default function AccountPage() {
)} )}
{/* Breadcrumb */} {/* Breadcrumb */}
<div className="bg-[#1c1815] border-b border-[#1e1e1e]"> <div className="bg-[#F8FAFC] border-b border-[#1e1e1e]">
<div className="max-w-container mx-auto px-4 py-2.5"> <div className="max-w-container mx-auto px-4 py-2.5">
<nav aria-label="Breadcrumb" className="flex items-center gap-2 text-xs font-body text-[#555]"> <nav aria-label="Breadcrumb" className="flex items-center gap-2 text-xs font-body text-[#555]">
<Link href="/home-page" className="hover:text-[#F0A623] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#1D4ED8] transition-colors">Home</Link>
<span aria-hidden="true">/</span> <span aria-hidden="true">/</span>
<span className="text-[#888]">Account</span> <span className="text-[#888]">Account</span>
</nav> </nav>
@@ -336,25 +336,25 @@ export default function AccountPage() {
{/* Page Header */} {/* Page Header */}
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div> <div>
<h1 className="font-heading text-2xl font-bold text-[#FBEFE0]">Account Settings</h1> <h1 className="font-heading text-2xl font-bold text-[#0F172A]">Account Settings</h1>
<p className="font-body text-sm text-[#666] mt-1">{user?.email}</p> <p className="font-body text-sm text-[#666] mt-1">{user?.email}</p>
</div> </div>
<button <button
onClick={handleSignOut} onClick={handleSignOut}
className="font-body text-xs font-bold uppercase tracking-wide text-[#888] hover:text-[#D85A30] border border-[#3a322b] hover:border-[#D85A30] px-4 py-2 rounded-sm transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#888] hover:text-[#1D4ED8] border border-[#DCE6F2] hover:border-[#1D4ED8] px-4 py-2 rounded-sm transition-colors">
Sign Out Sign Out
</button> </button>
</div> </div>
{/* Tabs */} {/* Tabs */}
<div className="flex border-b border-[#3a322b] mb-6 overflow-x-auto scrollbar-none"> <div className="flex border-b border-[#DCE6F2] mb-6 overflow-x-auto scrollbar-none">
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`font-body text-sm font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors whitespace-nowrap ${ className={`font-body text-sm font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors whitespace-nowrap ${
activeTab === tab.id activeTab === tab.id
? "border-[#F0A623] text-[#F0A623]" ? "border-[#1D4ED8] text-[#1D4ED8]"
: "border-transparent text-[#666] hover:text-[#aaa]" : "border-transparent text-[#666] hover:text-[#aaa]"
}`}> }`}>
{tab.label} {tab.label}
@@ -365,8 +365,8 @@ export default function AccountPage() {
{/* Profile Tab */} {/* Profile Tab */}
{activeTab === "profile" && ( {activeTab === "profile" && (
<form onSubmit={handleSaveProfile} className="space-y-5"> <form onSubmit={handleSaveProfile} className="space-y-5">
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#DCE6F2]">
Public Profile Public Profile
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
@@ -379,7 +379,7 @@ export default function AccountPage() {
value={fullName} value={fullName}
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setFullName(e.target.value)}
placeholder="Your full name" placeholder="Your full name"
className="w-full bg-[#1c1815] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -391,7 +391,7 @@ export default function AccountPage() {
onChange={(e) => setBio(e.target.value)} onChange={(e) => setBio(e.target.value)}
placeholder="Tell us a bit about yourself..." placeholder="Tell us a bit about yourself..."
rows={3} rows={3}
className="w-full bg-[#1c1815] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444] resize-none" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444] resize-none"
/> />
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
@@ -404,7 +404,7 @@ export default function AccountPage() {
value={website} value={website}
onChange={(e) => setWebsite(e.target.value)} onChange={(e) => setWebsite(e.target.value)}
placeholder="https://yoursite.com" placeholder="https://yoursite.com"
className="w-full bg-[#1c1815] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -416,15 +416,15 @@ export default function AccountPage() {
value={location} value={location}
onChange={(e) => setLocation(e.target.value)} onChange={(e) => setLocation(e.target.value)}
placeholder="City, Country" placeholder="City, Country"
className="w-full bg-[#1c1815] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444]"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-3 pb-3 border-b border-[#3a322b]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-3 pb-3 border-b border-[#DCE6F2]">
Account Info Account Info
</h2> </h2>
<div> <div>
@@ -435,7 +435,7 @@ export default function AccountPage() {
type="email" type="email"
value={user?.email || ""} value={user?.email || ""}
disabled disabled
className="w-full bg-[#1c1815] border border-[#222] text-[#555] font-body text-sm px-3 py-2.5 rounded-sm cursor-not-allowed" className="w-full bg-[#F8FAFC] border border-[#222] text-[#555] font-body text-sm px-3 py-2.5 rounded-sm cursor-not-allowed"
/> />
<p className="font-body text-xs text-[#555] mt-1.5">Email cannot be changed here.</p> <p className="font-body text-xs text-[#555] mt-1.5">Email cannot be changed here.</p>
</div> </div>
@@ -445,7 +445,7 @@ export default function AccountPage() {
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"> className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]">
{saving ? "Saving..." : "Save Profile"} {saving ? "Saving..." : "Save Profile"}
</button> </button>
</div> </div>
@@ -456,8 +456,8 @@ export default function AccountPage() {
{activeTab === "security" && ( {activeTab === "security" && (
<div className="space-y-5"> <div className="space-y-5">
<form onSubmit={handleChangePassword}> <form onSubmit={handleChangePassword}>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#DCE6F2]">
Change Password Change Password
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
@@ -471,7 +471,7 @@ export default function AccountPage() {
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPassword(e.target.value)}
placeholder="Enter new password" placeholder="Enter new password"
minLength={6} minLength={6}
className="w-full bg-[#1c1815] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -484,7 +484,7 @@ export default function AccountPage() {
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Confirm new password" placeholder="Confirm new password"
minLength={6} minLength={6}
className="w-full bg-[#1c1815] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444]"
/> />
</div> </div>
</div> </div>
@@ -492,7 +492,7 @@ export default function AccountPage() {
<button <button
type="submit" type="submit"
disabled={changingPassword || !newPassword || !confirmPassword} disabled={changingPassword || !newPassword || !confirmPassword}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"> className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body text-xs font-bold uppercase tracking-wide px-6 py-2.5 rounded-sm transition-colors disabled:opacity-60 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]">
{changingPassword ? "Updating..." : "Update Password"} {changingPassword ? "Updating..." : "Update Password"}
</button> </button>
</div> </div>
@@ -506,7 +506,7 @@ export default function AccountPage() {
</p> </p>
<button <button
onClick={handleSignOut} onClick={handleSignOut}
className="font-body text-xs font-bold uppercase tracking-wide text-[#D85A30] border border-[#D85A30]/40 hover:border-[#D85A30] hover:bg-[#D85A30]/10 px-5 py-2 rounded-sm transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] border border-[#1D4ED8]/40 hover:border-[#1D4ED8] hover:bg-[#1D4ED8]/10 px-5 py-2 rounded-sm transition-colors">
Sign Out of All Sessions Sign Out of All Sessions
</button> </button>
</div> </div>
@@ -520,12 +520,12 @@ export default function AccountPage() {
<h2 className="font-heading text-base font-bold text-[#e0e0e0]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0]">
Saved Articles Saved Articles
{bookmarks.length > 0 && ( {bookmarks.length > 0 && (
<span className="ml-2 font-body text-xs text-[#666] bg-[#231d18] border border-[#3a322b] px-2 py-0.5 rounded-sm"> <span className="ml-2 font-body text-xs text-[#666] bg-[#FFFFFF] border border-[#DCE6F2] px-2 py-0.5 rounded-sm">
{bookmarks.length} {bookmarks.length}
</span> </span>
)} )}
</h2> </h2>
<Link href="/reading-list" className="font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> <Link href="/reading-list" className="font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
Full List Full List
</Link> </Link>
</div> </div>
@@ -533,37 +533,37 @@ export default function AccountPage() {
{bookmarksLoading ? ( {bookmarksLoading ? (
<div className="space-y-3"> <div className="space-y-3">
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<div key={i} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 animate-pulse"> <div key={i} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-4 animate-pulse">
<div className="flex gap-3"> <div className="flex gap-3">
<div className="w-20 h-14 bg-[#3a322b] rounded-sm flex-shrink-0" /> <div className="w-20 h-14 bg-[#DCE6F2] rounded-sm flex-shrink-0" />
<div className="flex-1 space-y-2"> <div className="flex-1 space-y-2">
<div className="h-3 bg-[#3a322b] rounded w-1/4" /> <div className="h-3 bg-[#DCE6F2] rounded w-1/4" />
<div className="h-4 bg-[#3a322b] rounded w-3/4" /> <div className="h-4 bg-[#DCE6F2] rounded w-3/4" />
</div> </div>
</div> </div>
</div> </div>
))} ))}
</div> </div>
) : bookmarks.length === 0 ? ( ) : bookmarks.length === 0 ? (
<div className="text-center py-12 bg-[#231d18] border border-[#3a322b] rounded-sm"> <div className="text-center py-12 bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" className="mx-auto mb-3" aria-hidden="true"> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" className="mx-auto mb-3" aria-hidden="true">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" /> <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
</svg> </svg>
<p className="font-body text-sm text-[#666]">No saved articles yet.</p> <p className="font-body text-sm text-[#666]">No saved articles yet.</p>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> <Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
Browse News Browse News
</Link> </Link>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{bookmarks.map((item) => ( {bookmarks.map((item) => (
<div key={item.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm hover:border-[#3a3a3a] transition-colors group"> <div key={item.id} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm hover:border-[#3a3a3a] transition-colors group">
<div className="flex gap-3 p-4"> <div className="flex gap-3 p-4">
<Link href={`/articles/${item.article_slug}`} className="flex-shrink-0 w-20 h-14 overflow-hidden rounded-sm"> <Link href={`/articles/${item.article_slug}`} className="flex-shrink-0 w-20 h-14 overflow-hidden rounded-sm">
{item.article_image ? ( {item.article_image ? (
<AppImage src={item.article_image} alt={item.article_image_alt || item.article_title} width={80} height={56} className="w-full h-full object-cover" /> <AppImage src={item.article_image} alt={item.article_image_alt || item.article_title} width={80} height={56} className="w-full h-full object-cover" />
) : ( ) : (
<div className="w-full h-full bg-[#3a322b] flex items-center justify-center"> <div className="w-full h-full bg-[#DCE6F2] flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" aria-hidden="true"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /> <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" />
</svg> </svg>
@@ -572,10 +572,10 @@ export default function AccountPage() {
</Link> </Link>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{item.article_category && ( {item.article_category && (
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#F0A623]">{item.article_category}</span> <span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#1D4ED8]">{item.article_category}</span>
)} )}
<Link href={`/articles/${item.article_slug}`}> <Link href={`/articles/${item.article_slug}`}>
<h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#F0A623] transition-colors line-clamp-2 leading-snug mt-0.5"> <h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#1D4ED8] transition-colors line-clamp-2 leading-snug mt-0.5">
{item.article_title} {item.article_title}
</h3> </h3>
</Link> </Link>
@@ -605,7 +605,7 @@ export default function AccountPage() {
<h2 className="font-heading text-base font-bold text-[#e0e0e0]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0]">
Reading History Reading History
{history.length > 0 && ( {history.length > 0 && (
<span className="ml-2 font-body text-xs text-[#666] bg-[#231d18] border border-[#3a322b] px-2 py-0.5 rounded-sm"> <span className="ml-2 font-body text-xs text-[#666] bg-[#FFFFFF] border border-[#DCE6F2] px-2 py-0.5 rounded-sm">
{history.length} {history.length}
</span> </span>
)} )}
@@ -622,37 +622,37 @@ export default function AccountPage() {
{historyLoading ? ( {historyLoading ? (
<div className="space-y-3"> <div className="space-y-3">
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<div key={i} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 animate-pulse"> <div key={i} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-4 animate-pulse">
<div className="flex gap-3"> <div className="flex gap-3">
<div className="w-20 h-14 bg-[#3a322b] rounded-sm flex-shrink-0" /> <div className="w-20 h-14 bg-[#DCE6F2] rounded-sm flex-shrink-0" />
<div className="flex-1 space-y-2"> <div className="flex-1 space-y-2">
<div className="h-3 bg-[#3a322b] rounded w-1/4" /> <div className="h-3 bg-[#DCE6F2] rounded w-1/4" />
<div className="h-4 bg-[#3a322b] rounded w-3/4" /> <div className="h-4 bg-[#DCE6F2] rounded w-3/4" />
</div> </div>
</div> </div>
</div> </div>
))} ))}
</div> </div>
) : history.length === 0 ? ( ) : history.length === 0 ? (
<div className="text-center py-12 bg-[#231d18] border border-[#3a322b] rounded-sm"> <div className="text-center py-12 bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" className="mx-auto mb-3" aria-hidden="true"> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" className="mx-auto mb-3" aria-hidden="true">
<circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /> <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
</svg> </svg>
<p className="font-body text-sm text-[#666]">No reading history yet.</p> <p className="font-body text-sm text-[#666]">No reading history yet.</p>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> <Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
Browse News Browse News
</Link> </Link>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{history.map((item) => ( {history.map((item) => (
<div key={item.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm hover:border-[#3a3a3a] transition-colors group"> <div key={item.id} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm hover:border-[#3a3a3a] transition-colors group">
<div className="flex gap-3 p-4"> <div className="flex gap-3 p-4">
<Link href={`/articles/${item.article_slug}`} className="flex-shrink-0 w-20 h-14 overflow-hidden rounded-sm"> <Link href={`/articles/${item.article_slug}`} className="flex-shrink-0 w-20 h-14 overflow-hidden rounded-sm">
{item.article_image ? ( {item.article_image ? (
<AppImage src={item.article_image} alt={item.article_image_alt || item.article_title} width={80} height={56} className="w-full h-full object-cover" /> <AppImage src={item.article_image} alt={item.article_image_alt || item.article_title} width={80} height={56} className="w-full h-full object-cover" />
) : ( ) : (
<div className="w-full h-full bg-[#3a322b] flex items-center justify-center"> <div className="w-full h-full bg-[#DCE6F2] flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" aria-hidden="true"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" aria-hidden="true">
<rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /> <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" />
</svg> </svg>
@@ -661,10 +661,10 @@ export default function AccountPage() {
</Link> </Link>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{item.article_category && ( {item.article_category && (
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#F0A623]">{item.article_category}</span> <span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#1D4ED8]">{item.article_category}</span>
)} )}
<Link href={`/articles/${item.article_slug}`}> <Link href={`/articles/${item.article_slug}`}>
<h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#F0A623] transition-colors line-clamp-2 leading-snug mt-0.5"> <h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#1D4ED8] transition-colors line-clamp-2 leading-snug mt-0.5">
{item.article_title} {item.article_title}
</h3> </h3>
</Link> </Link>
@@ -693,17 +693,17 @@ export default function AccountPage() {
{topicsLoading ? ( {topicsLoading ? (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3"> <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{[1, 2, 3, 4, 5, 6].map((i) => ( {[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 animate-pulse h-16" /> <div key={i} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-4 animate-pulse h-16" />
))} ))}
</div> </div>
) : topics.length === 0 ? ( ) : topics.length === 0 ? (
<div className="text-center py-12 bg-[#231d18] border border-[#3a322b] rounded-sm"> <div className="text-center py-12 bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" className="mx-auto mb-3" aria-hidden="true"> <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#444" strokeWidth="1.5" className="mx-auto mb-3" aria-hidden="true">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" /><line x1="7" y1="7" x2="7.01" y2="7" /> <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" /><line x1="7" y1="7" x2="7.01" y2="7" />
</svg> </svg>
<p className="font-body text-sm text-[#666]">No topic preferences tracked yet.</p> <p className="font-body text-sm text-[#666]">No topic preferences tracked yet.</p>
<p className="font-body text-xs text-[#555] mt-1">Read articles to build your interest profile.</p> <p className="font-body text-xs text-[#555] mt-1">Read articles to build your interest profile.</p>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> <Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
Browse News Browse News
</Link> </Link>
</div> </div>
@@ -714,18 +714,18 @@ export default function AccountPage() {
const maxCount = topics[0]?.view_count || 1; const maxCount = topics[0]?.view_count || 1;
const pct = Math.round((t.view_count / maxCount) * 100); const pct = Math.round((t.view_count / maxCount) * 100);
return ( return (
<div key={t.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 hover:border-[#3a3a3a] transition-colors"> <div key={t.id} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-4 hover:border-[#3a3a3a] transition-colors">
<div className="flex items-start justify-between mb-2"> <div className="flex items-start justify-between mb-2">
<span className="font-body text-xs font-bold uppercase tracking-wider text-[#e0e0e0] capitalize leading-tight"> <span className="font-body text-xs font-bold uppercase tracking-wider text-[#e0e0e0] capitalize leading-tight">
{t.topic} {t.topic}
</span> </span>
<span className="font-body text-[10px] text-[#F0A623] font-bold ml-2 flex-shrink-0"> <span className="font-body text-[10px] text-[#1D4ED8] font-bold ml-2 flex-shrink-0">
{t.view_count}× {t.view_count}×
</span> </span>
</div> </div>
<div className="w-full bg-[#3a322b] rounded-full h-1"> <div className="w-full bg-[#DCE6F2] rounded-full h-1">
<div <div
className="bg-[#F0A623] h-1 rounded-full transition-all" className="bg-[#1D4ED8] h-1 rounded-full transition-all"
style={{ width: `${pct}%` }} style={{ width: `${pct}%` }}
/> />
</div> </div>
@@ -744,13 +744,13 @@ export default function AccountPage() {
{/* Preferences Tab */} {/* Preferences Tab */}
{activeTab === "preferences" && ( {activeTab === "preferences" && (
<div className="space-y-5"> <div className="space-y-5">
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#DCE6F2]">
Forum Reputation Forum Reputation
</h2> </h2>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex flex-col items-center justify-center bg-[#111] border border-[#3a322b] rounded-sm px-6 py-4 min-w-[100px]"> <div className="flex flex-col items-center justify-center bg-[#111] border border-[#DCE6F2] rounded-sm px-6 py-4 min-w-[100px]">
<span className="font-heading text-3xl font-bold text-[#F0A623]"> <span className="font-heading text-3xl font-bold text-[#1D4ED8]">
{profile?.reputation ?? 0} {profile?.reputation ?? 0}
</span> </span>
<span className="font-body text-xs text-[#666] mt-1 uppercase tracking-wider">Points</span> <span className="font-body text-xs text-[#666] mt-1 uppercase tracking-wider">Points</span>
@@ -759,15 +759,15 @@ export default function AccountPage() {
<p className="font-body text-sm text-[#aaa] leading-relaxed"> <p className="font-body text-sm text-[#aaa] leading-relaxed">
Earn reputation by posting helpful threads and replies in the community forum. Earn reputation by posting helpful threads and replies in the community forum.
</p> </p>
<Link href="/forum" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> <Link href="/forum" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
Go to Forum Go to Forum
</Link> </Link>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]"> <h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#DCE6F2]">
Reading Preferences Reading Preferences
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
@@ -778,7 +778,7 @@ export default function AccountPage() {
</div> </div>
<Link <Link
href="/home-page" href="/home-page"
className="font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
Manage Manage
</Link> </Link>
</div> </div>
@@ -789,7 +789,7 @@ export default function AccountPage() {
</div> </div>
<Link <Link
href="/reading-list" href="/reading-list"
className="font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors"> className="font-body text-xs font-bold uppercase tracking-wide text-[#1D4ED8] hover:text-[#1E3A8A] transition-colors">
View List View List
</Link> </Link>
</div> </div>
@@ -808,8 +808,8 @@ export default function AccountPage() {
onClick={() => handleToggleDigest(!weeklyDigest)} onClick={() => handleToggleDigest(!weeklyDigest)}
aria-pressed={weeklyDigest} aria-pressed={weeklyDigest}
aria-label="Toggle weekly digest email" aria-label="Toggle weekly digest email"
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] disabled:opacity-50 ${ className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] disabled:opacity-50 ${
weeklyDigest ? "bg-[#F0A623] border-[#F0A623]" : "bg-[#3a322b] border-[#333]" weeklyDigest ? "bg-[#1D4ED8] border-[#1D4ED8]" : "bg-[#DCE6F2] border-[#333]"
}`}> }`}>
<span <span
className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition duration-200 ease-in-out mt-0.5 ${ className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow transition duration-200 ease-in-out mt-0.5 ${

View File

@@ -91,7 +91,7 @@ export default function ClientsPage() {
setForm({ company_name: '', contact_name: '', contact_email: '', contact_phone: '', billing_address: '', notes: '' }); setForm({ company_name: '', contact_name: '', contact_email: '', contact_phone: '', billing_address: '', notes: '' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -102,7 +102,7 @@ export default function ClientsPage() {
<h1 className="text-xl font-bold">Clients</h1> <h1 className="text-xl font-bold">Clients</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 transition-colors">+ New Client</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 transition-colors">+ New Client</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -116,14 +116,14 @@ export default function ClientsPage() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
{['active','inactive','all'].map(s => ( {['active','inactive','all'].map(s => (
<button key={s} onClick={() => setFilter(s)} className={`px-3 py-1 rounded text-xs capitalize ${filter === s ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888] hover:text-white'}`}>{s}</button> <button key={s} onClick={() => setFilter(s)} className={`px-3 py-1 rounded text-xs capitalize ${filter === s ? 'bg-[#1D4ED8] text-white' : 'bg-[#111] border border-[#DCE6F2] text-[#888] hover:text-white'}`}>{s}</button>
))} ))}
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search clients…" className="ml-auto px-3 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#F0A623]" /> <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search clients…" className="ml-auto px-3 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
{/* New Client Form */} {/* New Client Form */}
{showForm && ( {showForm && (
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">New Client</h3> <h3 className="text-sm font-semibold">New Client</h3>
{errors._form && ( {errors._form && (
@@ -148,7 +148,7 @@ export default function ClientsPage() {
type={f.key === 'contact_email' ? 'email' : 'text'} type={f.key === 'contact_email' ? 'email' : 'text'}
value={(form as any)[f.key]} value={(form as any)[f.key]}
onChange={e => handleChange(f.key, e.target.value)} onChange={e => handleChange(f.key, e.target.value)}
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors[f.key] ? 'border-red-500/60' : 'border-[#3a322b]'}`} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors[f.key] ? 'border-red-500/60' : 'border-[#DCE6F2]'}`}
/> />
{errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>} {errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>}
</div> </div>
@@ -156,26 +156,26 @@ export default function ClientsPage() {
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Billing Address</label> <label className="text-xs text-[#888] block mb-1">Billing Address</label>
<input value={form.billing_address} onChange={e => handleChange('billing_address', e.target.value)} <input value={form.billing_address} onChange={e => handleChange('billing_address', e.target.value)}
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} <textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2}
className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Client'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Client'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
{/* Table */} {/* Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Company','Contact','Email','Active Orders','Total Revenue','Status',''].map(h => ( {['Company','Contact','Email','Active Orders','Total Revenue','Status',''].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -187,17 +187,17 @@ export default function ClientsPage() {
) : clients.length === 0 ? ( ) : clients.length === 0 ? (
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No clients found</td></tr> <tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No clients found</td></tr>
) : clients.map(c => ( ) : clients.map(c => (
<tr key={c.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={c.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-4 py-2 text-white font-medium">{c.company_name}</td> <td className="px-4 py-2 text-white font-medium">{c.company_name}</td>
<td className="px-4 py-2 text-[#ccc]">{c.contact_name}</td> <td className="px-4 py-2 text-[#ccc]">{c.contact_name}</td>
<td className="px-4 py-2 text-[#888]">{c.contact_email}</td> <td className="px-4 py-2 text-[#888]">{c.contact_email}</td>
<td className="px-4 py-2 text-[#ccc]">{c.active_orders ?? 0}</td> <td className="px-4 py-2 text-[#ccc]">{c.active_orders ?? 0}</td>
<td className="px-4 py-2 text-green-400">{fmt(c.total_revenue ?? 0)}</td> <td className="px-4 py-2 text-green-400">{fmt(c.total_revenue ?? 0)}</td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${c.status === 'active' ? 'bg-green-400/10 text-green-400' : 'bg-[#231d18] text-[#555]'}`}>{c.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${c.status === 'active' ? 'bg-green-400/10 text-green-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{c.status}</span>
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<Link href={`/admin/accounting/clients/${c.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link> <Link href={`/admin/accounting/clients/${c.id}`} className="text-xs text-[#1D4ED8] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -41,7 +41,7 @@ export default function CommissionsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -53,10 +53,10 @@ export default function CommissionsPage() {
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<select value={year} onChange={e => setYear(Number(e.target.value))} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={year} onChange={e => setYear(Number(e.target.value))} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
{[2024, 2025, 2026, 2027].map(y => <option key={y} value={y}>{y}</option>)} {[2024, 2025, 2026, 2027].map(y => <option key={y} value={y}>{y}</option>)}
</select> </select>
<select value={filterStaff} onChange={e => setFilterStaff(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterStaff} onChange={e => setFilterStaff(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Staff</option> <option value="">All Staff</option>
{staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)} {staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)}
</select> </select>
@@ -68,7 +68,7 @@ export default function CommissionsPage() {
{summary.map((s: any) => { {summary.map((s: any) => {
const pct = s.next_threshold_cents ? Math.min(100, Math.round((s.ytd_sales_cents / s.next_threshold_cents) * 100)) : 100; const pct = s.next_threshold_cents ? Math.min(100, Math.round((s.ytd_sales_cents / s.next_threshold_cents) * 100)) : 100;
return ( return (
<div key={s.staff_id} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-2"> <div key={s.staff_id} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<p className="text-sm font-semibold text-white">{s.full_name}</p> <p className="text-sm font-semibold text-white">{s.full_name}</p>
<span className="text-xs text-[#888]">{s.current_tier}</span> <span className="text-xs text-[#888]">{s.current_tier}</span>
@@ -83,8 +83,8 @@ export default function CommissionsPage() {
<div className="flex justify-between text-xs text-[#555] mb-1"> <div className="flex justify-between text-xs text-[#555] mb-1">
<span>Tier progress</span><span>{pct}%</span> <span>Tier progress</span><span>{pct}%</span>
</div> </div>
<div className="h-1.5 bg-[#231d18] rounded-full overflow-hidden"> <div className="h-1.5 bg-[#FFFFFF] rounded-full overflow-hidden">
<div className="h-full bg-[#F0A623] rounded-full transition-all" style={{ width: `${pct}%` }} /> <div className="h-full bg-[#1D4ED8] rounded-full transition-all" style={{ width: `${pct}%` }} />
</div> </div>
</div> </div>
)} )}
@@ -94,10 +94,10 @@ export default function CommissionsPage() {
</div> </div>
{/* Detail table */} {/* Detail table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Staff','Invoice','Sale Amount','Rate','Commission','YTD at Time','Paid',''].map(h => ( {['Staff','Invoice','Sale Amount','Rate','Commission','YTD at Time','Paid',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -109,9 +109,9 @@ export default function CommissionsPage() {
) : commissions.length === 0 ? ( ) : commissions.length === 0 ? (
<tr><td colSpan={8} className="px-4 py-8 text-center text-[#555] text-xs">No commission records</td></tr> <tr><td colSpan={8} className="px-4 py-8 text-center text-[#555] text-xs">No commission records</td></tr>
) : commissions.map((c: any) => ( ) : commissions.map((c: any) => (
<tr key={c.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={c.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-white text-xs">{c.rmp_sales_staff?.full_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{c.rmp_sales_staff?.full_name ?? '—'}</td>
<td className="px-3 py-2 text-[#F0A623] text-xs font-mono">{c.rmp_invoices?.invoice_number ?? '—'}</td> <td className="px-3 py-2 text-[#1D4ED8] text-xs font-mono">{c.rmp_invoices?.invoice_number ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{fmt(c.sale_amount_cents ?? 0)}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{fmt(c.sale_amount_cents ?? 0)}</td>
<td className="px-3 py-2 text-[#888] text-xs">{((c.commission_rate ?? 0) * 100).toFixed(1)}%</td> <td className="px-3 py-2 text-[#888] text-xs">{((c.commission_rate ?? 0) * 100).toFixed(1)}%</td>
<td className="px-3 py-2 text-green-400 text-xs">{fmt(c.commission_amount_cents ?? 0)}</td> <td className="px-3 py-2 text-green-400 text-xs">{fmt(c.commission_amount_cents ?? 0)}</td>

View File

@@ -43,7 +43,7 @@ export default function DocumentsPage() {
const years = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i); const years = Array.from({ length: 5 }, (_, i) => new Date().getFullYear() - i);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -54,70 +54,70 @@ export default function DocumentsPage() {
<h1 className="text-xl font-bold">Document Vault</h1> <h1 className="text-xl font-bold">Document Vault</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Upload Document</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ Upload Document</button>
</div> </div>
{/* Year tabs */} {/* Year tabs */}
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
<button onClick={() => setFilterYear('')} className={`px-3 py-1 rounded text-xs ${!filterYear ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888]'}`}>All Years</button> <button onClick={() => setFilterYear('')} className={`px-3 py-1 rounded text-xs ${!filterYear ? 'bg-[#1D4ED8] text-white' : 'bg-[#111] border border-[#DCE6F2] text-[#888]'}`}>All Years</button>
{years.map(y => ( {years.map(y => (
<button key={y} onClick={() => setFilterYear(String(y))} className={`px-3 py-1 rounded text-xs ${filterYear === String(y) ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888]'}`}>{y}</button> <button key={y} onClick={() => setFilterYear(String(y))} className={`px-3 py-1 rounded text-xs ${filterYear === String(y) ? 'bg-[#1D4ED8] text-white' : 'bg-[#111] border border-[#DCE6F2] text-[#888]'}`}>{y}</button>
))} ))}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<select value={filterType} onChange={e => setFilterType(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterType} onChange={e => setFilterType(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Types</option> <option value="">All Types</option>
{DOC_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)} {DOC_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
</select> </select>
</div> </div>
{showForm && ( {showForm && (
<form onSubmit={handleCreate} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">Upload Document</h3> <h3 className="text-sm font-semibold">Upload Document</h3>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
<select value={form.document_type} onChange={e => setForm(p => ({ ...p, document_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={form.document_type} onChange={e => setForm(p => ({ ...p, document_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
{DOC_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)} {DOC_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Title *</label> <label className="text-xs text-[#888] block mb-1">Title *</label>
<input required value={form.title} onChange={e => setForm(p => ({ ...p, title: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required value={form.title} onChange={e => setForm(p => ({ ...p, title: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
{form.document_type === 'sales_agreement' && ( {form.document_type === 'sales_agreement' && (
<> <>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Person *</label> <label className="text-xs text-[#888] block mb-1">Person *</label>
<input required value={form.person} onChange={e => setForm(p => ({ ...p, person: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required value={form.person} onChange={e => setForm(p => ({ ...p, person: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Year *</label> <label className="text-xs text-[#888] block mb-1">Year *</label>
<input type="number" required value={form.year} onChange={e => setForm(p => ({ ...p, year: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input type="number" required value={form.year} onChange={e => setForm(p => ({ ...p, year: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</> </>
)} )}
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">File URL</label> <label className="text-xs text-[#888] block mb-1">File URL</label>
<input value={form.file_url} onChange={e => setForm(p => ({ ...p, file_url: e.target.value }))} placeholder="https://…" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.file_url} onChange={e => setForm(p => ({ ...p, file_url: e.target.value }))} placeholder="https://…" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Save Document'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Save Document'}</button>
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Type','Title','Person','Year','Date',''].map(h => ( {['Type','Title','Person','Year','Date',''].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -129,14 +129,14 @@ export default function DocumentsPage() {
) : docs.length === 0 ? ( ) : docs.length === 0 ? (
<tr><td colSpan={6} className="px-4 py-8 text-center text-[#555] text-xs">No documents found</td></tr> <tr><td colSpan={6} className="px-4 py-8 text-center text-[#555] text-xs">No documents found</td></tr>
) : docs.map((d: any) => ( ) : docs.map((d: any) => (
<tr key={d.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={d.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-4 py-2 text-[#888] text-xs capitalize">{d.document_type?.replace(/_/g, ' ')}</td> <td className="px-4 py-2 text-[#888] text-xs capitalize">{d.document_type?.replace(/_/g, ' ')}</td>
<td className="px-4 py-2 text-white text-xs">{d.title}</td> <td className="px-4 py-2 text-white text-xs">{d.title}</td>
<td className="px-4 py-2 text-[#ccc] text-xs">{d.person ?? '—'}</td> <td className="px-4 py-2 text-[#ccc] text-xs">{d.person ?? '—'}</td>
<td className="px-4 py-2 text-[#888] text-xs">{d.year ?? '—'}</td> <td className="px-4 py-2 text-[#888] text-xs">{d.year ?? '—'}</td>
<td className="px-4 py-2 text-[#555] text-xs">{d.uploaded_at ? new Date(d.uploaded_at).toLocaleDateString() : '—'}</td> <td className="px-4 py-2 text-[#555] text-xs">{d.uploaded_at ? new Date(d.uploaded_at).toLocaleDateString() : '—'}</td>
<td className="px-4 py-2 flex gap-2"> <td className="px-4 py-2 flex gap-2">
{d.file_url && <a href={d.file_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#F0A623] hover:underline">Download</a>} {d.file_url && <a href={d.file_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#1D4ED8] hover:underline">Download</a>}
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -84,7 +84,7 @@ export default function ExpensesPage() {
setForm({ vendor: '', amount: '', category: 'other', description: '', expense_date: new Date().toISOString().split('T')[0], receipt_source: 'manual' }); setForm({ vendor: '', amount: '', category: 'other', description: '', expense_date: new Date().toISOString().split('T')[0], receipt_source: 'manual' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -95,7 +95,7 @@ export default function ExpensesPage() {
<h1 className="text-xl font-bold">Expenses</h1> <h1 className="text-xl font-bold">Expenses</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Log Expense</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ Log Expense</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -107,11 +107,11 @@ export default function ExpensesPage() {
)} )}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<select value={filterCat} onChange={e => setFilterCat(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterCat} onChange={e => setFilterCat(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Categories</option> <option value="">All Categories</option>
{CATEGORIES.map(c => <option key={c} value={c}>{c.replace(/_/g, ' ')}</option>)} {CATEGORIES.map(c => <option key={c} value={c}>{c.replace(/_/g, ' ')}</option>)}
</select> </select>
<select value={filterReconciled} onChange={e => setFilterReconciled(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterReconciled} onChange={e => setFilterReconciled(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All</option> <option value="">All</option>
<option value="true">Reconciled</option> <option value="true">Reconciled</option>
<option value="false">Unreconciled</option> <option value="false">Unreconciled</option>
@@ -119,7 +119,7 @@ export default function ExpensesPage() {
</div> </div>
{showForm && ( {showForm && (
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">Log Expense</h3> <h3 className="text-sm font-semibold">Log Expense</h3>
{errors._form && ( {errors._form && (
@@ -132,40 +132,40 @@ export default function ExpensesPage() {
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Vendor</label> <label className="text-xs text-[#888] block mb-1">Vendor</label>
<input type="text" value={form.vendor} onChange={e => handleChange('vendor', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input type="text" value={form.vendor} onChange={e => handleChange('vendor', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Amount ($) <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Amount ($) <span className="text-red-400">*</span></label>
<input type="number" step="0.01" value={form.amount} onChange={e => handleChange('amount', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.amount ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input type="number" step="0.01" value={form.amount} onChange={e => handleChange('amount', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors.amount ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{errors.amount && <p className="mt-1 text-xs text-red-400">{errors.amount}</p>} {errors.amount && <p className="mt-1 text-xs text-red-400">{errors.amount}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Date <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Date <span className="text-red-400">*</span></label>
<input type="date" value={form.expense_date} onChange={e => handleChange('expense_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.expense_date ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input type="date" value={form.expense_date} onChange={e => handleChange('expense_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors.expense_date ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{errors.expense_date && <p className="mt-1 text-xs text-red-400">{errors.expense_date}</p>} {errors.expense_date && <p className="mt-1 text-xs text-red-400">{errors.expense_date}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Category</label> <label className="text-xs text-[#888] block mb-1">Category</label>
<select value={form.category} onChange={e => handleChange('category', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={form.category} onChange={e => handleChange('category', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
{CATEGORIES.map(c => <option key={c} value={c}>{c.replace(/_/g, ' ')}</option>)} {CATEGORIES.map(c => <option key={c} value={c}>{c.replace(/_/g, ' ')}</option>)}
</select> </select>
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Description</label> <label className="text-xs text-[#888] block mb-1">Description</label>
<input value={form.description} onChange={e => handleChange('description', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.description} onChange={e => handleChange('description', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Log Expense'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Log Expense'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Date','Vendor','Amount','Category','Source','Reconciled','Description'].map(h => ( {['Date','Vendor','Amount','Category','Source','Reconciled','Description'].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -177,7 +177,7 @@ export default function ExpensesPage() {
) : expenses.length === 0 ? ( ) : expenses.length === 0 ? (
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No expenses found</td></tr> <tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No expenses found</td></tr>
) : expenses.map((ex: any) => ( ) : expenses.map((ex: any) => (
<tr key={ex.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={ex.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-[#888] text-xs">{ex.expense_date}</td> <td className="px-3 py-2 text-[#888] text-xs">{ex.expense_date}</td>
<td className="px-3 py-2 text-white text-xs">{ex.vendor ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{ex.vendor ?? '—'}</td>
<td className="px-3 py-2 text-red-400 text-xs">{ex.amount_cents ? fmt(ex.amount_cents) : '—'}</td> <td className="px-3 py-2 text-red-400 text-xs">{ex.amount_cents ? fmt(ex.amount_cents) : '—'}</td>

View File

@@ -10,7 +10,7 @@ const STATUS_COLORS: Record<string, string> = {
paid: 'bg-green-400/10 text-green-400', paid: 'bg-green-400/10 text-green-400',
pending: 'bg-yellow-400/10 text-yellow-400', pending: 'bg-yellow-400/10 text-yellow-400',
overdue: 'bg-red-400/10 text-red-400', overdue: 'bg-red-400/10 text-red-400',
cancelled: 'bg-[#231d18] text-[#555]', cancelled: 'bg-[#FFFFFF] text-[#555]',
}; };
function InvoicesContent() { function InvoicesContent() {
@@ -83,7 +83,7 @@ function InvoicesContent() {
setPayForm({ method: 'check', paid_date: new Date().toISOString().split('T')[0], wire_reference: '' }); setPayForm({ method: 'check', paid_date: new Date().toISOString().split('T')[0], wire_reference: '' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -94,7 +94,7 @@ function InvoicesContent() {
<h1 className="text-xl font-bold">Invoices</h1> <h1 className="text-xl font-bold">Invoices</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ New Invoice (via Order)</Link> <Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ New Invoice (via Order)</Link>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -108,14 +108,14 @@ function InvoicesContent() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
{['','pending','paid','overdue','cancelled'].map(s => ( {['','pending','paid','overdue','cancelled'].map(s => (
<button key={s} onClick={() => setFilterStatus(s)} className={`px-3 py-1 rounded text-xs capitalize ${filterStatus === s ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888] hover:text-white'}`}>{s || 'All'}</button> <button key={s} onClick={() => setFilterStatus(s)} className={`px-3 py-1 rounded text-xs capitalize ${filterStatus === s ? 'bg-[#1D4ED8] text-white' : 'bg-[#111] border border-[#DCE6F2] text-[#888] hover:text-white'}`}>{s || 'All'}</button>
))} ))}
</div> </div>
{/* Mark Paid Modal */} {/* Mark Paid Modal */}
{markPaidId && ( {markPaidId && (
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
<form onSubmit={handleMarkPaid} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-5 w-80 space-y-3"> <form onSubmit={handleMarkPaid} noValidate className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 w-80 space-y-3">
<h3 className="text-sm font-semibold">Mark Invoice as Paid</h3> <h3 className="text-sm font-semibold">Mark Invoice as Paid</h3>
{payErrors._form && ( {payErrors._form && (
@@ -127,35 +127,35 @@ function InvoicesContent() {
<div> <div>
<label className="text-xs text-[#888] block mb-1">Payment Method</label> <label className="text-xs text-[#888] block mb-1">Payment Method</label>
<select value={payForm.method} onChange={e => handlePayChange('method', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={payForm.method} onChange={e => handlePayChange('method', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
{['stripe','wire','check','other'].map(m => <option key={m} value={m}>{m}</option>)} {['stripe','wire','check','other'].map(m => <option key={m} value={m}>{m}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Paid Date <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Paid Date <span className="text-red-400">*</span></label>
<input type="date" value={payForm.paid_date} onChange={e => handlePayChange('paid_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.paid_date ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input type="date" value={payForm.paid_date} onChange={e => handlePayChange('paid_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.paid_date ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{payErrors.paid_date && <p className="mt-1 text-xs text-red-400">{payErrors.paid_date}</p>} {payErrors.paid_date && <p className="mt-1 text-xs text-red-400">{payErrors.paid_date}</p>}
</div> </div>
{payForm.method === 'wire' && ( {payForm.method === 'wire' && (
<div> <div>
<label className="text-xs text-[#888] block mb-1">Wire Reference <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Wire Reference <span className="text-red-400">*</span></label>
<input value={payForm.wire_reference} onChange={e => handlePayChange('wire_reference', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.wire_reference ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input value={payForm.wire_reference} onChange={e => handlePayChange('wire_reference', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${payErrors.wire_reference ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{payErrors.wire_reference && <p className="mt-1 text-xs text-red-400">{payErrors.wire_reference}</p>} {payErrors.wire_reference && <p className="mt-1 text-xs text-red-400">{payErrors.wire_reference}</p>}
</div> </div>
)} )}
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-green-600 rounded text-xs font-medium hover:bg-green-500 disabled:opacity-50">{saving ? 'Saving…' : 'Mark Paid'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-green-600 rounded text-xs font-medium hover:bg-green-500 disabled:opacity-50">{saving ? 'Saving…' : 'Mark Paid'}</button>
<button type="button" onClick={handleCloseModal} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCloseModal} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
</div> </div>
)} )}
{/* Table */} {/* Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Invoice#','Client','Site','Order#','Amount','Status','Due Date','Salesperson',''].map(h => ( {['Invoice#','Client','Site','Order#','Amount','Status','Due Date','Salesperson',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
))} ))}
@@ -167,14 +167,14 @@ function InvoicesContent() {
) : invoices.length === 0 ? ( ) : invoices.length === 0 ? (
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No invoices found</td></tr> <tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No invoices found</td></tr>
) : invoices.map((inv: any) => ( ) : invoices.map((inv: any) => (
<tr key={inv.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={inv.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-[#F0A623] font-mono text-xs">{inv.invoice_number}</td> <td className="px-3 py-2 text-[#1D4ED8] font-mono text-xs">{inv.invoice_number}</td>
<td className="px-3 py-2 text-white text-xs">{inv.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{inv.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{inv.site}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{inv.site}</td>
<td className="px-3 py-2 text-[#888] text-xs font-mono">{inv.rmp_orders?.internal_order_number ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs font-mono">{inv.rmp_orders?.internal_order_number ?? '—'}</td>
<td className="px-3 py-2 text-green-400 text-xs">{inv.amount_cents ? fmt(inv.amount_cents) : '—'}</td> <td className="px-3 py-2 text-green-400 text-xs">{inv.amount_cents ? fmt(inv.amount_cents) : '—'}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${STATUS_COLORS[inv.status] ?? 'bg-[#231d18] text-[#555]'}`}>{inv.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${STATUS_COLORS[inv.status] ?? 'bg-[#FFFFFF] text-[#555]'}`}>{inv.status}</span>
</td> </td>
<td className="px-3 py-2 text-[#888] text-xs">{inv.due_date ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{inv.due_date ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{inv.rmp_sales_staff?.full_name ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{inv.rmp_sales_staff?.full_name ?? '—'}</td>
@@ -195,7 +195,7 @@ function InvoicesContent() {
export default function InvoicesPage() { export default function InvoicesPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>}> <Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>}>
<InvoicesContent /> <InvoicesContent />
</Suspense> </Suspense>
); );

View File

@@ -104,7 +104,7 @@ function OrdersContent() {
setForm({ client_id: '', site: '', product_id: '', ad_unit: '', description: '', start_date: '', end_date: '', total_amount_cents: '', currency: 'USD', invoicing_schedule: 'single', next_invoice_date: '', staff_id: '', po_number: '', notes: '' }); setForm({ client_id: '', site: '', product_id: '', ad_unit: '', description: '', start_date: '', end_date: '', total_amount_cents: '', currency: 'USD', invoicing_schedule: 'single', next_invoice_date: '', staff_id: '', po_number: '', notes: '' });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -115,7 +115,7 @@ function OrdersContent() {
<h1 className="text-xl font-bold">Orders</h1> <h1 className="text-xl font-bold">Orders</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ New Order</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ New Order</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -128,20 +128,20 @@ function OrdersContent() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Statuses</option> <option value="">All Statuses</option>
{STATUSES.map(s => <option key={s} value={s}>{s}</option>)} {STATUSES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<input value={filterClient} onChange={e => setFilterClient(e.target.value)} placeholder="Filter by client…" className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#F0A623]" /> <input value={filterClient} onChange={e => setFilterClient(e.target.value)} placeholder="Filter by client…" className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
{/* New Order Form */} {/* New Order Form */}
{showForm && ( {showForm && (
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">New Order</h3> <h3 className="text-sm font-semibold">New Order</h3>
{errors._form && ( {errors._form && (
@@ -154,7 +154,7 @@ function OrdersContent() {
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Client <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Client <span className="text-red-400">*</span></label>
<select value={form.client_id} onChange={e => handleChange('client_id', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.client_id ? 'border-red-500/60' : 'border-[#3a322b]'}`}> <select value={form.client_id} onChange={e => handleChange('client_id', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors.client_id ? 'border-red-500/60' : 'border-[#DCE6F2]'}`}>
<option value="">Select client</option> <option value="">Select client</option>
{clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)} {clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)}
</select> </select>
@@ -162,7 +162,7 @@ function OrdersContent() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Site <span className="text-red-400">*</span></label> <label className="text-xs text-[#888] block mb-1">Site <span className="text-red-400">*</span></label>
<select value={form.site} onChange={e => handleChange('site', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.site ? 'border-red-500/60' : 'border-[#3a322b]'}`}> <select value={form.site} onChange={e => handleChange('site', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors.site ? 'border-red-500/60' : 'border-[#DCE6F2]'}`}>
<option value="">Select site</option> <option value="">Select site</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
@@ -170,67 +170,67 @@ function OrdersContent() {
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Product</label> <label className="text-xs text-[#888] block mb-1">Product</label>
<select value={form.product_id} onChange={e => handleChange('product_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]"> <select value={form.product_id} onChange={e => handleChange('product_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]">
<option value="">Select product</option> <option value="">Select product</option>
{products.map((p: any) => <option key={p.id} value={p.id}>{p.name}</option>)} {products.map((p: any) => <option key={p.id} value={p.id}>{p.name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Ad Unit</label> <label className="text-xs text-[#888] block mb-1">Ad Unit</label>
<input value={form.ad_unit} onChange={e => handleChange('ad_unit', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.ad_unit} onChange={e => handleChange('ad_unit', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Start Date</label> <label className="text-xs text-[#888] block mb-1">Start Date</label>
<input type="date" value={form.start_date} onChange={e => handleChange('start_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input type="date" value={form.start_date} onChange={e => handleChange('start_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">End Date</label> <label className="text-xs text-[#888] block mb-1">End Date</label>
<input type="date" value={form.end_date} onChange={e => handleChange('end_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.end_date ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input type="date" value={form.end_date} onChange={e => handleChange('end_date', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors.end_date ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{errors.end_date && <p className="mt-1 text-xs text-red-400">{errors.end_date}</p>} {errors.end_date && <p className="mt-1 text-xs text-red-400">{errors.end_date}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Total Amount ($)</label> <label className="text-xs text-[#888] block mb-1">Total Amount ($)</label>
<input type="number" step="0.01" value={form.total_amount_cents} onChange={e => handleChange('total_amount_cents', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors.total_amount_cents ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input type="number" step="0.01" value={form.total_amount_cents} onChange={e => handleChange('total_amount_cents', e.target.value)} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors.total_amount_cents ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{errors.total_amount_cents && <p className="mt-1 text-xs text-red-400">{errors.total_amount_cents}</p>} {errors.total_amount_cents && <p className="mt-1 text-xs text-red-400">{errors.total_amount_cents}</p>}
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Invoicing Schedule</label> <label className="text-xs text-[#888] block mb-1">Invoicing Schedule</label>
<select value={form.invoicing_schedule} onChange={e => handleChange('invoicing_schedule', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]"> <select value={form.invoicing_schedule} onChange={e => handleChange('invoicing_schedule', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]">
{SCHEDULES.map(s => <option key={s} value={s}>{s}</option>)} {SCHEDULES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Next Invoice Date</label> <label className="text-xs text-[#888] block mb-1">Next Invoice Date</label>
<input type="date" value={form.next_invoice_date} onChange={e => handleChange('next_invoice_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input type="date" value={form.next_invoice_date} onChange={e => handleChange('next_invoice_date', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Salesperson</label> <label className="text-xs text-[#888] block mb-1">Salesperson</label>
<select value={form.staff_id} onChange={e => handleChange('staff_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]"> <select value={form.staff_id} onChange={e => handleChange('staff_id', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]">
<option value="">None</option> <option value="">None</option>
{staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)} {staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">PO Number</label> <label className="text-xs text-[#888] block mb-1">PO Number</label>
<input value={form.po_number} onChange={e => handleChange('po_number', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.po_number} onChange={e => handleChange('po_number', e.target.value)} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div className="col-span-2 md:col-span-3"> <div className="col-span-2 md:col-span-3">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <textarea value={form.notes} onChange={e => handleChange('notes', e.target.value)} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Order'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create Order'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
{/* Table */} {/* Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Client','PO#','Order#','Site','Product','Ad Unit','Dates','Amount','Status',''].map(h => ( {['Client','PO#','Order#','Site','Product','Ad Unit','Dates','Amount','Status',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
))} ))}
@@ -242,20 +242,20 @@ function OrdersContent() {
) : orders.length === 0 ? ( ) : orders.length === 0 ? (
<tr><td colSpan={10} className="px-4 py-8 text-center text-[#555] text-xs">No orders found</td></tr> <tr><td colSpan={10} className="px-4 py-8 text-center text-[#555] text-xs">No orders found</td></tr>
) : orders.map((o: any) => ( ) : orders.map((o: any) => (
<tr key={o.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={o.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-white text-xs">{o.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{o.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{o.po_number ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{o.po_number ?? '—'}</td>
<td className="px-3 py-2 text-[#F0A623] text-xs font-mono">{o.internal_order_number}</td> <td className="px-3 py-2 text-[#1D4ED8] text-xs font-mono">{o.internal_order_number}</td>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{o.site}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{o.site}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{o.rmp_products?.name ?? o.ad_unit ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{o.rmp_products?.name ?? o.ad_unit ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{o.ad_unit ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{o.ad_unit ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs whitespace-nowrap">{o.start_date} {o.end_date ?? '∞'}</td> <td className="px-3 py-2 text-[#888] text-xs whitespace-nowrap">{o.start_date} {o.end_date ?? '∞'}</td>
<td className="px-3 py-2 text-green-400 text-xs">{o.total_amount_cents ? fmt(o.total_amount_cents) : '—'}</td> <td className="px-3 py-2 text-green-400 text-xs">{o.total_amount_cents ? fmt(o.total_amount_cents) : '—'}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${o.status === 'active' ? 'bg-green-400/10 text-green-400' : o.status === 'cancelled' ? 'bg-red-400/10 text-red-400' : 'bg-[#231d18] text-[#555]'}`}>{o.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${o.status === 'active' ? 'bg-green-400/10 text-green-400' : o.status === 'cancelled' ? 'bg-red-400/10 text-red-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{o.status}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<Link href={`/admin/accounting/orders/${o.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link> <Link href={`/admin/accounting/orders/${o.id}`} className="text-xs text-[#1D4ED8] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}
@@ -269,7 +269,7 @@ function OrdersContent() {
export default function OrdersPage() { export default function OrdersPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>}> <Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>}>
<OrdersContent /> <OrdersContent />
</Suspense> </Suspense>
); );

View File

@@ -44,7 +44,7 @@ export default function AccountingDashboard() {
if (loading || loadingData) return ( if (loading || loadingData) return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
if (!user) return null; if (!user) return null;
@@ -59,18 +59,18 @@ export default function AccountingDashboard() {
<p className="text-[#888] text-sm mt-1">Relevant Media Properties, LLC</p> <p className="text-[#888] text-sm mt-1">Relevant Media Properties, LLC</p>
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<Link href="/admin/accounting/clients" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link> <Link href="/admin/accounting/clients" className="px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] transition-colors">+ New Client</Link>
<Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link> <Link href="/admin/accounting/orders" className="px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] transition-colors">+ New Order</Link>
<Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link> <Link href="/admin/accounting/invoices" className="px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] transition-colors">+ New Invoice</Link>
<Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link> <Link href="/admin/accounting/expenses" className="px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] transition-colors">+ Log Expense</Link>
<Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors">Upload Bank CSV</Link> <Link href="/admin/accounting/reconciliation" className="px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] transition-colors">Upload Bank CSV</Link>
</div> </div>
</div> </div>
{/* Stat Cards */} {/* Stat Cards */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
{STAT_CARDS.map(c => ( {STAT_CARDS.map(c => (
<Link key={c.key} href={c.href} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 hover:border-[#333] transition-colors"> <Link key={c.key} href={c.href} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 hover:border-[#333] transition-colors">
<p className={`text-lg font-bold ${c.color}`}>{fmt(stats[c.key] ?? 0)}</p> <p className={`text-lg font-bold ${c.color}`}>{fmt(stats[c.key] ?? 0)}</p>
<p className="text-xs text-[#888] mt-1">{c.label}</p> <p className="text-xs text-[#888] mt-1">{c.label}</p>
</Link> </Link>
@@ -78,13 +78,13 @@ export default function AccountingDashboard() {
</div> </div>
{/* Site Revenue Table */} {/* Site Revenue Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b]"> <div className="px-4 py-3 border-b border-[#DCE6F2]">
<h2 className="text-sm font-semibold text-white">Revenue by Site</h2> <h2 className="text-sm font-semibold text-white">Revenue by Site</h2>
</div> </div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Site','Revenue MTD','Revenue YTD','Outstanding','Active Orders'].map(h => ( {['Site','Revenue MTD','Revenue YTD','Outstanding','Active Orders'].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -94,7 +94,7 @@ export default function AccountingDashboard() {
{siteRevenue.length === 0 ? ( {siteRevenue.length === 0 ? (
<tr><td colSpan={5} className="px-4 py-6 text-center text-[#555] text-xs">No data yet</td></tr> <tr><td colSpan={5} className="px-4 py-6 text-center text-[#555] text-xs">No data yet</td></tr>
) : siteRevenue.map((row: any) => ( ) : siteRevenue.map((row: any) => (
<tr key={row.site} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={row.site} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-4 py-2 text-white font-medium capitalize">{row.site}</td> <td className="px-4 py-2 text-white font-medium capitalize">{row.site}</td>
<td className="px-4 py-2 text-green-400">{fmt(row.revenue_mtd ?? 0)}</td> <td className="px-4 py-2 text-green-400">{fmt(row.revenue_mtd ?? 0)}</td>
<td className="px-4 py-2 text-green-400">{fmt(row.revenue_ytd ?? 0)}</td> <td className="px-4 py-2 text-green-400">{fmt(row.revenue_ytd ?? 0)}</td>
@@ -107,13 +107,13 @@ export default function AccountingDashboard() {
</div> </div>
{/* Top Clients */} {/* Top Clients */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b]"> <div className="px-4 py-3 border-b border-[#DCE6F2]">
<h2 className="text-sm font-semibold text-white">Top 5 Clients YTD</h2> <h2 className="text-sm font-semibold text-white">Top 5 Clients YTD</h2>
</div> </div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Client','Revenue YTD','Active Orders'].map(h => ( {['Client','Revenue YTD','Active Orders'].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -123,7 +123,7 @@ export default function AccountingDashboard() {
{topClients.length === 0 ? ( {topClients.length === 0 ? (
<tr><td colSpan={3} className="px-4 py-6 text-center text-[#555] text-xs">No data yet</td></tr> <tr><td colSpan={3} className="px-4 py-6 text-center text-[#555] text-xs">No data yet</td></tr>
) : topClients.map((c: any) => ( ) : topClients.map((c: any) => (
<tr key={c.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={c.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-4 py-2 text-white">{c.company_name}</td> <td className="px-4 py-2 text-white">{c.company_name}</td>
<td className="px-4 py-2 text-green-400">{fmt(c.revenue_ytd ?? 0)}</td> <td className="px-4 py-2 text-green-400">{fmt(c.revenue_ytd ?? 0)}</td>
<td className="px-4 py-2 text-[#ccc]">{c.active_orders ?? 0}</td> <td className="px-4 py-2 text-[#ccc]">{c.active_orders ?? 0}</td>
@@ -146,7 +146,7 @@ export default function AccountingDashboard() {
{ label: 'Reports', href: '/admin/accounting/reports' }, { label: 'Reports', href: '/admin/accounting/reports' },
{ label: 'Documents', href: '/admin/accounting/documents' }, { label: 'Documents', href: '/admin/accounting/documents' },
].map(l => ( ].map(l => (
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#3a322b] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#F0A623] transition-colors"> <Link key={l.href} href={l.href} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#1D4ED8] transition-colors">
{l.label} {l.label}
</Link> </Link>
))} ))}

View File

@@ -48,7 +48,7 @@ export default function ReconciliationPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -59,7 +59,7 @@ export default function ReconciliationPage() {
<h1 className="text-xl font-bold">Bank Reconciliation</h1> <h1 className="text-xl font-bold">Bank Reconciliation</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<label className={`px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 cursor-pointer ${uploading ? 'opacity-50' : ''}`}> <label className={`px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 cursor-pointer ${uploading ? 'opacity-50' : ''}`}>
{uploading ? 'Uploading…' : 'Upload Bank CSV'} {uploading ? 'Uploading…' : 'Upload Bank CSV'}
<input type="file" accept=".csv" className="hidden" onChange={handleUpload} disabled={uploading} /> <input type="file" accept=".csv" className="hidden" onChange={handleUpload} disabled={uploading} />
</label> </label>
@@ -78,10 +78,10 @@ export default function ReconciliationPage() {
</div> </div>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Date','Description','Amount','Type','Matched To','Reconciled','Action'].map(h => ( {['Date','Description','Amount','Type','Matched To','Reconciled','Action'].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -93,7 +93,7 @@ export default function ReconciliationPage() {
) : transactions.length === 0 ? ( ) : transactions.length === 0 ? (
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No transactions. Upload a CSV to begin.</td></tr> <tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No transactions. Upload a CSV to begin.</td></tr>
) : transactions.map((tx: any) => ( ) : transactions.map((tx: any) => (
<tr key={tx.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={tx.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-[#888] text-xs">{tx.transaction_date}</td> <td className="px-3 py-2 text-[#888] text-xs">{tx.transaction_date}</td>
<td className="px-3 py-2 text-white text-xs max-w-xs truncate">{tx.description}</td> <td className="px-3 py-2 text-white text-xs max-w-xs truncate">{tx.description}</td>
<td className={`px-3 py-2 text-xs font-medium ${tx.type === 'credit' ? 'text-green-400' : 'text-red-400'}`}>{fmt(Math.abs(tx.amount_cents ?? 0))}</td> <td className={`px-3 py-2 text-xs font-medium ${tx.type === 'credit' ? 'text-green-400' : 'text-red-400'}`}>{fmt(Math.abs(tx.amount_cents ?? 0))}</td>

View File

@@ -28,7 +28,7 @@ export default function ReportsPage() {
useEffect(() => { if (user) loadReport(); }, [user]); useEffect(() => { if (user) loadReport(); }, [user]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -40,41 +40,41 @@ export default function ReportsPage() {
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <input type="date" value={startDate} onChange={e => setStartDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
<span className="text-[#555] text-xs">to</span> <span className="text-[#555] text-xs">to</span>
<input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <input type="date" value={endDate} onChange={e => setEndDate(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
<button onClick={loadReport} disabled={loadingData} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Run Report</button> <button onClick={loadReport} disabled={loadingData} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Run Report</button>
</div> </div>
</div> </div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div> <div className="flex items-center justify-center py-20"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>
) : report ? ( ) : report ? (
<div className="space-y-4"> <div className="space-y-4">
{/* P&L Summary */} {/* P&L Summary */}
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-3 gap-3">
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<p className="text-xs text-[#555]">Total Revenue</p> <p className="text-xs text-[#555]">Total Revenue</p>
<p className="text-2xl font-bold text-green-400 mt-1">{fmt(report.total_revenue_cents ?? 0)}</p> <p className="text-2xl font-bold text-green-400 mt-1">{fmt(report.total_revenue_cents ?? 0)}</p>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<p className="text-xs text-[#555]">Total Expenses</p> <p className="text-xs text-[#555]">Total Expenses</p>
<p className="text-2xl font-bold text-red-400 mt-1">{fmt(report.total_expenses_cents ?? 0)}</p> <p className="text-2xl font-bold text-red-400 mt-1">{fmt(report.total_expenses_cents ?? 0)}</p>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<p className="text-xs text-[#555]">Net Profit</p> <p className="text-xs text-[#555]">Net Profit</p>
<p className={`text-2xl font-bold mt-1 ${(report.net_profit_cents ?? 0) >= 0 ? 'text-blue-400' : 'text-red-400'}`}>{fmt(report.net_profit_cents ?? 0)}</p> <p className={`text-2xl font-bold mt-1 ${(report.net_profit_cents ?? 0) >= 0 ? 'text-blue-400' : 'text-red-400'}`}>{fmt(report.net_profit_cents ?? 0)}</p>
</div> </div>
</div> </div>
{/* Revenue by Site */} {/* Revenue by Site */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b]"><h2 className="text-sm font-semibold">Revenue by Site</h2></div> <div className="px-4 py-3 border-b border-[#DCE6F2]"><h2 className="text-sm font-semibold">Revenue by Site</h2></div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead><tr className="border-b border-[#231d18]">{['Site','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead> <thead><tr className="border-b border-[#FFFFFF]">{['Site','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
<tbody> <tbody>
{(report.by_site ?? []).map((r: any) => ( {(report.by_site ?? []).map((r: any) => (
<tr key={r.site} className="border-b border-[#231d18]"> <tr key={r.site} className="border-b border-[#FFFFFF]">
<td className="px-4 py-2 text-white text-xs capitalize">{r.site}</td> <td className="px-4 py-2 text-white text-xs capitalize">{r.site}</td>
<td className="px-4 py-2 text-green-400 text-xs">{fmt(r.revenue_cents ?? 0)}</td> <td className="px-4 py-2 text-green-400 text-xs">{fmt(r.revenue_cents ?? 0)}</td>
</tr> </tr>
@@ -84,13 +84,13 @@ export default function ReportsPage() {
</div> </div>
{/* Top Clients */} {/* Top Clients */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b]"><h2 className="text-sm font-semibold">Top 10 Clients</h2></div> <div className="px-4 py-3 border-b border-[#DCE6F2]"><h2 className="text-sm font-semibold">Top 10 Clients</h2></div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead><tr className="border-b border-[#231d18]">{['Client','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead> <thead><tr className="border-b border-[#FFFFFF]">{['Client','Revenue'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
<tbody> <tbody>
{(report.top_clients ?? []).map((c: any) => ( {(report.top_clients ?? []).map((c: any) => (
<tr key={c.id} className="border-b border-[#231d18]"> <tr key={c.id} className="border-b border-[#FFFFFF]">
<td className="px-4 py-2 text-white text-xs">{c.company_name}</td> <td className="px-4 py-2 text-white text-xs">{c.company_name}</td>
<td className="px-4 py-2 text-green-400 text-xs">{fmt(c.revenue_cents ?? 0)}</td> <td className="px-4 py-2 text-green-400 text-xs">{fmt(c.revenue_cents ?? 0)}</td>
</tr> </tr>
@@ -100,13 +100,13 @@ export default function ReportsPage() {
</div> </div>
{/* Expenses by Category */} {/* Expenses by Category */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b]"><h2 className="text-sm font-semibold">Expenses by Category</h2></div> <div className="px-4 py-3 border-b border-[#DCE6F2]"><h2 className="text-sm font-semibold">Expenses by Category</h2></div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead><tr className="border-b border-[#231d18]">{['Category','Amount'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead> <thead><tr className="border-b border-[#FFFFFF]">{['Category','Amount'].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
<tbody> <tbody>
{(report.by_category ?? []).map((c: any) => ( {(report.by_category ?? []).map((c: any) => (
<tr key={c.category} className="border-b border-[#231d18]"> <tr key={c.category} className="border-b border-[#FFFFFF]">
<td className="px-4 py-2 text-white text-xs">{c.category?.replace(/_/g, ' ')}</td> <td className="px-4 py-2 text-white text-xs">{c.category?.replace(/_/g, ' ')}</td>
<td className="px-4 py-2 text-red-400 text-xs">{fmt(c.amount_cents ?? 0)}</td> <td className="px-4 py-2 text-red-400 text-xs">{fmt(c.amount_cents ?? 0)}</td>
</tr> </tr>

View File

@@ -97,7 +97,7 @@ export default function StaffPage() {
if (errors[errKey]) setErrors(p => { const n = { ...p }; delete n[errKey]; return n; }); if (errors[errKey]) setErrors(p => { const n = { ...p }; delete n[errKey]; return n; });
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -108,7 +108,7 @@ export default function StaffPage() {
<h1 className="text-xl font-bold">Sales Staff</h1> <h1 className="text-xl font-bold">Sales Staff</h1>
<Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link> <Link href="/admin/accounting" className="text-xs text-[#555] hover:text-[#888]"> Accounting</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Add Salesperson</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ Add Salesperson</button>
</div> </div>
{/* Success Toast */} {/* Success Toast */}
@@ -120,7 +120,7 @@ export default function StaffPage() {
)} )}
{showForm && ( {showForm && (
<form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} noValidate className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">New Salesperson</h3> <h3 className="text-sm font-semibold">New Salesperson</h3>
{errors._form && ( {errors._form && (
@@ -140,7 +140,7 @@ export default function StaffPage() {
type={f.key === 'email' ? 'email' : 'text'} type={f.key === 'email' ? 'email' : 'text'}
value={(form as any)[f.key]} value={(form as any)[f.key]}
onChange={e => handleChange(f.key, e.target.value)} onChange={e => handleChange(f.key, e.target.value)}
className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#F0A623] ${errors[f.key] ? 'border-red-500/60' : 'border-[#3a322b]'}`} className={`w-full px-2 py-1.5 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8] ${errors[f.key] ? 'border-red-500/60' : 'border-[#DCE6F2]'}`}
/> />
{errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>} {errors[f.key] && <p className="mt-1 text-xs text-red-400">{errors[f.key]}</p>}
</div> </div>
@@ -149,34 +149,34 @@ export default function StaffPage() {
<div> <div>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<label className="text-xs text-[#888]">Commission Tiers ({new Date().getFullYear()})</label> <label className="text-xs text-[#888]">Commission Tiers ({new Date().getFullYear()})</label>
<button type="button" onClick={() => setTiers(t => [...t, { tier_order: t.length + 1, threshold_cents: '', rate: '' }])} className="text-xs text-[#F0A623] hover:underline">+ Add Tier</button> <button type="button" onClick={() => setTiers(t => [...t, { tier_order: t.length + 1, threshold_cents: '', rate: '' }])} className="text-xs text-[#1D4ED8] hover:underline">+ Add Tier</button>
</div> </div>
{tiers.map((tier, i) => ( {tiers.map((tier, i) => (
<div key={i} className="flex gap-2 mb-2 items-start"> <div key={i} className="flex gap-2 mb-2 items-start">
<span className="text-xs text-[#555] w-12 pt-5">Tier {tier.tier_order}</span> <span className="text-xs text-[#555] w-12 pt-5">Tier {tier.tier_order}</span>
<div className="flex-1"> <div className="flex-1">
<label className="text-xs text-[#555] block mb-0.5">Threshold ($, blank = no cap)</label> <label className="text-xs text-[#555] block mb-0.5">Threshold ($, blank = no cap)</label>
<input type="number" step="0.01" value={tier.threshold_cents} onChange={e => handleTierChange(i, 'threshold_cents', e.target.value)} placeholder="100000" className="w-full px-2 py-1 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <input type="number" step="0.01" value={tier.threshold_cents} onChange={e => handleTierChange(i, 'threshold_cents', e.target.value)} placeholder="100000" className="w-full px-2 py-1 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<label className="text-xs text-[#555] block mb-0.5">Rate (%) <span className="text-red-400">*</span></label> <label className="text-xs text-[#555] block mb-0.5">Rate (%) <span className="text-red-400">*</span></label>
<input type="number" step="0.1" value={tier.rate} onChange={e => handleTierChange(i, 'rate', e.target.value)} placeholder="25" className={`w-full px-2 py-1 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${errors[`tier_rate_${i}`] ? 'border-red-500/60' : 'border-[#3a322b]'}`} /> <input type="number" step="0.1" value={tier.rate} onChange={e => handleTierChange(i, 'rate', e.target.value)} placeholder="25" className={`w-full px-2 py-1 bg-[#0a0a0a] border rounded text-xs text-white focus:outline-none ${errors[`tier_rate_${i}`] ? 'border-red-500/60' : 'border-[#DCE6F2]'}`} />
{errors[`tier_rate_${i}`] && <p className="mt-1 text-xs text-red-400">{errors[`tier_rate_${i}`]}</p>} {errors[`tier_rate_${i}`] && <p className="mt-1 text-xs text-red-400">{errors[`tier_rate_${i}`]}</p>}
</div> </div>
</div> </div>
))} ))}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create'}</button>
<button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={handleCancel} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Name','Email','Status','Commission YTD','Outstanding',''].map(h => ( {['Name','Email','Status','Commission YTD','Outstanding',''].map(h => (
<th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-4 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -188,16 +188,16 @@ export default function StaffPage() {
) : staff.length === 0 ? ( ) : staff.length === 0 ? (
<tr><td colSpan={6} className="px-4 py-8 text-center text-[#555] text-xs">No staff found</td></tr> <tr><td colSpan={6} className="px-4 py-8 text-center text-[#555] text-xs">No staff found</td></tr>
) : staff.map((s: any) => ( ) : staff.map((s: any) => (
<tr key={s.id} className={`border-b border-[#231d18] hover:bg-[#231d18] ${s.status === 'terminated' ? 'opacity-50' : ''}`}> <tr key={s.id} className={`border-b border-[#FFFFFF] hover:bg-[#FFFFFF] ${s.status === 'terminated' ? 'opacity-50' : ''}`}>
<td className="px-4 py-2 text-white">{s.full_name}</td> <td className="px-4 py-2 text-white">{s.full_name}</td>
<td className="px-4 py-2 text-[#888] text-xs">{s.email}</td> <td className="px-4 py-2 text-[#888] text-xs">{s.email}</td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${s.status === 'active' ? 'bg-green-400/10 text-green-400' : s.status === 'terminated' ? 'bg-red-400/10 text-red-400' : 'bg-[#231d18] text-[#555]'}`}>{s.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${s.status === 'active' ? 'bg-green-400/10 text-green-400' : s.status === 'terminated' ? 'bg-red-400/10 text-red-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{s.status}</span>
</td> </td>
<td className="px-4 py-2 text-green-400 text-xs">{fmt(s.commission_ytd ?? 0)}</td> <td className="px-4 py-2 text-green-400 text-xs">{fmt(s.commission_ytd ?? 0)}</td>
<td className="px-4 py-2 text-yellow-400 text-xs">{fmt(s.commission_outstanding ?? 0)}</td> <td className="px-4 py-2 text-yellow-400 text-xs">{fmt(s.commission_outstanding ?? 0)}</td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<Link href={`/admin/accounting/staff/${s.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link> <Link href={`/admin/accounting/staff/${s.id}`} className="text-xs text-[#1D4ED8] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -9,7 +9,7 @@ const FLIGHT_COLORS: Record<string, string> = {
scheduled: 'bg-blue-400/10 text-blue-400', scheduled: 'bg-blue-400/10 text-blue-400',
paused: 'bg-gray-600/20 text-gray-400', paused: 'bg-gray-600/20 text-gray-400',
expired: 'bg-red-400/10 text-red-400', expired: 'bg-red-400/10 text-red-400',
cancelled: 'bg-[#231d18] text-[#555]', cancelled: 'bg-[#FFFFFF] text-[#555]',
}; };
export default function AdOpsIOsPage() { export default function AdOpsIOsPage() {
@@ -73,7 +73,7 @@ export default function AdOpsIOsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
const selectedProduct = products.find((p: any) => p.id === form.product_id); const selectedProduct = products.find((p: any) => p.id === form.product_id);
@@ -88,63 +88,63 @@ export default function AdOpsIOsPage() {
<h1 className="text-xl font-bold">Insertion Orders</h1> <h1 className="text-xl font-bold">Insertion Orders</h1>
<Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link> <Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ New IO Extension</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ New IO Extension</button>
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Statuses</option> <option value="">All Statuses</option>
{['scheduled','active','paused','expired','cancelled'].map(s => <option key={s} value={s}>{s}</option>)} {['scheduled','active','paused','expired','cancelled'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{['avbeat','broadcastbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)} {['avbeat','broadcastbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
{showForm && ( {showForm && (
<form onSubmit={handleCreate} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">New IO Extension</h3> <h3 className="text-sm font-semibold">New IO Extension</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Existing IO ID *</label> <label className="text-xs text-[#888] block mb-1">Existing IO ID *</label>
<input required value={form.existing_io_id} onChange={e => setForm(p => ({ ...p, existing_io_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required value={form.existing_io_id} onChange={e => setForm(p => ({ ...p, existing_io_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Client</label> <label className="text-xs text-[#888] block mb-1">Client</label>
<select value={form.client_id} onChange={e => setForm(p => ({ ...p, client_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={form.client_id} onChange={e => setForm(p => ({ ...p, client_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
<option value="">Select</option> <option value="">Select</option>
{clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)} {clients.map((c: any) => <option key={c.id} value={c.id}>{c.company_name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Salesperson</label> <label className="text-xs text-[#888] block mb-1">Salesperson</label>
<select value={form.staff_id} onChange={e => setForm(p => ({ ...p, staff_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={form.staff_id} onChange={e => setForm(p => ({ ...p, staff_id: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
<option value="">None</option> <option value="">None</option>
{staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)} {staff.map((s: any) => <option key={s.id} value={s.id}>{s.full_name}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Start Date *</label> <label className="text-xs text-[#888] block mb-1">Start Date *</label>
<input type="date" required value={form.start_date} onChange={e => setForm(p => ({ ...p, start_date: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <input type="date" required value={form.start_date} onChange={e => setForm(p => ({ ...p, start_date: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">End Date</label> <label className="text-xs text-[#888] block mb-1">End Date</label>
<input type="date" value={form.end_date} onChange={e => setForm(p => ({ ...p, end_date: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <input type="date" value={form.end_date} onChange={e => setForm(p => ({ ...p, end_date: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Flight Status</label> <label className="text-xs text-[#888] block mb-1">Flight Status</label>
<select value={form.flight_status} onChange={e => setForm(p => ({ ...p, flight_status: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={form.flight_status} onChange={e => setForm(p => ({ ...p, flight_status: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
{['scheduled','active','paused'].map(s => <option key={s} value={s}>{s}</option>)} {['scheduled','active','paused'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Banner Image URL</label> <label className="text-xs text-[#888] block mb-1">Banner Image URL</label>
<input value={form.banner_image_url} onChange={e => setForm(p => ({ ...p, banner_image_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.banner_image_url} onChange={e => setForm(p => ({ ...p, banner_image_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Click-Through URL</label> <label className="text-xs text-[#888] block mb-1">Click-Through URL</label>
<input value={form.click_through_url} onChange={e => setForm(p => ({ ...p, click_through_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.click_through_url} onChange={e => setForm(p => ({ ...p, click_through_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div className="flex items-center gap-3 pt-4"> <div className="flex items-center gap-3 pt-4">
<label className="flex items-center gap-2 text-xs text-[#888] cursor-pointer"> <label className="flex items-center gap-2 text-xs text-[#888] cursor-pointer">
@@ -159,47 +159,47 @@ export default function AdOpsIOsPage() {
{form.is_comp && ( {form.is_comp && (
<div> <div>
<label className="text-xs text-[#888] block mb-1">Comp Reason</label> <label className="text-xs text-[#888] block mb-1">Comp Reason</label>
<input value={form.comp_reason} onChange={e => setForm(p => ({ ...p, comp_reason: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.comp_reason} onChange={e => setForm(p => ({ ...p, comp_reason: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
)} )}
{isEmailProduct && ( {isEmailProduct && (
<> <>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Email HTML URL</label> <label className="text-xs text-[#888] block mb-1">Email HTML URL</label>
<input value={form.email_html_url} onChange={e => setForm(p => ({ ...p, email_html_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.email_html_url} onChange={e => setForm(p => ({ ...p, email_html_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Scheduled Send Date</label> <label className="text-xs text-[#888] block mb-1">Scheduled Send Date</label>
<input type="date" value={form.email_scheduled_date} onChange={e => setForm(p => ({ ...p, email_scheduled_date: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <input type="date" value={form.email_scheduled_date} onChange={e => setForm(p => ({ ...p, email_scheduled_date: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">List Segment</label> <label className="text-xs text-[#888] block mb-1">List Segment</label>
<input value={form.email_list_segment} onChange={e => setForm(p => ({ ...p, email_list_segment: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.email_list_segment} onChange={e => setForm(p => ({ ...p, email_list_segment: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</> </>
)} )}
{isAdvertorial && ( {isAdvertorial && (
<div> <div>
<label className="text-xs text-[#888] block mb-1">Page URL (permanent)</label> <label className="text-xs text-[#888] block mb-1">Page URL (permanent)</label>
<input value={form.page_url} onChange={e => setForm(p => ({ ...p, page_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.page_url} onChange={e => setForm(p => ({ ...p, page_url: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
)} )}
<div className="col-span-2 md:col-span-3"> <div className="col-span-2 md:col-span-3">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <textarea value={form.notes} onChange={e => setForm(p => ({ ...p, notes: e.target.value }))} rows={2} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create IO Extension'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Create IO Extension'}</button>
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['IO ID','Client','Product','Start','End','Status','Comp','Paid','Actions'].map(h => ( {['IO ID','Client','Product','Start','End','Status','Comp','Paid','Actions'].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
))} ))}
@@ -211,8 +211,8 @@ export default function AdOpsIOsPage() {
) : ios.length === 0 ? ( ) : ios.length === 0 ? (
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No IO extensions found</td></tr> <tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No IO extensions found</td></tr>
) : ios.map((io: any) => ( ) : ios.map((io: any) => (
<tr key={io.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={io.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-[#F0A623] font-mono text-xs">{io.existing_io_id}</td> <td className="px-3 py-2 text-[#1D4ED8] font-mono text-xs">{io.existing_io_id}</td>
<td className="px-3 py-2 text-white text-xs">{io.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{io.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{io.rmp_products?.name ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{io.rmp_products?.name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{io.start_date}</td> <td className="px-3 py-2 text-[#888] text-xs">{io.start_date}</td>
@@ -227,7 +227,7 @@ export default function AdOpsIOsPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${io.is_paid ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{io.is_paid ? 'Paid' : 'Unpaid'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${io.is_paid ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{io.is_paid ? 'Paid' : 'Unpaid'}</span>
</td> </td>
<td className="px-3 py-2 flex gap-2"> <td className="px-3 py-2 flex gap-2">
<Link href={`/admin/adops/ios/${io.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link> <Link href={`/admin/adops/ios/${io.id}`} className="text-xs text-[#1D4ED8] hover:underline">View</Link>
{io.flight_status === 'expired' && ( {io.flight_status === 'expired' && (
<button onClick={() => { const r = prompt('Reactivation reason?'); if (r) updateStatus(io.id, 'active', r); }} className="text-xs text-green-400 hover:underline">Reactivate</button> <button onClick={() => { const r = prompt('Reactivation reason?'); if (r) updateStatus(io.id, 'active', r); }} className="text-xs text-green-400 hover:underline">Reactivate</button>
)} )}

View File

@@ -22,7 +22,7 @@ export default function AdOpsNotificationsPage() {
useEffect(() => { if (user) load(); }, [user, load]); useEffect(() => { if (user) load(); }, [user, load]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -33,10 +33,10 @@ export default function AdOpsNotificationsPage() {
<Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link> <Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Type','IO','Recipient','Via','Sent At','Status','Message'].map(h => ( {['Type','IO','Recipient','Via','Sent At','Status','Message'].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -48,9 +48,9 @@ export default function AdOpsNotificationsPage() {
) : notifications.length === 0 ? ( ) : notifications.length === 0 ? (
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No notifications yet</td></tr> <tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No notifications yet</td></tr>
) : notifications.map((n: any) => ( ) : notifications.map((n: any) => (
<tr key={n.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={n.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-[#888] text-xs">{n.notification_type?.replace(/_/g, ' ')}</td> <td className="px-3 py-2 text-[#888] text-xs">{n.notification_type?.replace(/_/g, ' ')}</td>
<td className="px-3 py-2 text-[#F0A623] font-mono text-xs">{n.io_extension_id?.slice(0, 8) ?? '—'}</td> <td className="px-3 py-2 text-[#1D4ED8] font-mono text-xs">{n.io_extension_id?.slice(0, 8) ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{n.recipient_email ?? n.recipient_phone ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{n.recipient_email ?? n.recipient_phone ?? '—'}</td>
<td className="px-3 py-2 text-[#555] text-xs">{n.sent_via}</td> <td className="px-3 py-2 text-[#555] text-xs">{n.sent_via}</td>
<td className="px-3 py-2 text-[#555] text-xs">{n.sent_at ? new Date(n.sent_at).toLocaleString() : '—'}</td> <td className="px-3 py-2 text-[#555] text-xs">{n.sent_at ? new Date(n.sent_at).toLocaleString() : '—'}</td>

View File

@@ -9,7 +9,7 @@ const FLIGHT_COLORS: Record<string, string> = {
scheduled: 'bg-blue-400/10 text-blue-400 border-blue-400/20', scheduled: 'bg-blue-400/10 text-blue-400 border-blue-400/20',
paused: 'bg-gray-600/20 text-gray-400 border-gray-600/20', paused: 'bg-gray-600/20 text-gray-400 border-gray-600/20',
expired: 'bg-red-400/10 text-red-400 border-red-400/20', expired: 'bg-red-400/10 text-red-400 border-red-400/20',
cancelled: 'bg-[#231d18] text-[#555] border-[#3a322b]', cancelled: 'bg-[#FFFFFF] text-[#555] border-[#DCE6F2]',
}; };
export default function AdOpsDashboardPage() { export default function AdOpsDashboardPage() {
@@ -43,7 +43,7 @@ export default function AdOpsDashboardPage() {
useEffect(() => { if (user) load(); }, [user, load]); useEffect(() => { if (user) load(); }, [user, load]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -64,7 +64,7 @@ export default function AdOpsDashboardPage() {
{ label: 'Unpaid Active', key: 'unpaid_active', color: 'text-orange-400' }, { label: 'Unpaid Active', key: 'unpaid_active', color: 'text-orange-400' },
{ label: 'Email Blasts Scheduled', key: 'email_scheduled', color: 'text-purple-400' }, { label: 'Email Blasts Scheduled', key: 'email_scheduled', color: 'text-purple-400' },
].map(c => ( ].map(c => (
<div key={c.key} className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div key={c.key} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<p className={`text-2xl font-bold ${c.color}`}>{stats?.[c.key] ?? 0}</p> <p className={`text-2xl font-bold ${c.color}`}>{stats?.[c.key] ?? 0}</p>
<p className="text-xs text-[#888] mt-1">{c.label}</p> <p className="text-xs text-[#888] mt-1">{c.label}</p>
</div> </div>
@@ -73,25 +73,25 @@ export default function AdOpsDashboardPage() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{['avbeat','broadcastbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)} {['avbeat','broadcastbeat','backlotbeat','broadcastengineering'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Statuses</option> <option value="">All Statuses</option>
{['active','scheduled','paused','expired','cancelled'].map(s => <option key={s} value={s}>{s}</option>)} {['active','scheduled','paused','expired','cancelled'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
{/* Active Flights Table */} {/* Active Flights Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<div className="px-4 py-3 border-b border-[#3a322b] flex items-center justify-between"> <div className="px-4 py-3 border-b border-[#DCE6F2] flex items-center justify-between">
<h2 className="text-sm font-semibold">Active Flights</h2> <h2 className="text-sm font-semibold">Active Flights</h2>
<Link href="/admin/adops/ios" className="text-xs text-[#F0A623] hover:underline">View All IOs </Link> <Link href="/admin/adops/ios" className="text-xs text-[#1D4ED8] hover:underline">View All IOs </Link>
</div> </div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Client','Product','Site','Start','End','Status','Salesperson','Paid',''].map(h => ( {['Client','Product','Site','Start','End','Status','Salesperson','Paid',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
))} ))}
@@ -103,7 +103,7 @@ export default function AdOpsDashboardPage() {
) : flights.length === 0 ? ( ) : flights.length === 0 ? (
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No flights found</td></tr> <tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No flights found</td></tr>
) : flights.map((f: any) => ( ) : flights.map((f: any) => (
<tr key={f.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={f.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-white text-xs">{f.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{f.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#ccc] text-xs">{f.rmp_products?.name ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs">{f.rmp_products?.name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{f.rmp_orders?.site ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{f.rmp_orders?.site ?? '—'}</td>
@@ -117,7 +117,7 @@ export default function AdOpsDashboardPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${f.is_paid ? 'bg-green-400/10 text-green-400' : f.is_comp ? 'bg-blue-400/10 text-blue-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{f.is_comp ? 'Comp' : f.is_paid ? 'Paid' : 'Unpaid'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${f.is_paid ? 'bg-green-400/10 text-green-400' : f.is_comp ? 'bg-blue-400/10 text-blue-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{f.is_comp ? 'Comp' : f.is_paid ? 'Paid' : 'Unpaid'}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<Link href={`/admin/adops/ios/${f.id}`} className="text-xs text-[#F0A623] hover:underline">View</Link> <Link href={`/admin/adops/ios/${f.id}`} className="text-xs text-[#1D4ED8] hover:underline">View</Link>
</td> </td>
</tr> </tr>
))} ))}
@@ -126,13 +126,13 @@ export default function AdOpsDashboardPage() {
</div> </div>
{/* Email Schedule */} {/* Email Schedule */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b]"> <div className="px-4 py-3 border-b border-[#DCE6F2]">
<h2 className="text-sm font-semibold">Email Distribution Schedule</h2> <h2 className="text-sm font-semibold">Email Distribution Schedule</h2>
</div> </div>
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Client','Type','Site','Scheduled Date','Status',''].map(h => ( {['Client','Type','Site','Scheduled Date','Status',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -142,7 +142,7 @@ export default function AdOpsDashboardPage() {
{emailSchedule.length === 0 ? ( {emailSchedule.length === 0 ? (
<tr><td colSpan={6} className="px-4 py-6 text-center text-[#555] text-xs">No email blasts scheduled</td></tr> <tr><td colSpan={6} className="px-4 py-6 text-center text-[#555] text-xs">No email blasts scheduled</td></tr>
) : emailSchedule.map((e: any) => ( ) : emailSchedule.map((e: any) => (
<tr key={e.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={e.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-white text-xs">{e.rmp_clients?.company_name ?? '—'}</td> <td className="px-3 py-2 text-white text-xs">{e.rmp_clients?.company_name ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{e.rmp_products?.product_type?.replace(/_/g, ' ')}</td> <td className="px-3 py-2 text-[#888] text-xs">{e.rmp_products?.product_type?.replace(/_/g, ' ')}</td>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{e.rmp_orders?.site ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{e.rmp_orders?.site ?? '—'}</td>
@@ -151,7 +151,7 @@ export default function AdOpsDashboardPage() {
<span className={`px-2 py-0.5 rounded-full text-xs ${e.email_sent_at ? 'bg-green-400/10 text-green-400' : 'bg-blue-400/10 text-blue-400'}`}>{e.email_sent_at ? 'Sent' : 'Pending'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${e.email_sent_at ? 'bg-green-400/10 text-green-400' : 'bg-blue-400/10 text-blue-400'}`}>{e.email_sent_at ? 'Sent' : 'Pending'}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
{e.email_html_url && <a href={e.email_html_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#F0A623] hover:underline">Preview</a>} {e.email_html_url && <a href={e.email_html_url} target="_blank" rel="noopener noreferrer" className="text-xs text-[#1D4ED8] hover:underline">Preview</a>}
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -49,7 +49,7 @@ export default function AdOpsProductsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -60,62 +60,62 @@ export default function AdOpsProductsPage() {
<h1 className="text-xl font-bold">Product Catalog</h1> <h1 className="text-xl font-bold">Product Catalog</h1>
<Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link> <Link href="/admin/adops" className="text-xs text-[#555] hover:text-[#888]"> Ad Operations</Link>
</div> </div>
<button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Add Product</button> <button onClick={() => setShowForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ Add Product</button>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
{showForm && ( {showForm && (
<form onSubmit={handleCreate} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleCreate} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">New Product</h3> <h3 className="text-sm font-semibold">New Product</h3>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3"> <div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Site *</label> <label className="text-xs text-[#888] block mb-1">Site *</label>
<select required value={form.site} onChange={e => setForm(p => ({ ...p, site: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select required value={form.site} onChange={e => setForm(p => ({ ...p, site: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
<option value="">Select</option> <option value="">Select</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Product Type *</label> <label className="text-xs text-[#888] block mb-1">Product Type *</label>
<select required value={form.product_type} onChange={e => setForm(p => ({ ...p, product_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select required value={form.product_type} onChange={e => setForm(p => ({ ...p, product_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
<option value="">Select</option> <option value="">Select</option>
{PRODUCT_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)} {PRODUCT_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Name *</label> <label className="text-xs text-[#888] block mb-1">Name *</label>
<input required value={form.name} onChange={e => setForm(p => ({ ...p, name: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required value={form.name} onChange={e => setForm(p => ({ ...p, name: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Dimensions</label> <label className="text-xs text-[#888] block mb-1">Dimensions</label>
<input value={form.dimensions} onChange={e => setForm(p => ({ ...p, dimensions: e.target.value }))} placeholder="e.g. 300x250" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.dimensions} onChange={e => setForm(p => ({ ...p, dimensions: e.target.value }))} placeholder="e.g. 300x250" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Duration (days)</label> <label className="text-xs text-[#888] block mb-1">Duration (days)</label>
<input type="number" value={form.duration_days} onChange={e => setForm(p => ({ ...p, duration_days: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input type="number" value={form.duration_days} onChange={e => setForm(p => ({ ...p, duration_days: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div className="col-span-2 md:col-span-3"> <div className="col-span-2 md:col-span-3">
<label className="text-xs text-[#888] block mb-1">Description</label> <label className="text-xs text-[#888] block mb-1">Description</label>
<input value={form.description} onChange={e => setForm(p => ({ ...p, description: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={form.description} onChange={e => setForm(p => ({ ...p, description: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Product'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Product'}</button>
<button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowForm(false)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Site','Type','Name','Dimensions','Duration','Active',''].map(h => ( {['Site','Type','Name','Dimensions','Duration','Active',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -127,17 +127,17 @@ export default function AdOpsProductsPage() {
) : products.length === 0 ? ( ) : products.length === 0 ? (
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No products found</td></tr> <tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No products found</td></tr>
) : products.map((p: any) => ( ) : products.map((p: any) => (
<tr key={p.id} className={`border-b border-[#231d18] hover:bg-[#231d18] ${!p.active ? 'opacity-50' : ''}`}> <tr key={p.id} className={`border-b border-[#FFFFFF] hover:bg-[#FFFFFF] ${!p.active ? 'opacity-50' : ''}`}>
<td className="px-3 py-2 text-[#888] text-xs capitalize">{p.site}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{p.site}</td>
<td className="px-3 py-2 text-[#555] text-xs">{p.product_type?.replace(/_/g, ' ')}</td> <td className="px-3 py-2 text-[#555] text-xs">{p.product_type?.replace(/_/g, ' ')}</td>
<td className="px-3 py-2 text-white text-xs">{p.name}</td> <td className="px-3 py-2 text-white text-xs">{p.name}</td>
<td className="px-3 py-2 text-[#888] text-xs">{p.dimensions ?? '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{p.dimensions ?? '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{p.duration_days ? `${p.duration_days}d` : '—'}</td> <td className="px-3 py-2 text-[#888] text-xs">{p.duration_days ? `${p.duration_days}d` : '—'}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${p.active ? 'bg-green-400/10 text-green-400' : 'bg-[#231d18] text-[#555]'}`}>{p.active ? 'Active' : 'Inactive'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${p.active ? 'bg-green-400/10 text-green-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{p.active ? 'Active' : 'Inactive'}</span>
</td> </td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<button onClick={() => toggleActive(p.id, p.active)} className="text-xs text-[#F0A623] hover:underline">{p.active ? 'Deactivate' : 'Activate'}</button> <button onClick={() => toggleActive(p.id, p.active)} className="text-xs text-[#1D4ED8] hover:underline">{p.active ? 'Deactivate' : 'Activate'}</button>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -64,7 +64,7 @@ interface MonthlyTrend {
// ─── Mock / derived data helpers ────────────────────────────────────────────── // ─── Mock / derived data helpers ──────────────────────────────────────────────
const CATEGORY_COLORS = [ const CATEGORY_COLORS = [
'#F0A623', '#f59e0b', '#10b981', '#8b5cf6', '#1D4ED8', '#f59e0b', '#10b981', '#8b5cf6',
'#ef4444', '#06b6d4', '#f97316', '#84cc16', '#ef4444', '#06b6d4', '#f97316', '#84cc16',
]; ];
@@ -78,9 +78,9 @@ interface StatCardProps {
icon: React.ReactNode; icon: React.ReactNode;
} }
function StatCard({ label, value, sub, accent = '#F0A623', icon }: StatCardProps) { function StatCard({ label, value, sub, accent = '#1D4ED8', icon }: StatCardProps) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5 flex items-start gap-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 flex items-start gap-4">
<div <div
className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0" className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0"
style={{ background: `${accent}18` }} style={{ background: `${accent}18` }}
@@ -112,7 +112,7 @@ function SectionHeader({ title, subtitle }: { title: string; subtitle?: string }
function ChartTooltip({ active, payload, label }: any) { function ChartTooltip({ active, payload, label }: any) {
if (!active || !payload?.length) return null; if (!active || !payload?.length) return null;
return ( return (
<div className="bg-[#231d18] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl"> <div className="bg-[#FFFFFF] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl">
{label && <p className="text-[#888] mb-1">{label}</p>} {label && <p className="text-[#888] mb-1">{label}</p>}
{payload.map((entry: any, i: number) => ( {payload.map((entry: any, i: number) => (
<p key={i} style={{ color: entry.color }} className="leading-5"> <p key={i} style={{ color: entry.color }} className="leading-5">
@@ -252,7 +252,7 @@ export default function AdminAnalyticsPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading analytics</p> <p className="text-[#555] text-sm font-body">Loading analytics</p>
</div> </div>
</div> </div>
@@ -266,7 +266,7 @@ export default function AdminAnalyticsPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] text-white"> <div className="min-h-screen bg-[#0a0a0a] text-white">
{/* ── Page Header ─────────────────────────────────────────────────── */} {/* ── Page Header ─────────────────────────────────────────────────── */}
<div className="bg-[#111] border-b border-[#3a322b]"> <div className="bg-[#111] border-b border-[#DCE6F2]">
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-5 flex items-center justify-between gap-4"> <div className="max-w-7xl mx-auto px-4 sm:px-6 py-5 flex items-center justify-between gap-4">
<div> <div>
<h1 className="text-white text-xl font-bold font-heading uppercase tracking-wider">Analytics</h1> <h1 className="text-white text-xl font-bold font-heading uppercase tracking-wider">Analytics</h1>
@@ -275,20 +275,20 @@ export default function AdminAnalyticsPage() {
</p> </p>
</div> </div>
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<Link href="/admin/articles" className="text-[#555] hover:text-[#F0A623] text-xs font-body uppercase tracking-wider transition-colors"> <Link href="/admin/articles" className="text-[#555] hover:text-[#1D4ED8] text-xs font-body uppercase tracking-wider transition-colors">
Articles Articles
</Link> </Link>
<span className="text-[#333]">·</span> <span className="text-[#333]">·</span>
<Link href="/admin/users" className="text-[#555] hover:text-[#F0A623] text-xs font-body uppercase tracking-wider transition-colors"> <Link href="/admin/users" className="text-[#555] hover:text-[#1D4ED8] text-xs font-body uppercase tracking-wider transition-colors">
Users Users
</Link> </Link>
<span className="text-[#333]">·</span> <span className="text-[#333]">·</span>
<Link href="/admin/import" className="text-[#555] hover:text-[#F0A623] text-xs font-body uppercase tracking-wider transition-colors"> <Link href="/admin/import" className="text-[#555] hover:text-[#1D4ED8] text-xs font-body uppercase tracking-wider transition-colors">
WP Import WP Import
</Link> </Link>
<button <button
onClick={fetchAnalytics} onClick={fetchAnalytics}
className="ml-2 px-3 py-1.5 bg-[#231d18] border border-[#333] hover:border-[#F0A623] text-[#888] hover:text-[#F0A623] text-xs font-body rounded transition-colors" className="ml-2 px-3 py-1.5 bg-[#FFFFFF] border border-[#333] hover:border-[#1D4ED8] text-[#888] hover:text-[#1D4ED8] text-xs font-body rounded transition-colors"
> >
Refresh Refresh
</button> </button>
@@ -312,7 +312,7 @@ export default function AdminAnalyticsPage() {
label="Total Articles" label="Total Articles"
value={metrics?.totalArticles ?? 0} value={metrics?.totalArticles ?? 0}
icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>} icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>}
accent="#F0A623" accent="#1D4ED8"
/> />
<StatCard <StatCard
label="Published" label="Published"
@@ -361,7 +361,7 @@ export default function AdminAnalyticsPage() {
value={`${publishRate}%`} value={`${publishRate}%`}
sub="Published / total" sub="Published / total"
icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>} icon={<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>}
accent="#F0A623" accent="#1D4ED8"
/> />
</div> </div>
</section> </section>
@@ -369,7 +369,7 @@ export default function AdminAnalyticsPage() {
{/* ── Monthly Trend ────────────────────────────────────────────── */} {/* ── Monthly Trend ────────────────────────────────────────────── */}
<section> <section>
<SectionHeader title="Monthly Trends" subtitle="Published, imported, and native articles over the last 6 months" /> <SectionHeader title="Monthly Trends" subtitle="Published, imported, and native articles over the last 6 months" />
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
{monthlyTrends.length > 0 ? ( {monthlyTrends.length > 0 ? (
<ResponsiveContainer width="100%" height={240}> <ResponsiveContainer width="100%" height={240}>
<LineChart data={monthlyTrends} margin={{ top: 4, right: 8, left: -20, bottom: 0 }}> <LineChart data={monthlyTrends} margin={{ top: 4, right: 8, left: -20, bottom: 0 }}>
@@ -395,7 +395,7 @@ export default function AdminAnalyticsPage() {
{/* Author Contributions */} {/* Author Contributions */}
<section> <section>
<SectionHeader title="Author Contributions" subtitle="Top contributors by article count" /> <SectionHeader title="Author Contributions" subtitle="Top contributors by article count" />
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
{authorStats.length > 0 ? ( {authorStats.length > 0 ? (
<ResponsiveContainer width="100%" height={280}> <ResponsiveContainer width="100%" height={280}>
<BarChart <BarChart
@@ -429,7 +429,7 @@ export default function AdminAnalyticsPage() {
{/* Top Categories */} {/* Top Categories */}
<section> <section>
<SectionHeader title="Top-Performing Categories" subtitle="Article distribution by category" /> <SectionHeader title="Top-Performing Categories" subtitle="Article distribution by category" />
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
{categoryStats.length > 0 ? ( {categoryStats.length > 0 ? (
<> <>
<ResponsiveContainer width="100%" height={200}> <ResponsiveContainer width="100%" height={200}>
@@ -481,7 +481,7 @@ export default function AdminAnalyticsPage() {
<SectionHeader title="Import Success Rates" subtitle="Publishing success across content sources" /> <SectionHeader title="Import Success Rates" subtitle="Publishing success across content sources" />
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{importStats.map((stat) => ( {importStats.map((stat) => (
<div key={stat.label} className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div key={stat.label} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<p className="text-white text-sm font-bold font-heading uppercase tracking-wider">{stat.label}</p> <p className="text-white text-sm font-bold font-heading uppercase tracking-wider">{stat.label}</p>
<span <span
@@ -526,7 +526,7 @@ export default function AdminAnalyticsPage() {
{/* ── Author Detail Table ──────────────────────────────────────── */} {/* ── Author Detail Table ──────────────────────────────────────── */}
<section> <section>
<SectionHeader title="Author Detail Breakdown" subtitle="Per-author article status distribution" /> <SectionHeader title="Author Detail Breakdown" subtitle="Per-author article status distribution" />
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-sm font-body"> <table className="w-full text-sm font-body">
<thead> <thead>
@@ -550,7 +550,7 @@ export default function AdminAnalyticsPage() {
return ( return (
<tr <tr
key={a.author_name} key={a.author_name}
className={`border-b border-[#231d18] hover:bg-[#151515] transition-colors ${i % 2 === 0 ? '' : 'bg-[#1c1815]'}`} className={`border-b border-[#FFFFFF] hover:bg-[#151515] transition-colors ${i % 2 === 0 ? '' : 'bg-[#F8FAFC]'}`}
> >
<td className="px-4 py-3 text-white font-medium">{a.author_name}</td> <td className="px-4 py-3 text-white font-medium">{a.author_name}</td>
<td className="px-4 py-3 text-right text-[#888]">{a.total}</td> <td className="px-4 py-3 text-right text-[#888]">{a.total}</td>

View File

@@ -33,7 +33,7 @@ const STATUS_COLORS: Record<string, string> = {
published: 'text-green-400 bg-green-400/10', published: 'text-green-400 bg-green-400/10',
draft: 'text-yellow-400 bg-yellow-400/10', draft: 'text-yellow-400 bg-yellow-400/10',
pending: 'text-orange-400 bg-orange-400/10', pending: 'text-orange-400 bg-orange-400/10',
archived: 'text-[#555] bg-[#231d18]', archived: 'text-[#555] bg-[#FFFFFF]',
}; };
const STATUS_LABELS: Record<string, string> = { const STATUS_LABELS: Record<string, string> = {
@@ -384,7 +384,7 @@ export default function AdminArticlesPage() {
if (loading || (!user && !loading)) { if (loading || (!user && !loading)) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -406,10 +406,10 @@ export default function AdminArticlesPage() {
)} )}
{/* Page Header */} {/* Page Header */}
<div className="bg-[#1c1815] border-b border-[#3a322b]"> <div className="bg-[#F8FAFC] border-b border-[#DCE6F2]">
<div className="max-w-7xl mx-auto px-4 py-5"> <div className="max-w-7xl mx-auto px-4 py-5">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<Link href="/home-page" className="text-[#555] hover:text-[#F0A623] transition-colors text-sm">Home</Link> <Link href="/home-page" className="text-[#555] hover:text-[#1D4ED8] transition-colors text-sm">Home</Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
<span className="text-[#888] text-sm">Admin</span> <span className="text-[#888] text-sm">Admin</span>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
@@ -423,13 +423,13 @@ export default function AdminArticlesPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => setShowNewArticle(true)} onClick={() => setShowNewArticle(true)}
className="flex items-center gap-1.5 text-xs font-bold text-white bg-[#F0A623] hover:bg-[#BA7517] rounded px-3 py-2 transition-colors"> className="flex items-center gap-1.5 text-xs font-bold text-white bg-[#1D4ED8] hover:bg-[#1E3A8A] rounded px-3 py-2 transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
New Article New Article
</button> </button>
<Link <Link
href="/admin/import" href="/admin/import"
className="flex items-center gap-1.5 text-xs text-[#666] hover:text-[#F0A623] border border-[#3a322b] hover:border-[#F0A623] rounded px-3 py-2 transition-colors"> className="flex items-center gap-1.5 text-xs text-[#666] hover:text-[#1D4ED8] border border-[#DCE6F2] hover:border-[#1D4ED8] rounded px-3 py-2 transition-colors">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>
WP Import WP Import
</Link> </Link>
@@ -471,7 +471,7 @@ export default function AdminArticlesPage() {
{ label: 'Drafts', value: counts.draft, color: 'text-yellow-400' }, { label: 'Drafts', value: counts.draft, color: 'text-yellow-400' },
{ label: 'Archived', value: counts.archived, color: 'text-[#555]' }, { label: 'Archived', value: counts.archived, color: 'text-[#555]' },
].map((s) => ( ].map((s) => (
<div key={s.label} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 text-center"> <div key={s.label} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 text-center">
<p className={`text-2xl font-bold ${s.color}`}>{s.value}</p> <p className={`text-2xl font-bold ${s.color}`}>{s.value}</p>
<p className="text-[#555] text-xs mt-0.5 uppercase tracking-wider">{s.label}</p> <p className="text-[#555] text-xs mt-0.5 uppercase tracking-wider">{s.label}</p>
</div> </div>
@@ -479,7 +479,7 @@ export default function AdminArticlesPage() {
</div> </div>
{/* Status Tabs */} {/* Status Tabs */}
<div className="flex items-center gap-1 border-b border-[#3a322b] overflow-x-auto"> <div className="flex items-center gap-1 border-b border-[#DCE6F2] overflow-x-auto">
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <button
key={tab.key} key={tab.key}
@@ -492,7 +492,7 @@ export default function AdminArticlesPage() {
{tab.label} {tab.label}
{tab.key !== 'all' && ( {tab.key !== 'all' && (
<span className={`text-[10px] px-1.5 py-0.5 rounded-full ${ <span className={`text-[10px] px-1.5 py-0.5 rounded-full ${
activeTab === tab.key ? 'bg-current/10' : 'bg-[#231d18]' activeTab === tab.key ? 'bg-current/10' : 'bg-[#FFFFFF]'
}`}> }`}>
{counts[tab.key]} {counts[tab.key]}
</span> </span>
@@ -512,13 +512,13 @@ export default function AdminArticlesPage() {
placeholder="Search articles..." placeholder="Search articles..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#3a322b] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#111] border border-[#DCE6F2] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
<select <select
value={sourceFilter} value={sourceFilter}
onChange={(e) => setSourceFilter(e.target.value as SourceFilter)} onChange={(e) => setSourceFilter(e.target.value as SourceFilter)}
className="bg-[#111] border border-[#3a322b] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623]"> className="bg-[#111] border border-[#DCE6F2] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8]">
<option value="all">All Sources</option> <option value="all">All Sources</option>
<option value="imported">Imported (WP)</option> <option value="imported">Imported (WP)</option>
<option value="native">Native</option> <option value="native">Native</option>
@@ -527,13 +527,13 @@ export default function AdminArticlesPage() {
{/* Bulk Actions Bar */} {/* Bulk Actions Bar */}
{selectedIds.size > 0 && ( {selectedIds.size > 0 && (
<div className="flex items-center gap-3 bg-[#0d1a2e] border border-[#412402] rounded-lg px-4 py-3"> <div className="flex items-center gap-3 bg-[#0d1a2e] border border-[#DCE6F2] rounded-lg px-4 py-3">
<span className="text-[#F0A623] text-sm font-bold">{selectedIds.size} selected</span> <span className="text-[#1D4ED8] text-sm font-bold">{selectedIds.size} selected</span>
<div className="flex-1" /> <div className="flex-1" />
<select <select
value={bulkAction} value={bulkAction}
onChange={(e) => setBulkAction(e.target.value as BulkAction | '')} onChange={(e) => setBulkAction(e.target.value as BulkAction | '')}
className="bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-1.5 focus:outline-none focus:border-[#F0A623]"> className="bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-1.5 focus:outline-none focus:border-[#1D4ED8]">
<option value="">Bulk Action</option> <option value="">Bulk Action</option>
<option value="publish">Publish</option> <option value="publish">Publish</option>
<option value="pending">Submit for Review</option> <option value="pending">Submit for Review</option>
@@ -544,7 +544,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={handleBulkAction} onClick={handleBulkAction}
disabled={!bulkAction || bulkProcessing} disabled={!bulkAction || bulkProcessing}
className="flex items-center gap-1.5 bg-[#F0A623] hover:bg-[#BA7517] disabled:bg-[#412402] disabled:cursor-not-allowed text-white text-sm font-bold px-4 py-1.5 rounded transition-colors"> className="flex items-center gap-1.5 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:bg-[#DCE6F2] disabled:cursor-not-allowed text-white text-sm font-bold px-4 py-1.5 rounded transition-colors">
{bulkProcessing ? ( {bulkProcessing ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : null} ) : null}
@@ -568,15 +568,15 @@ export default function AdminArticlesPage() {
<span className="text-orange-400 text-sm font-bold">Approval Queue</span> <span className="text-orange-400 text-sm font-bold">Approval Queue</span>
<span className="text-[#555] text-xs ml-1"> Review and approve or reject each submission</span> <span className="text-[#555] text-xs ml-1"> Review and approve or reject each submission</span>
</div> </div>
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{pendingArticles.map((article) => ( {pendingArticles.map((article) => (
<div key={article.id} className={`flex items-start gap-4 px-4 py-4 hover:bg-[#141414] transition-colors ${processingIds.has(article.id) ? 'opacity-50 pointer-events-none' : ''}`}> <div key={article.id} className={`flex items-start gap-4 px-4 py-4 hover:bg-[#141414] transition-colors ${processingIds.has(article.id) ? 'opacity-50 pointer-events-none' : ''}`}>
{article.featured_image ? ( {article.featured_image ? (
<div className="flex-shrink-0 w-16 h-12 rounded overflow-hidden bg-[#231d18]"> <div className="flex-shrink-0 w-16 h-12 rounded overflow-hidden bg-[#FFFFFF]">
<AppImage src={article.featured_image} alt={article.featured_image_alt || article.title} width={64} height={48} className="w-full h-full object-cover" /> <AppImage src={article.featured_image} alt={article.featured_image_alt || article.title} width={64} height={48} className="w-full h-full object-cover" />
</div> </div>
) : ( ) : (
<div className="flex-shrink-0 w-16 h-12 rounded bg-[#231d18] flex items-center justify-center"> <div className="flex-shrink-0 w-16 h-12 rounded bg-[#FFFFFF] flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333]"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333]">
<rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /> <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" />
</svg> </svg>
@@ -587,9 +587,9 @@ export default function AdminArticlesPage() {
<div className="flex items-center gap-3 mt-1"> <div className="flex items-center gap-3 mt-1">
<span className="text-[11px] text-[#555]">{article.author_name || 'Unknown author'}</span> <span className="text-[11px] text-[#555]">{article.author_name || 'Unknown author'}</span>
{article.category && ( {article.category && (
<span className="text-[10px] font-bold uppercase tracking-wider text-[#F0A623] bg-[#0d1a2e] px-1.5 py-0.5 rounded">{article.category}</span> <span className="text-[10px] font-bold uppercase tracking-wider text-[#1D4ED8] bg-[#0d1a2e] px-1.5 py-0.5 rounded">{article.category}</span>
)} )}
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#F0A623] bg-[#F0A623]/10'}`}> <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#1D4ED8] bg-[#1D4ED8]/10'}`}>
{article.source === 'imported' ? 'WP' : 'Native'} {article.source === 'imported' ? 'WP' : 'Native'}
</span> </span>
</div> </div>
@@ -606,13 +606,13 @@ export default function AdminArticlesPage() {
</button> </button>
<button <button
onClick={() => handleStatusChange(article, 'draft')} onClick={() => handleStatusChange(article, 'draft')}
className="flex items-center gap-1.5 text-xs font-bold text-[#666] border border-[#333] hover:border-[#555] hover:bg-[#231d18] px-3 py-1.5 rounded transition-colors"> className="flex items-center gap-1.5 text-xs font-bold text-[#666] border border-[#333] hover:border-[#555] hover:bg-[#FFFFFF] px-3 py-1.5 rounded transition-colors">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg> <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
Reject Reject
</button> </button>
<button <button
onClick={() => openEdit(article)} onClick={() => openEdit(article)}
className="p-1.5 text-[#555] hover:text-[#F0A623] transition-colors rounded hover:bg-[#231d18]" className="p-1.5 text-[#555] hover:text-[#1D4ED8] transition-colors rounded hover:bg-[#FFFFFF]"
title="Edit"> title="Edit">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /> <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
@@ -627,14 +627,14 @@ export default function AdminArticlesPage() {
)} )}
{/* Articles Table */} {/* Articles Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{/* Table Header */} {/* Table Header */}
<div className="flex items-center gap-3 px-4 py-3 border-b border-[#3a322b] bg-[#1c1815]"> <div className="flex items-center gap-3 px-4 py-3 border-b border-[#DCE6F2] bg-[#F8FAFC]">
<input <input
type="checkbox" type="checkbox"
checked={articles.length > 0 && selectedIds.size === articles.length} checked={articles.length > 0 && selectedIds.size === articles.length}
onChange={toggleSelectAll} onChange={toggleSelectAll}
className="w-4 h-4 rounded border-[#333] bg-[#231d18] accent-[#F0A623] cursor-pointer" className="w-4 h-4 rounded border-[#333] bg-[#FFFFFF] accent-[#1D4ED8] cursor-pointer"
/> />
<span className="text-[10px] text-[#555] uppercase tracking-wider font-bold flex-1">Article</span> <span className="text-[10px] text-[#555] uppercase tracking-wider font-bold flex-1">Article</span>
<span className="text-[10px] text-[#555] uppercase tracking-wider font-bold w-24 hidden md:block">Category</span> <span className="text-[10px] text-[#555] uppercase tracking-wider font-bold w-24 hidden md:block">Category</span>
@@ -646,7 +646,7 @@ export default function AdminArticlesPage() {
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : articles.length === 0 ? ( ) : articles.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
@@ -656,7 +656,7 @@ export default function AdminArticlesPage() {
<p className="text-[#555] text-sm">No articles found</p> <p className="text-[#555] text-sm">No articles found</p>
</div> </div>
) : ( ) : (
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{articles.map((article) => ( {articles.map((article) => (
<div <div
key={article.id} key={article.id}
@@ -668,13 +668,13 @@ export default function AdminArticlesPage() {
type="checkbox" type="checkbox"
checked={selectedIds.has(article.id)} checked={selectedIds.has(article.id)}
onChange={() => toggleSelect(article.id)} onChange={() => toggleSelect(article.id)}
className="w-4 h-4 rounded border-[#333] bg-[#231d18] accent-[#F0A623] cursor-pointer flex-shrink-0" className="w-4 h-4 rounded border-[#333] bg-[#FFFFFF] accent-[#1D4ED8] cursor-pointer flex-shrink-0"
/> />
{/* Thumbnail + Title */} {/* Thumbnail + Title */}
<div className="flex items-center gap-3 flex-1 min-w-0"> <div className="flex items-center gap-3 flex-1 min-w-0">
{article.featured_image ? ( {article.featured_image ? (
<div className="flex-shrink-0 w-12 h-9 rounded overflow-hidden bg-[#231d18]"> <div className="flex-shrink-0 w-12 h-9 rounded overflow-hidden bg-[#FFFFFF]">
<AppImage <AppImage
src={article.featured_image} src={article.featured_image}
alt={article.featured_image_alt || article.title} alt={article.featured_image_alt || article.title}
@@ -684,7 +684,7 @@ export default function AdminArticlesPage() {
/> />
</div> </div>
) : ( ) : (
<div className="flex-shrink-0 w-12 h-9 rounded bg-[#231d18] flex items-center justify-center"> <div className="flex-shrink-0 w-12 h-9 rounded bg-[#FFFFFF] flex items-center justify-center">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333]"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333]">
<rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /> <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" />
</svg> </svg>
@@ -699,7 +699,7 @@ export default function AdminArticlesPage() {
{/* Category */} {/* Category */}
<div className="w-24 hidden md:block"> <div className="w-24 hidden md:block">
{article.category ? ( {article.category ? (
<span className="text-[10px] font-bold uppercase tracking-wider text-[#F0A623] bg-[#0d1a2e] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-bold uppercase tracking-wider text-[#1D4ED8] bg-[#0d1a2e] px-1.5 py-0.5 rounded">
{article.category} {article.category}
</span> </span>
) : ( ) : (
@@ -710,7 +710,7 @@ export default function AdminArticlesPage() {
{/* Source */} {/* Source */}
<div className="w-20 hidden lg:block"> <div className="w-20 hidden lg:block">
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${ <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${
article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#F0A623] bg-[#F0A623]/10' article.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#1D4ED8] bg-[#1D4ED8]/10'
}`}> }`}>
{article.source === 'imported' ? 'WP' : 'Native'} {article.source === 'imported' ? 'WP' : 'Native'}
</span> </span>
@@ -730,7 +730,7 @@ export default function AdminArticlesPage() {
{/* Status */} {/* Status */}
<div className="w-28"> <div className="w-28">
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${STATUS_COLORS[article.status] || 'text-[#555] bg-[#231d18]'}`}> <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${STATUS_COLORS[article.status] || 'text-[#555] bg-[#FFFFFF]'}`}>
{article.status === 'pending' ? 'Pending' : article.status} {article.status === 'pending' ? 'Pending' : article.status}
</span> </span>
</div> </div>
@@ -741,7 +741,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => openEdit(article)} onClick={() => openEdit(article)}
title="Edit" title="Edit"
className="p-1.5 text-[#555] hover:text-[#F0A623] transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-[#1D4ED8] transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /> <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" /> <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
@@ -753,7 +753,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => handleStatusChange(article, 'pending')} onClick={() => handleStatusChange(article, 'pending')}
title="Submit for Review" title="Submit for Review"
className="p-1.5 text-[#555] hover:text-orange-400 transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-orange-400 transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" /> <circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" />
</svg> </svg>
@@ -765,7 +765,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => handleStatusChange(article, 'published')} onClick={() => handleStatusChange(article, 'published')}
title="Approve & Publish" title="Approve & Publish"
className="p-1.5 text-[#555] hover:text-green-400 transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-green-400 transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" /> <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" />
</svg> </svg>
@@ -777,7 +777,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => handleStatusChange(article, 'draft')} onClick={() => handleStatusChange(article, 'draft')}
title="Reject (back to draft)" title="Reject (back to draft)"
className="p-1.5 text-[#555] hover:text-red-400 transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-red-400 transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /> <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
</svg> </svg>
@@ -789,7 +789,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => handleStatusChange(article, 'published')} onClick={() => handleStatusChange(article, 'published')}
title="Publish" title="Publish"
className="p-1.5 text-[#555] hover:text-green-400 transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-green-400 transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" /> <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" />
</svg> </svg>
@@ -801,7 +801,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => handleStatusChange(article, 'archived')} onClick={() => handleStatusChange(article, 'archived')}
title="Archive" title="Archive"
className="p-1.5 text-[#555] hover:text-yellow-400 transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-yellow-400 transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="21 8 21 21 3 21 3 8" /><rect x="1" y="3" width="22" height="5" /><line x1="10" y1="12" x2="14" y2="12" /> <polyline points="21 8 21 21 3 21 3 8" /><rect x="1" y="3" width="22" height="5" /><line x1="10" y1="12" x2="14" y2="12" />
</svg> </svg>
@@ -812,7 +812,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={() => handleDelete(article)} onClick={() => handleDelete(article)}
title="Delete" title="Delete"
className="p-1.5 text-[#555] hover:text-red-400 transition-colors rounded hover:bg-[#231d18]"> className="p-1.5 text-[#555] hover:text-red-400 transition-colors rounded hover:bg-[#FFFFFF]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="3 6 5 6 21 6" /> <polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /> <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
@@ -834,12 +834,12 @@ export default function AdminArticlesPage() {
{/* New Article Modal */} {/* New Article Modal */}
{showNewArticle && ( {showNewArticle && (
<div className="fixed inset-0 z-50 flex items-start justify-center p-4 bg-black/80 overflow-y-auto"> <div className="fixed inset-0 z-50 flex items-start justify-center p-4 bg-black/80 overflow-y-auto">
<div className="bg-[#111] border border-[#3a322b] rounded-xl w-full max-w-4xl shadow-2xl my-8"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl w-full max-w-4xl shadow-2xl my-8">
{/* Modal Header */} {/* Modal Header */}
<div className="flex items-center justify-between px-6 py-4 border-b border-[#3a322b] sticky top-0 bg-[#111] z-10 rounded-t-xl"> <div className="flex items-center justify-between px-6 py-4 border-b border-[#DCE6F2] sticky top-0 bg-[#111] z-10 rounded-t-xl">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h2 className="text-base font-bold text-white font-heading">New Article</h2> <h2 className="text-base font-bold text-white font-heading">New Article</h2>
<span className="text-[10px] font-bold uppercase tracking-wider text-[#F0A623] bg-[#F0A623]/10 px-1.5 py-0.5 rounded">Native</span> <span className="text-[10px] font-bold uppercase tracking-wider text-[#1D4ED8] bg-[#1D4ED8]/10 px-1.5 py-0.5 rounded">Native</span>
</div> </div>
<button <button
onClick={() => setShowNewArticle(false)} onClick={() => setShowNewArticle(false)}
@@ -859,7 +859,7 @@ export default function AdminArticlesPage() {
value={newTitle} value={newTitle}
onChange={(e) => setNewTitle(e.target.value)} onChange={(e) => setNewTitle(e.target.value)}
placeholder="Enter article title..." placeholder="Enter article title..."
className="w-full bg-[#231d18] border border-[#333] text-white text-lg font-bold rounded px-4 py-3 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#FFFFFF] border border-[#333] text-white text-lg font-bold rounded px-4 py-3 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
@@ -871,7 +871,7 @@ export default function AdminArticlesPage() {
onChange={(e) => setNewExcerpt(e.target.value)} onChange={(e) => setNewExcerpt(e.target.value)}
placeholder="Brief summary shown in article listings..." placeholder="Brief summary shown in article listings..."
rows={2} rows={2}
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2.5 focus:outline-none focus:border-[#F0A623] placeholder-[#444] resize-none" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444] resize-none"
/> />
</div> </div>
@@ -894,7 +894,7 @@ export default function AdminArticlesPage() {
value={newAuthor} value={newAuthor}
onChange={(e) => setNewAuthor(e.target.value)} onChange={(e) => setNewAuthor(e.target.value)}
placeholder="Author name" placeholder="Author name"
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -904,7 +904,7 @@ export default function AdminArticlesPage() {
value={newCategory} value={newCategory}
onChange={(e) => setNewCategory(e.target.value)} onChange={(e) => setNewCategory(e.target.value)}
placeholder="e.g. Technology" placeholder="e.g. Technology"
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -912,7 +912,7 @@ export default function AdminArticlesPage() {
<select <select
value={newStatus} value={newStatus}
onChange={(e) => setNewStatus(e.target.value as 'draft' | 'pending' | 'published')} onChange={(e) => setNewStatus(e.target.value as 'draft' | 'pending' | 'published')}
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623]"> className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8]">
<option value="draft">Draft</option> <option value="draft">Draft</option>
<option value="pending">Submit for Review</option> <option value="pending">Submit for Review</option>
<option value="published">Publish Now</option> <option value="published">Publish Now</option>
@@ -929,7 +929,7 @@ export default function AdminArticlesPage() {
value={newFeaturedImage} value={newFeaturedImage}
onChange={(e) => setNewFeaturedImage(e.target.value)} onChange={(e) => setNewFeaturedImage(e.target.value)}
placeholder="https://..." placeholder="https://..."
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
<div> <div>
@@ -939,21 +939,21 @@ export default function AdminArticlesPage() {
value={newFeaturedImageAlt} value={newFeaturedImageAlt}
onChange={(e) => setNewFeaturedImageAlt(e.target.value)} onChange={(e) => setNewFeaturedImageAlt(e.target.value)}
placeholder="Describe the image..." placeholder="Describe the image..."
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
</div> </div>
{/* Featured image preview */} {/* Featured image preview */}
{newFeaturedImage && ( {newFeaturedImage && (
<div className="rounded-lg overflow-hidden border border-[#3a322b] max-h-48"> <div className="rounded-lg overflow-hidden border border-[#DCE6F2] max-h-48">
<img src={newFeaturedImage} alt={newFeaturedImageAlt || 'Featured image preview'} className="w-full h-48 object-cover" /> <img src={newFeaturedImage} alt={newFeaturedImageAlt || 'Featured image preview'} className="w-full h-48 object-cover" />
</div> </div>
)} )}
</div> </div>
{/* Modal Footer */} {/* Modal Footer */}
<div className="flex items-center justify-between px-6 py-4 border-t border-[#3a322b] bg-[#1c1815] rounded-b-xl"> <div className="flex items-center justify-between px-6 py-4 border-t border-[#DCE6F2] bg-[#F8FAFC] rounded-b-xl">
<p className="text-[11px] text-[#444]"> <p className="text-[11px] text-[#444]">
{newStatus === 'draft' ? 'Saved as draft — not publicly visible' : newStatus === 'pending' ? 'Submitted for editorial review' : 'Will be published immediately'} {newStatus === 'draft' ? 'Saved as draft — not publicly visible' : newStatus === 'pending' ? 'Submitted for editorial review' : 'Will be published immediately'}
</p> </p>
@@ -966,7 +966,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={handleNewArticleSave} onClick={handleNewArticleSave}
disabled={newSaving || !newTitle.trim()} disabled={newSaving || !newTitle.trim()}
className="flex items-center gap-2 bg-[#F0A623] hover:bg-[#BA7517] disabled:bg-[#412402] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors"> className="flex items-center gap-2 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:bg-[#DCE6F2] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors">
{newSaving ? ( {newSaving ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : ( ) : (
@@ -983,8 +983,8 @@ export default function AdminArticlesPage() {
{/* Edit Modal */} {/* Edit Modal */}
{editingArticle && ( {editingArticle && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70">
<div className="bg-[#111] border border-[#3a322b] rounded-xl w-full max-w-lg shadow-2xl"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl w-full max-w-lg shadow-2xl">
<div className="flex items-center justify-between px-5 py-4 border-b border-[#3a322b]"> <div className="flex items-center justify-between px-5 py-4 border-b border-[#DCE6F2]">
<h2 className="text-base font-bold text-white font-heading">Edit Article</h2> <h2 className="text-base font-bold text-white font-heading">Edit Article</h2>
<button <button
onClick={() => setEditingArticle(null)} onClick={() => setEditingArticle(null)}
@@ -1002,7 +1002,7 @@ export default function AdminArticlesPage() {
type="text" type="text"
value={editTitle} value={editTitle}
onChange={(e) => setEditTitle(e.target.value)} onChange={(e) => setEditTitle(e.target.value)}
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
@@ -1012,7 +1012,7 @@ export default function AdminArticlesPage() {
type="text" type="text"
value={editAuthor} value={editAuthor}
onChange={(e) => setEditAuthor(e.target.value)} onChange={(e) => setEditAuthor(e.target.value)}
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -1021,7 +1021,7 @@ export default function AdminArticlesPage() {
type="text" type="text"
value={editCategory} value={editCategory}
onChange={(e) => setEditCategory(e.target.value)} onChange={(e) => setEditCategory(e.target.value)}
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
</div> </div>
@@ -1030,7 +1030,7 @@ export default function AdminArticlesPage() {
<select <select
value={editStatus} value={editStatus}
onChange={(e) => setEditStatus(e.target.value)} onChange={(e) => setEditStatus(e.target.value)}
className="w-full bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623]"> className="w-full bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8]">
<option value="draft">Draft</option> <option value="draft">Draft</option>
<option value="pending">Pending Review</option> <option value="pending">Pending Review</option>
<option value="published">Published</option> <option value="published">Published</option>
@@ -1040,19 +1040,19 @@ export default function AdminArticlesPage() {
{/* Scheduling — only shown for draft status */} {/* Scheduling — only shown for draft status */}
{editStatus === 'draft' && ( {editStatus === 'draft' && (
<div className="bg-[#0d1a2e] border border-[#412402] rounded-lg p-4"> <div className="bg-[#0d1a2e] border border-[#DCE6F2] rounded-lg p-4">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#F0A623]"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#1D4ED8]">
<circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /> <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
</svg> </svg>
<label className="text-xs text-[#F0A623] font-bold uppercase tracking-wider">Schedule Publish</label> <label className="text-xs text-[#1D4ED8] font-bold uppercase tracking-wider">Schedule Publish</label>
</div> </div>
<input <input
type="datetime-local" type="datetime-local"
value={editScheduledAt} value={editScheduledAt}
onChange={(e) => setEditScheduledAt(e.target.value)} onChange={(e) => setEditScheduledAt(e.target.value)}
min={new Date().toISOString().slice(0, 16)} min={new Date().toISOString().slice(0, 16)}
className="w-full bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] [color-scheme:dark]" className="w-full bg-[#111] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] [color-scheme:dark]"
/> />
{editScheduledAt && ( {editScheduledAt && (
<div className="flex items-center justify-between mt-2"> <div className="flex items-center justify-between mt-2">
@@ -1074,14 +1074,14 @@ export default function AdminArticlesPage() {
<div className="flex items-center gap-2 pt-1"> <div className="flex items-center gap-2 pt-1">
<span className="text-xs text-[#555]">Source:</span> <span className="text-xs text-[#555]">Source:</span>
<span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${ <span className={`text-[10px] font-bold uppercase tracking-wider px-1.5 py-0.5 rounded ${
editingArticle.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#F0A623] bg-[#F0A623]/10' editingArticle.source === 'imported' ? 'text-purple-400 bg-purple-400/10' : 'text-[#1D4ED8] bg-[#1D4ED8]/10'
}`}> }`}>
{editingArticle.source === 'imported' ? 'WordPress Import' : 'Native'} {editingArticle.source === 'imported' ? 'WordPress Import' : 'Native'}
</span> </span>
</div> </div>
</div> </div>
<div className="flex items-center justify-end gap-3 px-5 py-4 border-t border-[#3a322b]"> <div className="flex items-center justify-end gap-3 px-5 py-4 border-t border-[#DCE6F2]">
<button <button
onClick={() => setEditingArticle(null)} onClick={() => setEditingArticle(null)}
className="text-[#666] hover:text-[#999] text-sm transition-colors px-4 py-2"> className="text-[#666] hover:text-[#999] text-sm transition-colors px-4 py-2">
@@ -1090,7 +1090,7 @@ export default function AdminArticlesPage() {
<button <button
onClick={handleEditSave} onClick={handleEditSave}
disabled={editSaving || !editTitle.trim()} disabled={editSaving || !editTitle.trim()}
className="flex items-center gap-2 bg-[#F0A623] hover:bg-[#BA7517] disabled:bg-[#412402] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors"> className="flex items-center gap-2 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:bg-[#DCE6F2] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors">
{editSaving ? ( {editSaving ? (
<div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : null} ) : null}

View File

@@ -50,13 +50,13 @@ const ACTION_COLORS: Record<string, string> = {
article_published: 'text-green-400 bg-green-400/10', article_published: 'text-green-400 bg-green-400/10',
article_unpublished: 'text-yellow-400 bg-yellow-400/10', article_unpublished: 'text-yellow-400 bg-yellow-400/10',
article_deleted: 'text-red-400 bg-red-400/10', article_deleted: 'text-red-400 bg-red-400/10',
article_archived: 'text-[#888] bg-[#231d18]', article_archived: 'text-[#888] bg-[#FFFFFF]',
article_status_changed: 'text-purple-400 bg-purple-400/10', article_status_changed: 'text-purple-400 bg-purple-400/10',
user_role_changed: 'text-orange-400 bg-orange-400/10', user_role_changed: 'text-orange-400 bg-orange-400/10',
user_activated: 'text-green-400 bg-green-400/10', user_activated: 'text-green-400 bg-green-400/10',
user_deactivated: 'text-red-400 bg-red-400/10', user_deactivated: 'text-red-400 bg-red-400/10',
user_invited: 'text-blue-400 bg-blue-400/10', user_invited: 'text-blue-400 bg-blue-400/10',
user_invitation_revoked: 'text-[#888] bg-[#231d18]', user_invitation_revoked: 'text-[#888] bg-[#FFFFFF]',
import_completed: 'text-teal-400 bg-teal-400/10', import_completed: 'text-teal-400 bg-teal-400/10',
import_failed: 'text-red-400 bg-red-400/10', import_failed: 'text-red-400 bg-red-400/10',
}; };
@@ -150,7 +150,7 @@ interface StatCardProps {
function StatCard({ label, value, accent, icon }: StatCardProps) { function StatCard({ label, value, accent, icon }: StatCardProps) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4 flex items-center gap-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 flex items-center gap-4">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${accent}`}> <div className={`w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0 ${accent}`}>
{icon} {icon}
</div> </div>
@@ -170,16 +170,16 @@ interface DetailModalProps {
} }
function DetailModal({ log, onClose }: DetailModalProps) { function DetailModal({ log, onClose }: DetailModalProps) {
const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#231d18]'; const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#FFFFFF]';
const actionLabel = ACTION_LABELS[log.action] || log.action; const actionLabel = ACTION_LABELS[log.action] || log.action;
const performerName = log.user_profiles?.full_name || log.performed_by_email || 'System'; const performerName = log.user_profiles?.full_name || log.performed_by_email || 'System';
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/70" onClick={onClose} /> <div className="absolute inset-0 bg-black/70" onClick={onClose} />
<div className="relative bg-[#111] border border-[#3a322b] rounded-xl w-full max-w-lg shadow-2xl"> <div className="relative bg-[#111] border border-[#DCE6F2] rounded-xl w-full max-w-lg shadow-2xl">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between p-5 border-b border-[#3a322b]"> <div className="flex items-center justify-between p-5 border-b border-[#DCE6F2]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${actionColor}`}> <div className={`w-8 h-8 rounded-lg flex items-center justify-center ${actionColor}`}>
{ACTION_ICONS[log.action] || getDefaultIcon(log.action)} {ACTION_ICONS[log.action] || getDefaultIcon(log.action)}
@@ -247,7 +247,7 @@ function DetailModal({ log, onClose }: DetailModalProps) {
{log.metadata && Object.keys(log.metadata).length > 0 && ( {log.metadata && Object.keys(log.metadata).length > 0 && (
<div> <div>
<p className="text-[#555] text-xs font-body uppercase tracking-wider mb-1">Metadata</p> <p className="text-[#555] text-xs font-body uppercase tracking-wider mb-1">Metadata</p>
<div className="bg-[#1c1815] border border-[#1e1e1e] rounded-lg p-3"> <div className="bg-[#F8FAFC] border border-[#1e1e1e] rounded-lg p-3">
<pre className="text-[#aaa] text-xs font-mono whitespace-pre-wrap break-all"> <pre className="text-[#aaa] text-xs font-mono whitespace-pre-wrap break-all">
{JSON.stringify(log.metadata, null, 2)} {JSON.stringify(log.metadata, null, 2)}
</pre> </pre>
@@ -340,7 +340,7 @@ export default function AuditLogPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -368,7 +368,7 @@ export default function AuditLogPage() {
</div> </div>
<button <button
onClick={fetchLogs} onClick={fetchLogs}
className="flex items-center gap-2 px-3 py-2 bg-[#111] border border-[#3a322b] rounded-lg text-[#888] hover:text-white hover:border-[#333] transition-all text-sm font-body" className="flex items-center gap-2 px-3 py-2 bg-[#111] border border-[#DCE6F2] rounded-lg text-[#888] hover:text-white hover:border-[#333] transition-all text-sm font-body"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> <path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@@ -382,7 +382,7 @@ export default function AuditLogPage() {
<StatCard <StatCard
label="Total Events" label="Total Events"
value={logs.length} value={logs.length}
accent="bg-[#F0A623]/10 text-[#F0A623]" accent="bg-[#1D4ED8]/10 text-[#1D4ED8]"
icon={ icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
@@ -433,19 +433,19 @@ export default function AuditLogPage() {
placeholder="Search by user, item, or action…" placeholder="Search by user, item, or action…"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#3a322b] rounded-lg pl-9 pr-4 py-2.5 text-sm text-white placeholder-[#444] focus:outline-none focus:border-[#F0A623] font-body" className="w-full bg-[#111] border border-[#DCE6F2] rounded-lg pl-9 pr-4 py-2.5 text-sm text-white placeholder-[#444] focus:outline-none focus:border-[#1D4ED8] font-body"
/> />
</div> </div>
{/* Action Type Filter */} {/* Action Type Filter */}
<div className="flex items-center gap-1 bg-[#111] border border-[#3a322b] rounded-lg p-1"> <div className="flex items-center gap-1 bg-[#111] border border-[#DCE6F2] rounded-lg p-1">
{(['all', 'article', 'user', 'import'] as ActionFilter[]).map((f) => ( {(['all', 'article', 'user', 'import'] as ActionFilter[]).map((f) => (
<button <button
key={f} key={f}
onClick={() => setActionFilter(f)} onClick={() => setActionFilter(f)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-all ${ className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-all ${
actionFilter === f actionFilter === f
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#555] hover:text-[#888]' : 'text-[#555] hover:text-[#888]'
}`} }`}
> >
@@ -466,9 +466,9 @@ export default function AuditLogPage() {
)} )}
{/* Table */} {/* Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-xl overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl overflow-hidden">
{/* Table Header */} {/* Table Header */}
<div className="hidden md:grid grid-cols-[2fr_1.5fr_1.5fr_1fr_auto] gap-4 px-5 py-3 border-b border-[#1e1e1e] bg-[#1c1815]"> <div className="hidden md:grid grid-cols-[2fr_1.5fr_1.5fr_1fr_auto] gap-4 px-5 py-3 border-b border-[#1e1e1e] bg-[#F8FAFC]">
<p className="text-[#444] text-xs font-body font-semibold uppercase tracking-wider">Action</p> <p className="text-[#444] text-xs font-body font-semibold uppercase tracking-wider">Action</p>
<p className="text-[#444] text-xs font-body font-semibold uppercase tracking-wider">Performed By</p> <p className="text-[#444] text-xs font-body font-semibold uppercase tracking-wider">Performed By</p>
<p className="text-[#444] text-xs font-body font-semibold uppercase tracking-wider">Affected Item</p> <p className="text-[#444] text-xs font-body font-semibold uppercase tracking-wider">Affected Item</p>
@@ -480,7 +480,7 @@ export default function AuditLogPage() {
{loadingData && ( {loadingData && (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading audit logs</p> <p className="text-[#555] text-sm font-body">Loading audit logs</p>
</div> </div>
</div> </div>
@@ -489,14 +489,14 @@ export default function AuditLogPage() {
{/* Empty */} {/* Empty */}
{!loadingData && filteredLogs.length === 0 && ( {!loadingData && filteredLogs.length === 0 && (
<div className="flex flex-col items-center justify-center py-16 gap-3"> <div className="flex flex-col items-center justify-center py-16 gap-3">
<div className="w-12 h-12 rounded-full bg-[#231d18] flex items-center justify-center"> <div className="w-12 h-12 rounded-full bg-[#FFFFFF] flex items-center justify-center">
<svg className="w-6 h-6 text-[#333]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-6 h-6 text-[#333]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg> </svg>
</div> </div>
<p className="text-[#555] text-sm font-body">No audit log entries found</p> <p className="text-[#555] text-sm font-body">No audit log entries found</p>
{searchQuery && ( {searchQuery && (
<button onClick={() => setSearchQuery('')} className="text-[#F0A623] text-xs font-body hover:underline"> <button onClick={() => setSearchQuery('')} className="text-[#1D4ED8] text-xs font-body hover:underline">
Clear search Clear search
</button> </button>
)} )}
@@ -505,7 +505,7 @@ export default function AuditLogPage() {
{/* Rows */} {/* Rows */}
{!loadingData && filteredLogs.map((log, idx) => { {!loadingData && filteredLogs.map((log, idx) => {
const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#231d18]'; const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#FFFFFF]';
const actionLabel = ACTION_LABELS[log.action] || log.action; const actionLabel = ACTION_LABELS[log.action] || log.action;
const icon = ACTION_ICONS[log.action] || getDefaultIcon(log.action); const icon = ACTION_ICONS[log.action] || getDefaultIcon(log.action);
const performerName = log.user_profiles?.full_name || log.performed_by_email || 'System'; const performerName = log.user_profiles?.full_name || log.performed_by_email || 'System';
@@ -513,8 +513,8 @@ export default function AuditLogPage() {
return ( return (
<div <div
key={log.id} key={log.id}
className={`grid grid-cols-1 md:grid-cols-[2fr_1.5fr_1.5fr_1fr_auto] gap-3 md:gap-4 px-5 py-4 hover:bg-[#1c1815] transition-colors cursor-pointer ${ className={`grid grid-cols-1 md:grid-cols-[2fr_1.5fr_1.5fr_1fr_auto] gap-3 md:gap-4 px-5 py-4 hover:bg-[#F8FAFC] transition-colors cursor-pointer ${
idx !== filteredLogs.length - 1 ? 'border-b border-[#231d18]' : '' idx !== filteredLogs.length - 1 ? 'border-b border-[#FFFFFF]' : ''
}`} }`}
onClick={() => setSelectedLog(log)} onClick={() => setSelectedLog(log)}
> >
@@ -530,7 +530,7 @@ export default function AuditLogPage() {
{/* Performed By */} {/* Performed By */}
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<div className="w-6 h-6 rounded-full bg-[#231d18] border border-[#3a322b] flex items-center justify-center flex-shrink-0"> <div className="w-6 h-6 rounded-full bg-[#FFFFFF] border border-[#DCE6F2] flex items-center justify-center flex-shrink-0">
<span className="text-[#888] text-xs font-body font-bold"> <span className="text-[#888] text-xs font-body font-bold">
{performerName.charAt(0).toUpperCase()} {performerName.charAt(0).toUpperCase()}
</span> </span>
@@ -569,7 +569,7 @@ export default function AuditLogPage() {
<div className="flex items-center"> <div className="flex items-center">
<button <button
onClick={(e) => { e.stopPropagation(); setSelectedLog(log); }} onClick={(e) => { e.stopPropagation(); setSelectedLog(log); }}
className="text-[#444] hover:text-[#F0A623] transition-colors p-1" className="text-[#444] hover:text-[#1D4ED8] transition-colors p-1"
aria-label="View details" aria-label="View details"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -593,7 +593,7 @@ export default function AuditLogPage() {
<button <button
onClick={() => setPage((p) => Math.max(0, p - 1))} onClick={() => setPage((p) => Math.max(0, p - 1))}
disabled={page === 0} disabled={page === 0}
className="px-3 py-1.5 bg-[#111] border border-[#3a322b] rounded-lg text-xs font-body text-[#888] hover:text-white hover:border-[#333] disabled:opacity-30 disabled:cursor-not-allowed transition-all" className="px-3 py-1.5 bg-[#111] border border-[#DCE6F2] rounded-lg text-xs font-body text-[#888] hover:text-white hover:border-[#333] disabled:opacity-30 disabled:cursor-not-allowed transition-all"
> >
Previous Previous
</button> </button>
@@ -601,7 +601,7 @@ export default function AuditLogPage() {
<button <button
onClick={() => setPage((p) => p + 1)} onClick={() => setPage((p) => p + 1)}
disabled={filteredLogs.length < PAGE_SIZE} disabled={filteredLogs.length < PAGE_SIZE}
className="px-3 py-1.5 bg-[#111] border border-[#3a322b] rounded-lg text-xs font-body text-[#888] hover:text-white hover:border-[#333] disabled:opacity-30 disabled:cursor-not-allowed transition-all" className="px-3 py-1.5 bg-[#111] border border-[#DCE6F2] rounded-lg text-xs font-body text-[#888] hover:text-white hover:border-[#333] disabled:opacity-30 disabled:cursor-not-allowed transition-all"
> >
Next Next
</button> </button>

View File

@@ -162,7 +162,7 @@ export default function BannedTermsPage() {
if (loading || loadingData) { if (loading || loadingData) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -190,17 +190,17 @@ export default function BannedTermsPage() {
<p className="text-[#555] text-sm font-body mt-1">Manage restricted company names and keywords visible to administrators only</p> <p className="text-[#555] text-sm font-body mt-1">Manage restricted company names and keywords visible to administrators only</p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={exportCSV} className="flex items-center gap-2 px-3 py-2 bg-[#231d18] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors"> <button onClick={exportCSV} className="flex items-center gap-2 px-3 py-2 bg-[#FFFFFF] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
Export CSV Export CSV
</button> </button>
<button onClick={handleRetroactiveScan} disabled={scanning} <button onClick={handleRetroactiveScan} disabled={scanning}
className="flex items-center gap-2 px-3 py-2 bg-[#231d18] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors disabled:opacity-50"> className="flex items-center gap-2 px-3 py-2 bg-[#FFFFFF] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors disabled:opacity-50">
{scanning ? <div className="w-4 h-4 border-2 border-[#888]/30 border-t-[#888] rounded-full animate-spin" /> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>} {scanning ? <div className="w-4 h-4 border-2 border-[#888]/30 border-t-[#888] rounded-full animate-spin" /> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>}
Scan Published Posts Scan Published Posts
</button> </button>
<button onClick={() => { setForm(EMPTY_FORM); setEditingId(null); setShowAddForm(true); }} <button onClick={() => { setForm(EMPTY_FORM); setEditingId(null); setShowAddForm(true); }}
className="flex items-center gap-2 px-4 py-2 bg-[#F0A623] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors"> className="flex items-center gap-2 px-4 py-2 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg>
Add Term Add Term
</button> </button>
@@ -209,7 +209,7 @@ export default function BannedTermsPage() {
{/* Retroactive scan results */} {/* Retroactive scan results */}
{scanResults !== null && ( {scanResults !== null && (
<div className="mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="mb-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-display font-bold text-white">Retroactive Scan Results {scanResults.length} match{scanResults.length !== 1 ? 'es' : ''} found</h3> <h3 className="text-sm font-display font-bold text-white">Retroactive Scan Results {scanResults.length} match{scanResults.length !== 1 ? 'es' : ''} found</h3>
<button onClick={() => setScanResults(null)} className="text-[#555] hover:text-white text-xs font-body">Dismiss</button> <button onClick={() => setScanResults(null)} className="text-[#555] hover:text-white text-xs font-body">Dismiss</button>
@@ -234,18 +234,18 @@ export default function BannedTermsPage() {
{/* Add/Edit Form */} {/* Add/Edit Form */}
{showAddForm && ( {showAddForm && (
<div className="mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="mb-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<h3 className="text-sm font-display font-bold text-white mb-4">{editingId ? 'Edit Term' : 'Add Banned Term'}</h3> <h3 className="text-sm font-display font-bold text-white mb-4">{editingId ? 'Edit Term' : 'Add Banned Term'}</h3>
<form onSubmit={handleSave} className="grid grid-cols-1 md:grid-cols-2 gap-4"> <form onSubmit={handleSave} className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-xs font-body text-[#888] mb-1.5">Term / Company Name *</label> <label className="block text-xs font-body text-[#888] mb-1.5">Term / Company Name *</label>
<input type="text" value={form.term} onChange={e => setForm(f => ({ ...f, term: e.target.value }))} placeholder="e.g. Sony, Acme Corp" <input type="text" value={form.term} onChange={e => setForm(f => ({ ...f, term: e.target.value }))} placeholder="e.g. Sony, Acme Corp"
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]" required /> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]" required />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Ban Level</label> <label className="block text-xs font-body text-[#888] mb-1.5">Ban Level</label>
<select value={form.ban_level} onChange={e => setForm(f => ({ ...f, ban_level: e.target.value as 'soft' | 'hard' }))} <select value={form.ban_level} onChange={e => setForm(f => ({ ...f, ban_level: e.target.value as 'soft' | 'hard' }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="soft">Soft Ban Flag for review (contributor doesn't know)</option> <option value="soft">Soft Ban Flag for review (contributor doesn't know)</option>
<option value="hard">Hard Ban — Block submission entirely</option> <option value="hard">Hard Ban — Block submission entirely</option>
</select> </select>
@@ -253,7 +253,7 @@ export default function BannedTermsPage() {
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label> <label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label>
<select value={form.site_scope} onChange={e => setForm(f => ({ ...f, site_scope: e.target.value as any }))} <select value={form.site_scope} onChange={e => setForm(f => ({ ...f, site_scope: e.target.value as any }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="both">Both Sites</option> <option value="both">Both Sites</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
@@ -262,15 +262,15 @@ export default function BannedTermsPage() {
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-xs font-body text-[#888] mb-1.5">Internal Notes (not shown to contributors)</label> <label className="block text-xs font-body text-[#888] mb-1.5">Internal Notes (not shown to contributors)</label>
<textarea value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} rows={2} placeholder="Reason for ban, context..." <textarea value={form.notes} onChange={e => setForm(f => ({ ...f, notes: e.target.value }))} rows={2} placeholder="Reason for ban, context..."
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623] resize-none" /> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8] resize-none" />
</div> </div>
<div className="md:col-span-2 flex gap-3"> <div className="md:col-span-2 flex gap-3">
<button type="submit" disabled={saving} <button type="submit" disabled={saving}
className="px-5 py-2 bg-[#F0A623] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50"> className="px-5 py-2 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50">
{saving ? 'Saving...' : editingId ? 'Update Term' : 'Add Term'} {saving ? 'Saving...' : editingId ? 'Update Term' : 'Add Term'}
</button> </button>
<button type="button" onClick={() => { setShowAddForm(false); setEditingId(null); setForm(EMPTY_FORM); }} <button type="button" onClick={() => { setShowAddForm(false); setEditingId(null); setForm(EMPTY_FORM); }}
className="px-4 py-2 bg-[#231d18] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors"> className="px-4 py-2 bg-[#FFFFFF] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors">
Cancel Cancel
</button> </button>
</div> </div>
@@ -281,22 +281,22 @@ export default function BannedTermsPage() {
{/* Filters */} {/* Filters */}
<div className="mb-4 flex flex-wrap gap-3"> <div className="mb-4 flex flex-wrap gap-3">
<input type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} placeholder="Search terms..." <input type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)} placeholder="Search terms..."
className="bg-[#111] border border-[#3a322b] rounded-lg px-3 py-2 text-white text-sm font-body focus:outline-none focus:border-[#F0A623] w-48" /> className="bg-[#111] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8] w-48" />
<select value={filterBanLevel} onChange={e => setFilterBanLevel(e.target.value)} <select value={filterBanLevel} onChange={e => setFilterBanLevel(e.target.value)}
className="bg-[#111] border border-[#3a322b] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="bg-[#111] border border-[#DCE6F2] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="">All Ban Levels</option> <option value="">All Ban Levels</option>
<option value="soft">Soft Ban</option> <option value="soft">Soft Ban</option>
<option value="hard">Hard Ban</option> <option value="hard">Hard Ban</option>
</select> </select>
<select value={filterSiteScope} onChange={e => setFilterSiteScope(e.target.value)} <select value={filterSiteScope} onChange={e => setFilterSiteScope(e.target.value)}
className="bg-[#111] border border-[#3a322b] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="bg-[#111] border border-[#DCE6F2] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="">All Sites</option> <option value="">All Sites</option>
<option value="avbeat">AV Beat</option> <option value="avbeat">AV Beat</option>
<option value="avbeat">AV Beat</option> <option value="avbeat">AV Beat</option>
<option value="both">Both</option> <option value="both">Both</option>
</select> </select>
<select value={filterActive} onChange={e => setFilterActive(e.target.value)} <select value={filterActive} onChange={e => setFilterActive(e.target.value)}
className="bg-[#111] border border-[#3a322b] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="bg-[#111] border border-[#DCE6F2] rounded-lg px-3 py-2 text-[#888] text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="">All Status</option> <option value="">All Status</option>
<option value="true">Active</option> <option value="true">Active</option>
<option value="false">Inactive</option> <option value="false">Inactive</option>
@@ -304,8 +304,8 @@ export default function BannedTermsPage() {
</div> </div>
{/* Terms Table */} {/* Terms Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#231d18] flex items-center justify-between"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex items-center justify-between">
<h2 className="text-sm font-display font-bold text-white">Banned Terms ({terms.length})</h2> <h2 className="text-sm font-display font-bold text-white">Banned Terms ({terms.length})</h2>
</div> </div>
{terms.length === 0 ? ( {terms.length === 0 ? (
@@ -316,15 +316,15 @@ export default function BannedTermsPage() {
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Term', 'Ban Level', 'Site Scope', 'Status', 'Date Added', 'Added By', 'Notes', 'Actions'].map(h => ( {['Term', 'Ban Level', 'Site Scope', 'Status', 'Date Added', 'Added By', 'Notes', 'Actions'].map(h => (
<th key={h} className="px-4 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">{h}</th> <th key={h} className="px-4 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-[#231d18]"> <tbody className="divide-y divide-[#FFFFFF]">
{terms.map(term => ( {terms.map(term => (
<tr key={term.id} className={`hover:bg-[#1c1815] transition-colors ${!term.is_active ? 'opacity-50' : ''}`}> <tr key={term.id} className={`hover:bg-[#F8FAFC] transition-colors ${!term.is_active ? 'opacity-50' : ''}`}>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className="text-sm font-body font-semibold text-white">{term.term}</span> <span className="text-sm font-body font-semibold text-white">{term.term}</span>
</td> </td>
@@ -339,7 +339,7 @@ export default function BannedTermsPage() {
<span className="text-xs text-[#888] font-body capitalize">{term.site_scope === 'both' ? 'Both Sites' : term.site_scope === 'avbeat' ? 'AV Beat' : 'AV Beat'}</span> <span className="text-xs text-[#888] font-body capitalize">{term.site_scope === 'both' ? 'Both Sites' : term.site_scope === 'avbeat' ? 'AV Beat' : 'AV Beat'}</span>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className={`text-xs px-2 py-0.5 rounded-full font-body ${term.is_active ? 'bg-green-500/10 text-green-400' : 'bg-[#231d18] text-[#555]'}`}> <span className={`text-xs px-2 py-0.5 rounded-full font-body ${term.is_active ? 'bg-green-500/10 text-green-400' : 'bg-[#FFFFFF] text-[#555]'}`}>
{term.is_active ? 'Active' : 'Inactive'} {term.is_active ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
@@ -348,7 +348,7 @@ export default function BannedTermsPage() {
<td className="px-4 py-3 text-xs text-[#555] font-body max-w-xs truncate">{term.notes || ''}</td> <td className="px-4 py-3 text-xs text-[#555] font-body max-w-xs truncate">{term.notes || ''}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={() => handleEdit(term)} className="text-xs text-[#F0A623] hover:text-blue-300 font-body transition-colors">Edit</button> <button onClick={() => handleEdit(term)} className="text-xs text-[#1D4ED8] hover:text-blue-300 font-body transition-colors">Edit</button>
<button onClick={() => handleDeactivate(term.id, term.is_active)} disabled={actionLoading === term.id} <button onClick={() => handleDeactivate(term.id, term.is_active)} disabled={actionLoading === term.id}
className="text-xs text-[#888] hover:text-white font-body transition-colors disabled:opacity-50"> className="text-xs text-[#888] hover:text-white font-body transition-colors disabled:opacity-50">
{term.is_active ? 'Deactivate' : 'Reactivate'} {term.is_active ? 'Deactivate' : 'Reactivate'}
@@ -366,7 +366,7 @@ export default function BannedTermsPage() {
</div> </div>
{/* Legend */} {/* Legend */}
<div className="mt-6 bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="mt-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">Ban Level Reference</h3> <h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">Ban Level Reference</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>

View File

@@ -54,7 +54,7 @@ function StatBlock({ label, b }: { label: string; b: StatBucket }) {
<span className="font-semibold">{fmtNum(b.clicks)}</span> <span className="font-semibold">{fmtNum(b.clicks)}</span>
<span className="text-[#6b7280]"> clk</span> <span className="text-[#6b7280]"> clk</span>
<span className="mx-1 text-[#374151]">·</span> <span className="mx-1 text-[#374151]">·</span>
<span className="text-[#F0A623]">{pct(b.clicks, b.impressions)}</span> <span className="text-[#1D4ED8]">{pct(b.clicks, b.impressions)}</span>
</div> </div>
</div> </div>
); );
@@ -140,7 +140,7 @@ export default function BannerRows({
<input <input
value={current.name} value={current.name}
onChange={(e) => field(b.id, "name", e.target.value)} onChange={(e) => field(b.id, "name", e.target.value)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#F0A623] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#1D4ED8] focus:outline-none"
/> />
</label> </label>
@@ -150,7 +150,7 @@ export default function BannerRows({
value={current.click_url || ""} value={current.click_url || ""}
onChange={(e) => field(b.id, "click_url", e.target.value)} onChange={(e) => field(b.id, "click_url", e.target.value)}
placeholder="https://…" placeholder="https://…"
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#F0A623] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#1D4ED8] focus:outline-none"
/> />
</label> </label>
@@ -160,7 +160,7 @@ export default function BannerRows({
type="date" type="date"
value={fmtDate(current.start_date)} value={fmtDate(current.start_date)}
onChange={(e) => field(b.id, "start_date", e.target.value ? new Date(e.target.value).toISOString() : null)} onChange={(e) => field(b.id, "start_date", e.target.value ? new Date(e.target.value).toISOString() : null)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#F0A623] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#1D4ED8] focus:outline-none"
/> />
</label> </label>
@@ -170,7 +170,7 @@ export default function BannerRows({
type="date" type="date"
value={fmtDate(current.end_date)} value={fmtDate(current.end_date)}
onChange={(e) => field(b.id, "end_date", e.target.value ? new Date(e.target.value).toISOString() : null)} onChange={(e) => field(b.id, "end_date", e.target.value ? new Date(e.target.value).toISOString() : null)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#F0A623] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#1D4ED8] focus:outline-none"
/> />
</label> </label>
@@ -179,7 +179,7 @@ export default function BannerRows({
<select <select
value={current.status} value={current.status}
onChange={(e) => field(b.id, "status", e.target.value)} onChange={(e) => field(b.id, "status", e.target.value)}
className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#F0A623] focus:outline-none" className="rounded border border-[#1f2937] bg-[#070a10] p-2 text-[#e5e7eb] focus:border-[#1D4ED8] focus:outline-none"
> >
<option value="active">active</option> <option value="active">active</option>
<option value="scheduled">scheduled</option> <option value="scheduled">scheduled</option>
@@ -210,7 +210,7 @@ export default function BannerRows({
<button <button
onClick={() => save(b.id)} onClick={() => save(b.id)}
disabled={!dirty || pending} disabled={!dirty || pending}
className="rounded bg-[#1d4ed8] px-3 py-1.5 text-sm font-medium text-white hover:bg-[#BA7517] disabled:opacity-40 disabled:cursor-not-allowed" className="rounded bg-[#1d4ed8] px-3 py-1.5 text-sm font-medium text-white hover:bg-[#1E3A8A] disabled:opacity-40 disabled:cursor-not-allowed"
> >
Save changes Save changes
</button> </button>
@@ -219,7 +219,7 @@ export default function BannerRows({
</div> </div>
<Link <Link
href={`/admin/banners/${b.id}/analytics`} href={`/admin/banners/${b.id}/analytics`}
className="text-sm text-[#F0A623] hover:underline" className="text-sm text-[#1D4ED8] hover:underline"
> >
View detailed analytics View detailed analytics
</Link> </Link>

View File

@@ -68,7 +68,7 @@ export default function AnalyticsCharts({
</div> </div>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <section className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<ChartCard title="Impressions per day" data={imp} stroke="#F0A623" /> <ChartCard title="Impressions per day" data={imp} stroke="#1D4ED8" />
<ChartCard title="Clicks per day" data={clk} stroke="#34d399" /> <ChartCard title="Clicks per day" data={clk} stroke="#34d399" />
</section> </section>
@@ -121,7 +121,7 @@ function TopListCard({ title, rows }: { title: string; rows: TopRow[] }) {
{rows.map((r) => ( {rows.map((r) => (
<tr key={r.label} className="border-b border-[#1f2937] last:border-0"> <tr key={r.label} className="border-b border-[#1f2937] last:border-0">
<td className="py-2 pr-3 text-[#e5e7eb] truncate max-w-[400px]" title={r.label}>{r.label}</td> <td className="py-2 pr-3 text-[#e5e7eb] truncate max-w-[400px]" title={r.label}>{r.label}</td>
<td className="py-2 text-right text-[#F0A623]">{r.count.toLocaleString()}</td> <td className="py-2 text-right text-[#1D4ED8]">{r.count.toLocaleString()}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@@ -142,7 +142,7 @@ export default async function BannerAnalyticsPage({
return ( return (
<main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]"> <main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]">
<Link href="/admin/banners" className="text-sm text-[#F0A623] hover:underline"> <Link href="/admin/banners" className="text-sm text-[#1D4ED8] hover:underline">
Back to banners Back to banners
</Link> </Link>
@@ -159,7 +159,7 @@ export default async function BannerAnalyticsPage({
<p className="text-sm text-[#9ca3af]"> <p className="text-sm text-[#9ca3af]">
{b.size} · {b.status} ·{" "} {b.size} · {b.status} ·{" "}
{b.click_url && ( {b.click_url && (
<a className="text-[#F0A623] hover:underline" href={b.click_url} target="_blank" rel="noopener noreferrer"> <a className="text-[#1D4ED8] hover:underline" href={b.click_url} target="_blank" rel="noopener noreferrer">
{b.click_url.slice(0, 80)}{b.click_url.length > 80 ? "…" : ""} {b.click_url.slice(0, 80)}{b.click_url.length > 80 ? "…" : ""}
</a> </a>
)} )}

View File

@@ -113,7 +113,7 @@ export default async function AdminBannersPage() {
after the next refresh. after the next refresh.
</p> </p>
</div> </div>
<Link href="/admin" className="text-sm text-[#F0A623] hover:underline"> <Link href="/admin" className="text-sm text-[#1D4ED8] hover:underline">
Admin home Admin home
</Link> </Link>
</header> </header>

View File

@@ -171,7 +171,7 @@ export default function CompanyCoveragePage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading company coverage</p> <p className="text-[#555] text-sm font-body">Loading company coverage</p>
</div> </div>
</div> </div>
@@ -218,7 +218,7 @@ export default function CompanyCoveragePage() {
<button <button
onClick={handleScan} onClick={handleScan}
disabled={scanning} disabled={scanning}
className="flex items-center gap-2 px-4 py-2 bg-[#F0A623] hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-body font-semibold rounded-lg transition-colors" className="flex items-center gap-2 px-4 py-2 bg-[#1D4ED8] hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-body font-semibold rounded-lg transition-colors"
> >
{scanning ? ( {scanning ? (
<> <>
@@ -255,14 +255,14 @@ export default function CompanyCoveragePage() {
<div className="lg:col-span-2"> <div className="lg:col-span-2">
{/* Tabs */} {/* Tabs */}
<div className="flex gap-1 mb-4 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit"> <div className="flex gap-1 mb-4 bg-[#111] border border-[#DCE6F2] rounded-lg p-1 w-fit">
{(['queued', 'published', 'dismissed'] as const).map((tab) => ( {(['queued', 'published', 'dismissed'] as const).map((tab) => (
<button <button
key={tab} key={tab}
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-4 py-1.5 rounded-md text-sm font-body font-semibold capitalize transition-colors ${ className={`px-4 py-1.5 rounded-md text-sm font-body font-semibold capitalize transition-colors ${
activeTab === tab activeTab === tab
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -272,7 +272,7 @@ export default function CompanyCoveragePage() {
</div> </div>
{/* Queue List */} {/* Queue List */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{queue.length === 0 ? ( {queue.length === 0 ? (
<div className="px-5 py-16 text-center"> <div className="px-5 py-16 text-center">
<svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24"> <svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
@@ -282,13 +282,13 @@ export default function CompanyCoveragePage() {
{activeTab === 'queued' ? 'No stories queued — run a scan to detect companies' : `No ${activeTab} stories`} {activeTab === 'queued' ? 'No stories queued — run a scan to detect companies' : `No ${activeTab} stories`}
</p> </p>
{activeTab === 'queued' && ( {activeTab === 'queued' && (
<button onClick={handleScan} disabled={scanning} className="mt-3 text-xs text-[#F0A623] hover:text-blue-300 font-body underline"> <button onClick={handleScan} disabled={scanning} className="mt-3 text-xs text-[#1D4ED8] hover:text-blue-300 font-body underline">
Run scan now Run scan now
</button> </button>
)} )}
</div> </div>
) : ( ) : (
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{queue.map((item) => ( {queue.map((item) => (
<div key={item.id} className="p-4"> <div key={item.id} className="p-4">
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
@@ -332,7 +332,7 @@ export default function CompanyCoveragePage() {
</button> </button>
<button <button
onClick={() => setExpandedItem(expandedItem === item.id ? null : item.id)} onClick={() => setExpandedItem(expandedItem === item.id ? null : item.id)}
className="px-3 py-1.5 bg-[#231d18] hover:bg-[#222] text-[#888] text-xs font-body rounded-md transition-colors" className="px-3 py-1.5 bg-[#FFFFFF] hover:bg-[#222] text-[#888] text-xs font-body rounded-md transition-colors"
> >
{expandedItem === item.id ? 'Hide' : 'Preview'} {expandedItem === item.id ? 'Hide' : 'Preview'}
</button> </button>
@@ -349,7 +349,7 @@ export default function CompanyCoveragePage() {
{/* Expanded Draft Preview */} {/* Expanded Draft Preview */}
{expandedItem === item.id && ( {expandedItem === item.id && (
<div className="mt-4 bg-[#1c1815] border border-[#231d18] rounded-lg p-4"> <div className="mt-4 bg-[#F8FAFC] border border-[#FFFFFF] rounded-lg p-4">
<p className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">AI Draft Preview</p> <p className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">AI Draft Preview</p>
<h3 className="text-base font-display font-bold text-white mb-2">{item.ai_draft_title}</h3> <h3 className="text-base font-display font-bold text-white mb-2">{item.ai_draft_title}</h3>
<p className="text-sm text-[#888] font-body italic mb-3">{item.ai_draft_excerpt}</p> <p className="text-sm text-[#888] font-body italic mb-3">{item.ai_draft_excerpt}</p>
@@ -370,11 +370,11 @@ export default function CompanyCoveragePage() {
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{/* Auto-Pilot Toggle */} {/* Auto-Pilot Toggle */}
<div className={`bg-[#111] border rounded-lg p-5 transition-colors ${settings.autopilot_enabled ? 'border-green-500/40' : 'border-[#3a322b]'}`}> <div className={`bg-[#111] border rounded-lg p-5 transition-colors ${settings.autopilot_enabled ? 'border-green-500/40' : 'border-[#DCE6F2]'}`}>
<div className="flex items-start justify-between gap-3 mb-3"> <div className="flex items-start justify-between gap-3 mb-3">
<div> <div>
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<svg className="w-4 h-4 text-[#F0A623]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#1D4ED8]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg> </svg>
<h3 className="text-sm font-display font-bold text-white">Auto-Pilot</h3> <h3 className="text-sm font-display font-bold text-white">Auto-Pilot</h3>
@@ -405,7 +405,7 @@ export default function CompanyCoveragePage() {
<p className="text-xs text-green-400 font-body font-semibold">Auto-Pilot is ON stories publish automatically</p> <p className="text-xs text-green-400 font-body font-semibold">Auto-Pilot is ON stories publish automatically</p>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-2 bg-[#231d18] rounded-md px-3 py-2"> <div className="flex items-center gap-2 bg-[#FFFFFF] rounded-md px-3 py-2">
<div className="w-2 h-2 rounded-full bg-[#444] flex-shrink-0" /> <div className="w-2 h-2 rounded-full bg-[#444] flex-shrink-0" />
<p className="text-xs text-[#666] font-body">Manual review required before publishing</p> <p className="text-xs text-[#666] font-body">Manual review required before publishing</p>
</div> </div>
@@ -413,7 +413,7 @@ export default function CompanyCoveragePage() {
</div> </div>
{/* Reminder Settings */} {/* Reminder Settings */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<svg className="w-4 h-4 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
@@ -421,14 +421,14 @@ export default function CompanyCoveragePage() {
<h3 className="text-sm font-display font-bold text-white">Reminder Email</h3> <h3 className="text-sm font-display font-bold text-white">Reminder Email</h3>
</div> </div>
<p className="text-xs text-[#555] font-body mb-2">New story suggestions are emailed to:</p> <p className="text-xs text-[#555] font-body mb-2">New story suggestions are emailed to:</p>
<div className="bg-[#1c1815] border border-[#231d18] rounded-md px-3 py-2"> <div className="bg-[#F8FAFC] border border-[#FFFFFF] rounded-md px-3 py-2">
<p className="text-xs text-[#F0A623] font-body font-mono break-all">{settings.reminder_email}</p> <p className="text-xs text-[#1D4ED8] font-body font-mono break-all">{settings.reminder_email}</p>
</div> </div>
<p className="text-xs text-[#444] font-body mt-2">Reminders are sent when Auto-Pilot is off and new companies are detected.</p> <p className="text-xs text-[#444] font-body mt-2">Reminders are sent when Auto-Pilot is off and new companies are detected.</p>
</div> </div>
{/* Last Scan Info */} {/* Last Scan Info */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<svg className="w-4 h-4 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path strokeLinecap="round" d="M12 6v6l4 2" /> <circle cx="12" cy="12" r="10" /><path strokeLinecap="round" d="M12 6v6l4 2" />
@@ -456,7 +456,7 @@ export default function CompanyCoveragePage() {
</div> </div>
{/* How It Works */} {/* How It Works */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">How It Works</h3> <h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">How It Works</h3>
<ol className="space-y-2"> <ol className="space-y-2">
{[ {[
@@ -467,7 +467,7 @@ export default function CompanyCoveragePage() {
'Email reminder sent to coverage contact', 'Email reminder sent to coverage contact',
].map((step, i) => ( ].map((step, i) => (
<li key={i} className="flex items-start gap-2"> <li key={i} className="flex items-start gap-2">
<span className="flex-shrink-0 w-4 h-4 rounded-full bg-[#231d18] text-[#555] text-xs font-body font-bold flex items-center justify-center mt-0.5"> <span className="flex-shrink-0 w-4 h-4 rounded-full bg-[#FFFFFF] text-[#555] text-xs font-body font-bold flex items-center justify-center mt-0.5">
{i + 1} {i + 1}
</span> </span>
<span className="text-xs text-[#666] font-body leading-relaxed">{step}</span> <span className="text-xs text-[#666] font-body leading-relaxed">{step}</span>

View File

@@ -245,7 +245,7 @@ export default function CompanyTrackerPage() {
if (loading || loadingData) { if (loading || loadingData) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -279,7 +279,7 @@ export default function CompanyTrackerPage() {
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Auto-Pilot toggle */} {/* Auto-Pilot toggle */}
<div className="flex items-center gap-3 bg-[#111] border border-[#3a322b] rounded-lg px-4 py-2.5"> <div className="flex items-center gap-3 bg-[#111] border border-[#DCE6F2] rounded-lg px-4 py-2.5">
<div> <div>
<div className="text-xs font-body font-bold text-white">Auto-Pilot</div> <div className="text-xs font-body font-bold text-white">Auto-Pilot</div>
<div className="text-xs text-[#555] font-body">{settings.autopilot_enabled ? 'Stories auto-publish' : 'Manual review'}</div> <div className="text-xs text-[#555] font-body">{settings.autopilot_enabled ? 'Stories auto-publish' : 'Manual review'}</div>
@@ -291,13 +291,13 @@ export default function CompanyTrackerPage() {
await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'update_settings', autopilot_enabled: newVal, reminder_email: settings.reminder_email }) }); await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'update_settings', autopilot_enabled: newVal, reminder_email: settings.reminder_email }) });
showToast(newVal ? '🚀 Auto-Pilot enabled' : '⏸ Auto-Pilot disabled'); showToast(newVal ? '🚀 Auto-Pilot enabled' : '⏸ Auto-Pilot disabled');
}} }}
className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#F0A623]' : 'bg-[#333]'}`} className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#1D4ED8]' : 'bg-[#333]'}`}
> >
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
<button onClick={handleScan} disabled={scanning} <button onClick={handleScan} disabled={scanning}
className="flex items-center gap-2 px-4 py-2 bg-[#F0A623] hover:bg-blue-500 disabled:opacity-50 text-white text-sm font-body font-semibold rounded-lg transition-colors"> className="flex items-center gap-2 px-4 py-2 bg-[#1D4ED8] hover:bg-blue-500 disabled:opacity-50 text-white text-sm font-body font-semibold rounded-lg transition-colors">
{scanning ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>} {scanning ? <div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" /> : <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>}
{scanning ? 'Scanning…' : 'Scan Articles Now'} {scanning ? 'Scanning…' : 'Scan Articles Now'}
</button> </button>
@@ -313,7 +313,7 @@ export default function CompanyTrackerPage() {
{ label: 'Companies Found', value: settings.last_scan_companies_found }, { label: 'Companies Found', value: settings.last_scan_companies_found },
{ label: 'Tracked Companies', value: companies.length || '—' }, { label: 'Tracked Companies', value: companies.length || '—' },
].map(stat => ( ].map(stat => (
<div key={stat.label} className="bg-[#111] border border-[#3a322b] rounded-lg px-4 py-3"> <div key={stat.label} className="bg-[#111] border border-[#DCE6F2] rounded-lg px-4 py-3">
<div className="text-lg font-display font-bold text-white">{stat.value}</div> <div className="text-lg font-display font-bold text-white">{stat.value}</div>
<div className="text-xs text-[#555] font-body">{stat.label}</div> <div className="text-xs text-[#555] font-body">{stat.label}</div>
</div> </div>
@@ -322,10 +322,10 @@ export default function CompanyTrackerPage() {
)} )}
{/* Tabs */} {/* Tabs */}
<div className="flex gap-1 mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit"> <div className="flex gap-1 mb-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-1 w-fit">
{tabs.map(tab => ( {tabs.map(tab => (
<button key={tab.id} onClick={() => setActiveTab(tab.id)} <button key={tab.id} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 rounded-md text-sm font-body font-medium transition-colors ${activeTab === tab.id ? 'bg-[#231d18] text-white' : 'text-[#555] hover:text-[#888]'}`}> className={`px-4 py-2 rounded-md text-sm font-body font-medium transition-colors ${activeTab === tab.id ? 'bg-[#FFFFFF] text-white' : 'text-[#555] hover:text-[#888]'}`}>
{tab.label} {tab.label}
</button> </button>
))} ))}
@@ -337,12 +337,12 @@ export default function CompanyTrackerPage() {
<div className="flex flex-wrap gap-3 mb-4"> <div className="flex flex-wrap gap-3 mb-4">
{(['generated','pending','approved','published','rejected'] as QueueStatus[]).map(s => ( {(['generated','pending','approved','published','rejected'] as QueueStatus[]).map(s => (
<button key={s} onClick={() => setQueueStatus(s)} <button key={s} onClick={() => setQueueStatus(s)}
className={`px-3 py-1.5 rounded-lg text-xs font-body font-medium transition-colors capitalize ${queueStatus === s ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#555] hover:text-white'}`}> className={`px-3 py-1.5 rounded-lg text-xs font-body font-medium transition-colors capitalize ${queueStatus === s ? 'bg-[#1D4ED8] text-white' : 'bg-[#111] border border-[#DCE6F2] text-[#555] hover:text-white'}`}>
{s === 'generated' ? 'Pending Review' : s} {s === 'generated' ? 'Pending Review' : s}
</button> </button>
))} ))}
<select value={queueSiteFilter} onChange={e => setQueueSiteFilter(e.target.value)} <select value={queueSiteFilter} onChange={e => setQueueSiteFilter(e.target.value)}
className="bg-[#111] border border-[#3a322b] rounded-lg px-3 py-1.5 text-[#555] text-xs font-body focus:outline-none"> className="bg-[#111] border border-[#DCE6F2] rounded-lg px-3 py-1.5 text-[#555] text-xs font-body focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
<option value="1">AV Beat</option> <option value="1">AV Beat</option>
<option value="2">AV Beat</option> <option value="2">AV Beat</option>
@@ -350,18 +350,18 @@ export default function CompanyTrackerPage() {
</div> </div>
{queue.length === 0 ? ( {queue.length === 0 ? (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-12 text-center"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-12 text-center">
<p className="text-[#555] text-sm font-body">No stories in this queue. Run a scan to generate new story suggestions.</p> <p className="text-[#555] text-sm font-body">No stories in this queue. Run a scan to generate new story suggestions.</p>
</div> </div>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{queue.map(item => ( {queue.map(item => (
<div key={item.id} className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div key={item.id} className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 flex items-start gap-4"> <div className="px-5 py-4 flex items-start gap-4">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap"> <div className="flex items-center gap-2 mb-1 flex-wrap">
<SiteBadge siteId={item.site_id} /> <SiteBadge siteId={item.site_id} />
<span className="text-xs px-2 py-0.5 rounded bg-[#231d18] text-[#888] font-body capitalize">{item.story_type.replace(/_/g, ' ')}</span> <span className="text-xs px-2 py-0.5 rounded bg-[#FFFFFF] text-[#888] font-body capitalize">{item.story_type.replace(/_/g, ' ')}</span>
{item.quality_confidence && ( {item.quality_confidence && (
<span className={`text-xs px-2 py-0.5 rounded font-body ${item.quality_confidence >= 0.85 ? 'bg-green-500/10 text-green-400' : 'bg-yellow-500/10 text-yellow-400'}`}> <span className={`text-xs px-2 py-0.5 rounded font-body ${item.quality_confidence >= 0.85 ? 'bg-green-500/10 text-green-400' : 'bg-yellow-500/10 text-yellow-400'}`}>
{Math.round(item.quality_confidence * 100)}% confidence {Math.round(item.quality_confidence * 100)}% confidence
@@ -398,7 +398,7 @@ export default function CompanyTrackerPage() {
</div> </div>
</div> </div>
{expandedItem === item.id && item.generated_content && ( {expandedItem === item.id && item.generated_content && (
<div className="px-5 pb-5 border-t border-[#231d18]"> <div className="px-5 pb-5 border-t border-[#FFFFFF]">
<div className="mt-4 bg-[#0a0a0a] rounded-lg p-4 text-sm text-[#aaa] font-body leading-relaxed max-h-64 overflow-y-auto" <div className="mt-4 bg-[#0a0a0a] rounded-lg p-4 text-sm text-[#aaa] font-body leading-relaxed max-h-64 overflow-y-auto"
dangerouslySetInnerHTML={{ __html: item.generated_content }} /> dangerouslySetInnerHTML={{ __html: item.generated_content }} />
<div className="mt-3 flex items-center gap-3"> <div className="mt-3 flex items-center gap-3">
@@ -430,30 +430,30 @@ export default function CompanyTrackerPage() {
<div> <div>
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<p className="text-sm text-[#555] font-body">{companies.length} companies tracked</p> <p className="text-sm text-[#555] font-body">{companies.length} companies tracked</p>
<button onClick={() => setShowAddCompany(true)} className="flex items-center gap-2 px-4 py-2 bg-[#F0A623] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors"> <button onClick={() => setShowAddCompany(true)} className="flex items-center gap-2 px-4 py-2 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /></svg>
Add Company Add Company
</button> </button>
</div> </div>
{showAddCompany && ( {showAddCompany && (
<div className="mb-4 bg-[#111] border border-[#3a322b] rounded-lg p-5"> <div className="mb-4 bg-[#111] border border-[#DCE6F2] rounded-lg p-5">
<h3 className="text-sm font-display font-bold text-white mb-4">Add Company Manually</h3> <h3 className="text-sm font-display font-bold text-white mb-4">Add Company Manually</h3>
<form onSubmit={handleAddCompany} className="grid grid-cols-1 md:grid-cols-2 gap-4"> <form onSubmit={handleAddCompany} className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Company Name *</label> <label className="block text-xs font-body text-[#888] mb-1.5">Company Name *</label>
<input type="text" value={addCompanyForm.company_name} onChange={e => setAddCompanyForm(f => ({ ...f, company_name: e.target.value }))} required <input type="text" value={addCompanyForm.company_name} onChange={e => setAddCompanyForm(f => ({ ...f, company_name: e.target.value }))} required
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]" /> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Website URL</label> <label className="block text-xs font-body text-[#888] mb-1.5">Website URL</label>
<input type="text" value={addCompanyForm.company_website} onChange={e => setAddCompanyForm(f => ({ ...f, company_website: e.target.value }))} placeholder="example.com" <input type="text" value={addCompanyForm.company_website} onChange={e => setAddCompanyForm(f => ({ ...f, company_website: e.target.value }))} placeholder="example.com"
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]" /> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label> <label className="block text-xs font-body text-[#888] mb-1.5">Site Scope</label>
<select value={addCompanyForm.site_scope} onChange={e => setAddCompanyForm(f => ({ ...f, site_scope: e.target.value }))} <select value={addCompanyForm.site_scope} onChange={e => setAddCompanyForm(f => ({ ...f, site_scope: e.target.value }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="both">Both Sites</option> <option value="both">Both Sites</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
<option value="avbeat">AV Beat only</option> <option value="avbeat">AV Beat only</option>
@@ -462,51 +462,51 @@ export default function CompanyTrackerPage() {
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Crawl Priority</label> <label className="block text-xs font-body text-[#888] mb-1.5">Crawl Priority</label>
<select value={addCompanyForm.crawl_priority} onChange={e => setAddCompanyForm(f => ({ ...f, crawl_priority: e.target.value }))} <select value={addCompanyForm.crawl_priority} onChange={e => setAddCompanyForm(f => ({ ...f, crawl_priority: e.target.value }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]"> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]">
<option value="standard">Standard (weekly)</option> <option value="standard">Standard (weekly)</option>
<option value="high">High (every 3 days)</option> <option value="high">High (every 3 days)</option>
<option value="critical">Critical (daily)</option> <option value="critical">Critical (daily)</option>
</select> </select>
</div> </div>
<div className="md:col-span-2 flex gap-3"> <div className="md:col-span-2 flex gap-3">
<button type="submit" className="px-5 py-2 bg-[#F0A623] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">Add Company</button> <button type="submit" className="px-5 py-2 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors">Add Company</button>
<button type="button" onClick={() => setShowAddCompany(false)} className="px-4 py-2 bg-[#231d18] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors">Cancel</button> <button type="button" onClick={() => setShowAddCompany(false)} className="px-4 py-2 bg-[#FFFFFF] border border-[#333] text-[#888] hover:text-white text-sm font-body rounded-lg transition-colors">Cancel</button>
</div> </div>
</form> </form>
</div> </div>
)} )}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Company', 'Website', 'Mentions', 'Sites', 'Auto-Stories', 'Priority', 'Last Crawl', 'Actions'].map(h => ( {['Company', 'Website', 'Mentions', 'Sites', 'Auto-Stories', 'Priority', 'Last Crawl', 'Actions'].map(h => (
<th key={h} className="px-4 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">{h}</th> <th key={h} className="px-4 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-[#231d18]"> <tbody className="divide-y divide-[#FFFFFF]">
{companies.length === 0 ? ( {companies.length === 0 ? (
<tr><td colSpan={8} className="px-4 py-12 text-center text-[#555] text-sm font-body">No companies tracked yet. Run a scan to auto-detect companies from published articles.</td></tr> <tr><td colSpan={8} className="px-4 py-12 text-center text-[#555] text-sm font-body">No companies tracked yet. Run a scan to auto-detect companies from published articles.</td></tr>
) : companies.map(company => ( ) : companies.map(company => (
<tr key={company.id} className="hover:bg-[#1c1815] transition-colors"> <tr key={company.id} className="hover:bg-[#F8FAFC] transition-colors">
<td className="px-4 py-3 text-sm font-body font-semibold text-white">{company.company_name}</td> <td className="px-4 py-3 text-sm font-body font-semibold text-white">{company.company_name}</td>
<td className="px-4 py-3 text-xs text-[#555] font-body">{company.company_website ? <a href={`https://${company.company_website}`} target="_blank" rel="noopener noreferrer" className="text-[#F0A623] hover:underline">{company.company_website}</a> : '—'}</td> <td className="px-4 py-3 text-xs text-[#555] font-body">{company.company_website ? <a href={`https://${company.company_website}`} target="_blank" rel="noopener noreferrer" className="text-[#1D4ED8] hover:underline">{company.company_website}</a> : '—'}</td>
<td className="px-4 py-3 text-sm font-body text-white font-bold">{company.mention_count}</td> <td className="px-4 py-3 text-sm font-body text-white font-bold">{company.mention_count}</td>
<td className="px-4 py-3 text-xs text-[#888] font-body capitalize">{company.site_scope === 'both' ? 'Both' : company.site_scope === 'avbeat' ? 'BB' : 'AV'}</td> <td className="px-4 py-3 text-xs text-[#888] font-body capitalize">{company.site_scope === 'both' ? 'Both' : company.site_scope === 'avbeat' ? 'BB' : 'AV'}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<button onClick={() => handleToggleCompanyAutoStory(company.id, !company.auto_story_enabled)} disabled={actionLoading === company.id} <button onClick={() => handleToggleCompanyAutoStory(company.id, !company.auto_story_enabled)} disabled={actionLoading === company.id}
className={`relative w-9 h-5 rounded-full transition-colors ${company.auto_story_enabled ? 'bg-[#F0A623]' : 'bg-[#333]'}`}> className={`relative w-9 h-5 rounded-full transition-colors ${company.auto_story_enabled ? 'bg-[#1D4ED8]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${company.auto_story_enabled ? 'translate-x-4' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${company.auto_story_enabled ? 'translate-x-4' : 'translate-x-0'}`} />
</button> </button>
</td> </td>
<td className="px-4 py-3"><span className={`text-xs px-2 py-0.5 rounded font-body capitalize ${company.crawl_priority === 'critical' ? 'bg-red-500/10 text-red-400' : company.crawl_priority === 'high' ? 'bg-yellow-500/10 text-yellow-400' : 'bg-[#231d18] text-[#555]'}`}>{company.crawl_priority}</span></td> <td className="px-4 py-3"><span className={`text-xs px-2 py-0.5 rounded font-body capitalize ${company.crawl_priority === 'critical' ? 'bg-red-500/10 text-red-400' : company.crawl_priority === 'high' ? 'bg-yellow-500/10 text-yellow-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{company.crawl_priority}</span></td>
<td className="px-4 py-3 text-xs text-[#555] font-body">{company.last_crawled ? timeAgo(company.last_crawled) : 'Never'}</td> <td className="px-4 py-3 text-xs text-[#555] font-body">{company.last_crawled ? timeAgo(company.last_crawled) : 'Never'}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={async () => { await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'force_crawl', company_id: company.id }) }); showToast('Crawl scheduled'); }} <button onClick={async () => { await fetch('/api/admin/company-coverage', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'force_crawl', company_id: company.id }) }); showToast('Crawl scheduled'); }}
className="text-xs text-[#F0A623] hover:text-blue-300 font-body transition-colors">Force Crawl</button> className="text-xs text-[#1D4ED8] hover:text-blue-300 font-body transition-colors">Force Crawl</button>
<button onClick={() => handleDeleteCompany(company.id)} disabled={actionLoading === company.id} <button onClick={() => handleDeleteCompany(company.id)} disabled={actionLoading === company.id}
className="text-xs text-red-500 hover:text-red-400 font-body transition-colors disabled:opacity-50">Remove</button> className="text-xs text-red-500 hover:text-red-400 font-body transition-colors disabled:opacity-50">Remove</button>
</div> </div>
@@ -522,21 +522,21 @@ export default function CompanyTrackerPage() {
{/* ── Crawl Log Tab ── */} {/* ── Crawl Log Tab ── */}
{activeTab === 'crawl-log' && ( {activeTab === 'crawl-log' && (
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Date', 'Company', 'Source URL', 'Items Found', 'Stories Queued', 'Status'].map(h => ( {['Date', 'Company', 'Source URL', 'Items Found', 'Stories Queued', 'Status'].map(h => (
<th key={h} className="px-4 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">{h}</th> <th key={h} className="px-4 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-[#231d18]"> <tbody className="divide-y divide-[#FFFFFF]">
{crawlLogs.length === 0 ? ( {crawlLogs.length === 0 ? (
<tr><td colSpan={6} className="px-4 py-12 text-center text-[#555] text-sm font-body">No crawl history yet.</td></tr> <tr><td colSpan={6} className="px-4 py-12 text-center text-[#555] text-sm font-body">No crawl history yet.</td></tr>
) : crawlLogs.map(log => ( ) : crawlLogs.map(log => (
<tr key={log.id} className="hover:bg-[#1c1815] transition-colors"> <tr key={log.id} className="hover:bg-[#F8FAFC] transition-colors">
<td className="px-4 py-3 text-xs text-[#555] font-body">{new Date(log.crawled_at).toLocaleString()}</td> <td className="px-4 py-3 text-xs text-[#555] font-body">{new Date(log.crawled_at).toLocaleString()}</td>
<td className="px-4 py-3 text-sm text-white font-body">{log.company_name}</td> <td className="px-4 py-3 text-sm text-white font-body">{log.company_name}</td>
<td className="px-4 py-3 text-xs text-[#555] font-body truncate max-w-xs">{log.source_url || '—'}</td> <td className="px-4 py-3 text-xs text-[#555] font-body truncate max-w-xs">{log.source_url || '—'}</td>
@@ -558,7 +558,7 @@ export default function CompanyTrackerPage() {
{/* ── Settings Tab ── */} {/* ── Settings Tab ── */}
{activeTab === 'settings' && ( {activeTab === 'settings' && (
<div className="max-w-2xl space-y-6"> <div className="max-w-2xl space-y-6">
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<h3 className="text-sm font-display font-bold text-white mb-4">Auto-Pilot & Reminders</h3> <h3 className="text-sm font-display font-bold text-white mb-4">Auto-Pilot & Reminders</h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -567,19 +567,19 @@ export default function CompanyTrackerPage() {
<div className="text-xs text-[#555] font-body mt-0.5">When enabled, high-confidence stories publish automatically without review</div> <div className="text-xs text-[#555] font-body mt-0.5">When enabled, high-confidence stories publish automatically without review</div>
</div> </div>
<button onClick={() => setSettings(s => ({ ...s, autopilot_enabled: !s.autopilot_enabled }))} <button onClick={() => setSettings(s => ({ ...s, autopilot_enabled: !s.autopilot_enabled }))}
className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#F0A623]' : 'bg-[#333]'}`}> className={`relative w-11 h-6 rounded-full transition-colors ${settings.autopilot_enabled ? 'bg-[#1D4ED8]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${settings.autopilot_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Reminder Email</label> <label className="block text-xs font-body text-[#888] mb-1.5">Reminder Email</label>
<input type="email" value={settings.reminder_email} onChange={e => setSettings(s => ({ ...s, reminder_email: e.target.value }))} <input type="email" value={settings.reminder_email} onChange={e => setSettings(s => ({ ...s, reminder_email: e.target.value }))}
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]" /> className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<h3 className="text-sm font-display font-bold text-white mb-4">Story Generation Settings</h3> <h3 className="text-sm font-display font-bold text-white mb-4">Story Generation Settings</h3>
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -588,7 +588,7 @@ export default function CompanyTrackerPage() {
<div className="text-xs text-[#555] font-body mt-0.5">Master switch for all automated story generation</div> <div className="text-xs text-[#555] font-body mt-0.5">Master switch for all automated story generation</div>
</div> </div>
<button onClick={() => setTrackerSettings(s => ({ ...s, auto_story_global_enabled: !s.auto_story_global_enabled }))} <button onClick={() => setTrackerSettings(s => ({ ...s, auto_story_global_enabled: !s.auto_story_global_enabled }))}
className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_story_global_enabled ? 'bg-[#F0A623]' : 'bg-[#333]'}`}> className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_story_global_enabled ? 'bg-[#1D4ED8]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_story_global_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_story_global_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
@@ -598,25 +598,25 @@ export default function CompanyTrackerPage() {
<div className="text-xs text-[#555] font-body mt-0.5">Publish stories with confidence {Math.round(trackerSettings.auto_publish_confidence_threshold * 100)}% automatically</div> <div className="text-xs text-[#555] font-body mt-0.5">Publish stories with confidence {Math.round(trackerSettings.auto_publish_confidence_threshold * 100)}% automatically</div>
</div> </div>
<button onClick={() => setTrackerSettings(s => ({ ...s, auto_publish_enabled: !s.auto_publish_enabled }))} <button onClick={() => setTrackerSettings(s => ({ ...s, auto_publish_enabled: !s.auto_publish_enabled }))}
className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_publish_enabled ? 'bg-[#F0A623]' : 'bg-[#333]'}`}> className={`relative w-11 h-6 rounded-full transition-colors ${trackerSettings.auto_publish_enabled ? 'bg-[#1D4ED8]' : 'bg-[#333]'}`}>
<span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_publish_enabled ? 'translate-x-5' : 'translate-x-0'}`} /> <span className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${trackerSettings.auto_publish_enabled ? 'translate-x-5' : 'translate-x-0'}`} />
</button> </button>
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Mention Threshold (min mentions to qualify for auto-stories)</label> <label className="block text-xs font-body text-[#888] mb-1.5">Mention Threshold (min mentions to qualify for auto-stories)</label>
<input type="number" min={1} max={20} value={trackerSettings.mention_threshold} onChange={e => setTrackerSettings(s => ({ ...s, mention_threshold: parseInt(e.target.value) }))} <input type="number" min={1} max={20} value={trackerSettings.mention_threshold} onChange={e => setTrackerSettings(s => ({ ...s, mention_threshold: parseInt(e.target.value) }))}
className="w-32 bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]" /> className="w-32 bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="block text-xs font-body text-[#888] mb-1.5">Max Stories Per Company Per Week</label> <label className="block text-xs font-body text-[#888] mb-1.5">Max Stories Per Company Per Week</label>
<input type="number" min={1} max={20} value={trackerSettings.max_stories_per_company_per_week} onChange={e => setTrackerSettings(s => ({ ...s, max_stories_per_company_per_week: parseInt(e.target.value) }))} <input type="number" min={1} max={20} value={trackerSettings.max_stories_per_company_per_week} onChange={e => setTrackerSettings(s => ({ ...s, max_stories_per_company_per_week: parseInt(e.target.value) }))}
className="w-32 bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#F0A623]" /> className="w-32 bg-[#0a0a0a] border border-[#333] rounded-lg px-3 py-2.5 text-white text-sm font-body focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
</div> </div>
<button onClick={handleSaveSettings} disabled={savingSettings} <button onClick={handleSaveSettings} disabled={savingSettings}
className="px-6 py-2.5 bg-[#F0A623] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50"> className="px-6 py-2.5 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm font-body font-semibold rounded-lg transition-colors disabled:opacity-50">
{savingSettings ? 'Saving...' : 'Save Settings'} {savingSettings ? 'Saving...' : 'Save Settings'}
</button> </button>
</div> </div>

View File

@@ -97,7 +97,7 @@ function StatCard({
good?: boolean; good?: boolean;
}) { }) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5 flex flex-col gap-2"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 flex flex-col gap-2">
<p className={`text-2xl font-display font-bold ${accent}`}>{value}</p> <p className={`text-2xl font-display font-bold ${accent}`}>{value}</p>
<p className="text-sm text-[#888] font-body">{label}</p> <p className="text-sm text-[#888] font-body">{label}</p>
{sub && <p className="text-xs text-[#555] font-body">{sub}</p>} {sub && <p className="text-xs text-[#555] font-body">{sub}</p>}
@@ -173,7 +173,7 @@ function GeneratePanel({ onGenerated }: { onGenerated: () => void }) {
}; };
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5 flex flex-col gap-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 flex flex-col gap-4">
<h3 className="text-sm font-display font-bold text-white">Manual Controls</h3> <h3 className="text-sm font-display font-bold text-white">Manual Controls</h3>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div> <div>
@@ -181,7 +181,7 @@ function GeneratePanel({ onGenerated }: { onGenerated: () => void }) {
<select <select
value={site} value={site}
onChange={(e) => setSite(e.target.value as RMPSite)} onChange={(e) => setSite(e.target.value as RMPSite)}
className="w-full bg-[#231d18] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#FFFFFF] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#1D4ED8]"
> >
{Object.entries(SITE_LABELS).map(([key, val]) => ( {Object.entries(SITE_LABELS).map(([key, val]) => (
<option key={key} value={key}>{val.label}</option> <option key={key} value={key}>{val.label}</option>
@@ -195,21 +195,21 @@ function GeneratePanel({ onGenerated }: { onGenerated: () => void }) {
value={topic} value={topic}
onChange={(e) => setTopic(e.target.value)} onChange={(e) => setTopic(e.target.value)}
placeholder="e.g. ATSC 3.0 deployment update" placeholder="e.g. ATSC 3.0 deployment update"
className="w-full bg-[#231d18] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body placeholder-[#444] focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#FFFFFF] border border-[#333] rounded-lg px-3 py-2 text-sm text-white font-body placeholder-[#444] focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={handleGenerate} onClick={handleGenerate}
disabled={loading} disabled={loading}
className="flex-1 bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white text-sm font-body font-semibold py-2 px-4 rounded-lg transition-colors" className="flex-1 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white text-sm font-body font-semibold py-2 px-4 rounded-lg transition-colors"
> >
{loading ? 'Generating…' : 'Generate Article'} {loading ? 'Generating…' : 'Generate Article'}
</button> </button>
<button <button
onClick={handleSEOBatch} onClick={handleSEOBatch}
disabled={loading} disabled={loading}
className="flex-1 bg-[#231d18] hover:bg-[#3a322b] disabled:opacity-50 border border-[#333] text-white text-sm font-body font-semibold py-2 px-4 rounded-lg transition-colors" className="flex-1 bg-[#FFFFFF] hover:bg-[#DCE6F2] disabled:opacity-50 border border-[#333] text-white text-sm font-body font-semibold py-2 px-4 rounded-lg transition-colors"
> >
{loading ? 'Running…' : 'Batch SEO Meta'} {loading ? 'Running…' : 'Batch SEO Meta'}
</button> </button>
@@ -279,7 +279,7 @@ export default function ContentStatusPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading content status</p> <p className="text-[#555] text-sm font-body">Loading content status</p>
</div> </div>
</div> </div>
@@ -296,7 +296,7 @@ export default function ContentStatusPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] text-white"> <div className="min-h-screen bg-[#0a0a0a] text-white">
{/* Header */} {/* Header */}
<div className="border-b border-[#231d18] bg-[#1c1815] px-6 py-4"> <div className="border-b border-[#FFFFFF] bg-[#F8FAFC] px-6 py-4">
<div className="max-w-7xl mx-auto flex items-center justify-between"> <div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-white transition-colors text-sm font-body"> <Link href="/admin" className="text-[#555] hover:text-white transition-colors text-sm font-body">
@@ -327,7 +327,7 @@ export default function ContentStatusPage() {
</button> </button>
<button <button
onClick={fetchAll} onClick={fetchAll}
className="text-xs font-body px-3 py-1.5 rounded-lg border border-[#333] text-white bg-[#111] hover:bg-[#231d18] transition-colors" className="text-xs font-body px-3 py-1.5 rounded-lg border border-[#333] text-white bg-[#111] hover:bg-[#FFFFFF] transition-colors"
> >
Refresh Now Refresh Now
</button> </button>
@@ -395,7 +395,7 @@ export default function ContentStatusPage() {
{Object.entries(SITE_LABELS).map(([key, cfg]) => { {Object.entries(SITE_LABELS).map(([key, cfg]) => {
const count = status?.siteBreakdown?.[key] ?? 0; const count = status?.siteBreakdown?.[key] ?? 0;
return ( return (
<div key={key} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 flex flex-col gap-2"> <div key={key} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 flex flex-col gap-2">
<p className={`text-xs font-body font-semibold ${cfg.color}`}>{cfg.label}</p> <p className={`text-xs font-body font-semibold ${cfg.color}`}>{cfg.label}</p>
<p className="text-3xl font-display font-bold text-white">{count}</p> <p className="text-3xl font-display font-bold text-white">{count}</p>
<p className="text-xs text-[#555] font-body">Target: {cfg.target}</p> <p className="text-xs text-[#555] font-body">Target: {cfg.target}</p>
@@ -484,7 +484,7 @@ export default function ContentStatusPage() {
{/* API Reference */} {/* API Reference */}
<section> <section>
<h2 className="text-sm font-display font-bold text-[#888] uppercase tracking-wider mb-4">API Endpoints</h2> <h2 className="text-sm font-display font-bold text-[#888] uppercase tracking-wider mb-4">API Endpoints</h2>
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5 grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 grid grid-cols-1 sm:grid-cols-2 gap-3">
{[ {[
{ method: 'GET', path: '/api/content/audit', desc: 'Full content audit report' }, { method: 'GET', path: '/api/content/audit', desc: 'Full content audit report' },
{ method: 'GET', path: '/api/content/pipeline-status', desc: 'Live pipeline + node status' }, { method: 'GET', path: '/api/content/pipeline-status', desc: 'Live pipeline + node status' },

View File

@@ -90,7 +90,7 @@ function StatCard({
label, label,
value, value,
sub, sub,
accent = '#F0A623', accent = '#1D4ED8',
icon, icon,
trend, trend,
}: { }: {
@@ -102,7 +102,7 @@ function StatCard({
trend?: { value: number; label: string }; trend?: { value: number; label: string };
}) { }) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-5 flex flex-col gap-3"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-5 flex flex-col gap-3">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div <div
className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0" className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0"
@@ -134,7 +134,7 @@ function StatCard({
function ChartTooltip({ active, payload, label }: any) { function ChartTooltip({ active, payload, label }: any) {
if (!active || !payload?.length) return null; if (!active || !payload?.length) return null;
return ( return (
<div className="bg-[#231d18] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl"> <div className="bg-[#FFFFFF] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl">
{label && <p className="text-[#888] mb-1.5 font-semibold">{label}</p>} {label && <p className="text-[#888] mb-1.5 font-semibold">{label}</p>}
{payload.map((entry: any, i: number) => ( {payload.map((entry: any, i: number) => (
<p key={i} style={{ color: entry.color }} className="leading-5"> <p key={i} style={{ color: entry.color }} className="leading-5">
@@ -252,7 +252,7 @@ export default function EmailAnalyticsDashboard() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading analytics</p> <p className="text-[#555] text-sm font-body">Loading analytics</p>
</div> </div>
</div> </div>
@@ -262,7 +262,7 @@ export default function EmailAnalyticsDashboard() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] text-white"> <div className="min-h-screen bg-[#0a0a0a] text-white">
{/* Header */} {/* Header */}
<div className="border-b border-[#231d18] bg-[#1c1815] sticky top-0 z-10"> <div className="border-b border-[#FFFFFF] bg-[#F8FAFC] sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between"> <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-white transition-colors"> <Link href="/admin" className="text-[#555] hover:text-white transition-colors">
@@ -270,7 +270,7 @@ export default function EmailAnalyticsDashboard() {
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" /> <path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg> </svg>
</Link> </Link>
<div className="w-px h-5 bg-[#3a322b]" /> <div className="w-px h-5 bg-[#DCE6F2]" />
<span className="text-white text-sm font-body font-semibold">Email Analytics</span> <span className="text-white text-sm font-body font-semibold">Email Analytics</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -280,17 +280,17 @@ export default function EmailAnalyticsDashboard() {
onClick={() => setTimeRange(r)} onClick={() => setTimeRange(r)}
className={`px-3 py-1.5 text-xs font-semibold font-body rounded-lg transition-colors ${ className={`px-3 py-1.5 text-xs font-semibold font-body rounded-lg transition-colors ${
timeRange === r timeRange === r
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#555] hover:text-white border border-[#3a322b] hover:border-[#444]' : 'text-[#555] hover:text-white border border-[#DCE6F2] hover:border-[#444]'
}`} }`}
> >
{r === '3m' ? '3 Mo' : r === '6m' ? '6 Mo' : 'All'} {r === '3m' ? '3 Mo' : r === '6m' ? '6 Mo' : 'All'}
</button> </button>
))} ))}
<div className="w-px h-6 bg-[#3a322b] mx-1" /> <div className="w-px h-6 bg-[#DCE6F2] mx-1" />
<Link <Link
href="/admin/newsletter/analytics" href="/admin/newsletter/analytics"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 10h18M3 14h18M10 3v18M14 3v18" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 10h18M3 14h18M10 3v18M14 3v18" />
@@ -317,7 +317,7 @@ export default function EmailAnalyticsDashboard() {
label="Active Subscribers" label="Active Subscribers"
value={fmt(stats.activeSubscribers)} value={fmt(stats.activeSubscribers)}
sub={`${fmt(stats.unsubscribed)} unsubscribed`} sub={`${fmt(stats.unsubscribed)} unsubscribed`}
accent="#F0A623" accent="#1D4ED8"
trend={{ value: 12, label: 'vs last month' }} trend={{ value: 12, label: 'vs last month' }}
icon={ icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -365,7 +365,7 @@ export default function EmailAnalyticsDashboard() {
</div> </div>
{/* Send Volume Chart */} {/* Send Volume Chart */}
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-6">
<SectionHeader <SectionHeader
title="Send Volume Over Time" title="Send Volume Over Time"
subtitle="Total emails sent, opens, and clicks per month" subtitle="Total emails sent, opens, and clicks per month"
@@ -378,7 +378,7 @@ export default function EmailAnalyticsDashboard() {
<YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)} /> <YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => fmt(v)} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
<Legend wrapperStyle={{ fontSize: 11, color: '#888', paddingTop: 12 }} /> <Legend wrapperStyle={{ fontSize: 11, color: '#888', paddingTop: 12 }} />
<Bar dataKey="sent" name="Sent" fill="#3b82f620" stroke="#F0A623" strokeWidth={1} radius={[3, 3, 0, 0]} /> <Bar dataKey="sent" name="Sent" fill="#3b82f620" stroke="#1D4ED8" strokeWidth={1} radius={[3, 3, 0, 0]} />
<Bar dataKey="opens" name="Opens" fill="#10b98120" stroke="#10b981" strokeWidth={1} radius={[3, 3, 0, 0]} /> <Bar dataKey="opens" name="Opens" fill="#10b98120" stroke="#10b981" strokeWidth={1} radius={[3, 3, 0, 0]} />
<Bar dataKey="clicks" name="Clicks" fill="#f59e0b20" stroke="#f59e0b" strokeWidth={1} radius={[3, 3, 0, 0]} /> <Bar dataKey="clicks" name="Clicks" fill="#f59e0b20" stroke="#f59e0b" strokeWidth={1} radius={[3, 3, 0, 0]} />
</ComposedChart> </ComposedChart>
@@ -387,7 +387,7 @@ export default function EmailAnalyticsDashboard() {
</div> </div>
{/* Open Rate & CTR Trend */} {/* Open Rate & CTR Trend */}
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-6">
<SectionHeader <SectionHeader
title="Open Rate & Click-Through Rate Trend" title="Open Rate & Click-Through Rate Trend"
subtitle="Monthly engagement rates as a percentage of recipients" subtitle="Monthly engagement rates as a percentage of recipients"
@@ -397,8 +397,8 @@ export default function EmailAnalyticsDashboard() {
<AreaChart data={filteredRates} margin={{ top: 4, right: 8, left: -10, bottom: 0 }}> <AreaChart data={filteredRates} margin={{ top: 4, right: 8, left: -10, bottom: 0 }}>
<defs> <defs>
<linearGradient id="openGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="openGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#F0A623" stopOpacity={0.2} /> <stop offset="5%" stopColor="#1D4ED8" stopOpacity={0.2} />
<stop offset="95%" stopColor="#F0A623" stopOpacity={0} /> <stop offset="95%" stopColor="#1D4ED8" stopOpacity={0} />
</linearGradient> </linearGradient>
<linearGradient id="clickGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="clickGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#10b981" stopOpacity={0.2} /> <stop offset="5%" stopColor="#10b981" stopOpacity={0.2} />
@@ -418,10 +418,10 @@ export default function EmailAnalyticsDashboard() {
<Area <Area
type="monotone" type="monotone"
dataKey="Open Rate" dataKey="Open Rate"
stroke="#F0A623" stroke="#1D4ED8"
strokeWidth={2} strokeWidth={2}
fill="url(#openGrad)" fill="url(#openGrad)"
dot={{ r: 3, fill: '#F0A623' }} dot={{ r: 3, fill: '#1D4ED8' }}
activeDot={{ r: 5 }} activeDot={{ r: 5 }}
/> />
<Area <Area
@@ -441,7 +441,7 @@ export default function EmailAnalyticsDashboard() {
{/* Subscriber Growth */} {/* Subscriber Growth */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Growth Over Time */} {/* Growth Over Time */}
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-6">
<SectionHeader <SectionHeader
title="Subscriber Growth" title="Subscriber Growth"
subtitle="Total subscribers and monthly net change" subtitle="Total subscribers and monthly net change"
@@ -463,14 +463,14 @@ export default function EmailAnalyticsDashboard() {
fill="#8b5cf618" fill="#8b5cf618"
dot={false} dot={false}
/> />
<Bar dataKey="net" name="Net New" fill="#F0A623" radius={[3, 3, 0, 0]} /> <Bar dataKey="net" name="Net New" fill="#1D4ED8" radius={[3, 3, 0, 0]} />
</ComposedChart> </ComposedChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
</div> </div>
{/* New vs Unsubscribed */} {/* New vs Unsubscribed */}
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-6">
<SectionHeader <SectionHeader
title="New Subscribers vs Unsubscribes" title="New Subscribers vs Unsubscribes"
subtitle="Monthly acquisition vs churn" subtitle="Monthly acquisition vs churn"
@@ -492,7 +492,7 @@ export default function EmailAnalyticsDashboard() {
</div> </div>
{/* Benchmark Comparison */} {/* Benchmark Comparison */}
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-6">
<SectionHeader <SectionHeader
title="Performance vs Industry Benchmarks" title="Performance vs Industry Benchmarks"
subtitle="How your metrics compare to email marketing industry averages" subtitle="How your metrics compare to email marketing industry averages"
@@ -503,7 +503,7 @@ export default function EmailAnalyticsDashboard() {
label: 'Open Rate', label: 'Open Rate',
yours: stats.avgOpenRate, yours: stats.avgOpenRate,
benchmark: 21.5, benchmark: 21.5,
color: '#F0A623', color: '#1D4ED8',
unit: '%', unit: '%',
}, },
{ {
@@ -584,7 +584,7 @@ export default function EmailAnalyticsDashboard() {
<div className="flex flex-wrap gap-3 pb-4"> <div className="flex flex-wrap gap-3 pb-4">
<Link <Link
href="/admin/newsletter" href="/admin/newsletter"
className="flex items-center gap-2 px-4 py-2 bg-[#111] border border-[#3a322b] rounded-lg text-sm font-body text-[#888] hover:text-white hover:border-[#444] transition-colors" className="flex items-center gap-2 px-4 py-2 bg-[#111] border border-[#DCE6F2] rounded-lg text-sm font-body text-[#888] hover:text-white hover:border-[#444] transition-colors"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" /> <path strokeLinecap="round" strokeLinejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
@@ -593,7 +593,7 @@ export default function EmailAnalyticsDashboard() {
</Link> </Link>
<Link <Link
href="/admin/newsletter/analytics" href="/admin/newsletter/analytics"
className="flex items-center gap-2 px-4 py-2 bg-[#111] border border-[#3a322b] rounded-lg text-sm font-body text-[#888] hover:text-white hover:border-[#444] transition-colors" className="flex items-center gap-2 px-4 py-2 bg-[#111] border border-[#DCE6F2] rounded-lg text-sm font-body text-[#888] hover:text-white hover:border-[#444] transition-colors"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
@@ -602,7 +602,7 @@ export default function EmailAnalyticsDashboard() {
</Link> </Link>
<Link <Link
href="/admin/newsletter/templates" href="/admin/newsletter/templates"
className="flex items-center gap-2 px-4 py-2 bg-[#111] border border-[#3a322b] rounded-lg text-sm font-body text-[#888] hover:text-white hover:border-[#444] transition-colors" className="flex items-center gap-2 px-4 py-2 bg-[#111] border border-[#DCE6F2] rounded-lg text-sm font-body text-[#888] hover:text-white hover:border-[#444] transition-colors"
> >
<svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />

View File

@@ -75,7 +75,7 @@ function DomainsContent() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -86,41 +86,41 @@ function DomainsContent() {
<h1 className="text-xl font-bold">Sending Domains</h1> <h1 className="text-xl font-bold">Sending Domains</h1>
<Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link> <Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link>
</div> </div>
<button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Add Domain</button> <button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ Add Domain</button>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
{showAddForm && ( {showAddForm && (
<form onSubmit={handleAddDomain} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleAddDomain} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">Add Rotation Domain</h3> <h3 className="text-sm font-semibold">Add Rotation Domain</h3>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Site *</label> <label className="text-xs text-[#888] block mb-1">Site *</label>
<select required value={addForm.site} onChange={e => setAddForm(p => ({ ...p, site: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select required value={addForm.site} onChange={e => setAddForm(p => ({ ...p, site: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
<option value="">Select</option> <option value="">Select</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Domain *</label> <label className="text-xs text-[#888] block mb-1">Domain *</label>
<input required value={addForm.domain} onChange={e => setAddForm(p => ({ ...p, domain: e.target.value }))} placeholder="mail.example.com" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required value={addForm.domain} onChange={e => setAddForm(p => ({ ...p, domain: e.target.value }))} placeholder="mail.example.com" className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
<select value={addForm.domain_type} onChange={e => setAddForm(p => ({ ...p, domain_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={addForm.domain_type} onChange={e => setAddForm(p => ({ ...p, domain_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
{['subdomain','sister_domain','primary'].map(t => <option key={t} value={t}>{t}</option>)} {['subdomain','sister_domain','primary'].map(t => <option key={t} value={t}>{t}</option>)}
</select> </select>
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Domain'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add Domain'}</button>
<button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
@@ -131,10 +131,10 @@ function DomainsContent() {
<div className="bg-[#111] border border-yellow-400/30 rounded-lg p-5 w-80 space-y-3"> <div className="bg-[#111] border border-yellow-400/30 rounded-lg p-5 w-80 space-y-3">
<h3 className="text-sm font-semibold text-yellow-400"> Override DNS Verification</h3> <h3 className="text-sm font-semibold text-yellow-400"> Override DNS Verification</h3>
<p className="text-xs text-[#888]">This allows sending before DNS is verified. Use with caution.</p> <p className="text-xs text-[#888]">This allows sending before DNS is verified. Use with caution.</p>
<textarea value={bypassReason} onChange={e => setBypassReason(e.target.value)} placeholder="Reason for bypass…" rows={3} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none" /> <textarea value={bypassReason} onChange={e => setBypassReason(e.target.value)} placeholder="Reason for bypass…" rows={3} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none" />
<div className="flex gap-2"> <div className="flex gap-2">
<button onClick={() => setBypass(showBypass)} disabled={!bypassReason} className="px-3 py-1.5 bg-yellow-600 rounded text-xs font-medium hover:bg-yellow-500 disabled:opacity-50">Set Bypass</button> <button onClick={() => setBypass(showBypass)} disabled={!bypassReason} className="px-3 py-1.5 bg-yellow-600 rounded text-xs font-medium hover:bg-yellow-500 disabled:opacity-50">Set Bypass</button>
<button onClick={() => setShowBypass(null)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button onClick={() => setShowBypass(null)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
@@ -142,12 +142,12 @@ function DomainsContent() {
<div className="space-y-2"> <div className="space-y-2">
{loadingData ? ( {loadingData ? (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-8 text-center text-[#555] text-xs">Loading</div> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-8 text-center text-[#555] text-xs">Loading</div>
) : domains.length === 0 ? ( ) : domains.length === 0 ? (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-8 text-center text-[#555] text-xs">No domains found</div> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-8 text-center text-[#555] text-xs">No domains found</div>
) : domains.map((d: any) => ( ) : domains.map((d: any) => (
<div key={d.id} className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div key={d.id} className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 flex items-center gap-3 cursor-pointer hover:bg-[#231d18]" onClick={() => setExpanded(expanded === d.id ? null : d.id)}> <div className="px-4 py-3 flex items-center gap-3 cursor-pointer hover:bg-[#FFFFFF]" onClick={() => setExpanded(expanded === d.id ? null : d.id)}>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-medium text-white">{d.domain}</span> <span className="text-sm font-medium text-white">{d.domain}</span>
@@ -156,7 +156,7 @@ function DomainsContent() {
<span className={`px-1.5 py-0.5 rounded text-xs ${d.dns_fully_verified ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{d.dns_fully_verified ? 'DNS OK' : 'DNS Pending'}</span> <span className={`px-1.5 py-0.5 rounded text-xs ${d.dns_fully_verified ? 'bg-green-400/10 text-green-400' : 'bg-yellow-400/10 text-yellow-400'}`}>{d.dns_fully_verified ? 'DNS OK' : 'DNS Pending'}</span>
{d.blacklisted && <span className="px-1.5 py-0.5 bg-red-400/10 text-red-400 rounded text-xs">Blacklisted</span>} {d.blacklisted && <span className="px-1.5 py-0.5 bg-red-400/10 text-red-400 rounded text-xs">Blacklisted</span>}
{d.bypass_verification && <span className="px-1.5 py-0.5 bg-yellow-400/10 text-yellow-400 rounded text-xs">Bypass Active</span>} {d.bypass_verification && <span className="px-1.5 py-0.5 bg-yellow-400/10 text-yellow-400 rounded text-xs">Bypass Active</span>}
<span className={`px-1.5 py-0.5 rounded text-xs ${d.is_active ? 'bg-green-400/10 text-green-400' : 'bg-[#231d18] text-[#555]'}`}>{d.is_active ? 'Active' : 'Inactive'}</span> <span className={`px-1.5 py-0.5 rounded text-xs ${d.is_active ? 'bg-green-400/10 text-green-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{d.is_active ? 'Active' : 'Inactive'}</span>
</div> </div>
<div className="flex gap-3 mt-1 text-xs text-[#555]"> <div className="flex gap-3 mt-1 text-xs text-[#555]">
<span>Health: {d.health_score}/100</span> <span>Health: {d.health_score}/100</span>
@@ -167,7 +167,7 @@ function DomainsContent() {
</div> </div>
{expanded === d.id && ( {expanded === d.id && (
<div className="border-t border-[#231d18] px-4 py-3 space-y-3"> <div className="border-t border-[#FFFFFF] px-4 py-3 space-y-3">
{/* DNS checks */} {/* DNS checks */}
<div className="grid grid-cols-2 gap-x-6"> <div className="grid grid-cols-2 gap-x-6">
<DnsRow label="SPF" valid={d.spf_valid} checkedAt={d.spf_checked_at} /> <DnsRow label="SPF" valid={d.spf_valid} checkedAt={d.spf_checked_at} />
@@ -178,7 +178,7 @@ function DomainsContent() {
</div> </div>
{/* DNS helper panel */} {/* DNS helper panel */}
<div className="bg-[#0a0a0a] border border-[#231d18] rounded p-3 space-y-2"> <div className="bg-[#0a0a0a] border border-[#FFFFFF] rounded p-3 space-y-2">
<p className="text-xs font-semibold text-[#888]">DNS Record Helper (GoDaddy)</p> <p className="text-xs font-semibold text-[#888]">DNS Record Helper (GoDaddy)</p>
<div className="space-y-1 text-xs text-[#555] font-mono"> <div className="space-y-1 text-xs text-[#555] font-mono">
<p>SPF: <span className="text-[#888]">v=spf1 include:sendgrid.net ~all</span></p> <p>SPF: <span className="text-[#888]">v=spf1 include:sendgrid.net ~all</span></p>
@@ -197,10 +197,10 @@ function DomainsContent() {
{/* Actions */} {/* Actions */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<button onClick={() => runVerify(d.id)} disabled={verifying === d.id} className="px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] disabled:opacity-50"> <button onClick={() => runVerify(d.id)} disabled={verifying === d.id} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] disabled:opacity-50">
{verifying === d.id ? 'Verifying…' : 'Run Full Verification'} {verifying === d.id ? 'Verifying…' : 'Run Full Verification'}
</button> </button>
<button onClick={() => toggleActive(d.id, d.is_active)} disabled={!d.dns_fully_verified && !d.bypass_verification && !d.is_active} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white disabled:opacity-30"> <button onClick={() => toggleActive(d.id, d.is_active)} disabled={!d.dns_fully_verified && !d.bypass_verification && !d.is_active} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white disabled:opacity-30">
{d.is_active ? 'Deactivate' : 'Activate'} {d.is_active ? 'Deactivate' : 'Activate'}
</button> </button>
<button onClick={() => setShowBypass(d.id)} className="px-3 py-1.5 bg-yellow-900/30 border border-yellow-400/20 rounded text-xs text-yellow-400 hover:bg-yellow-900/50"> <button onClick={() => setShowBypass(d.id)} className="px-3 py-1.5 bg-yellow-900/30 border border-yellow-400/20 rounded text-xs text-yellow-400 hover:bg-yellow-900/50">
@@ -219,7 +219,7 @@ function DomainsContent() {
export default function EmailDomainsPage() { export default function EmailDomainsPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>}> <Suspense fallback={<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>}>
<DomainsContent /> <DomainsContent />
</Suspense> </Suspense>
); );

View File

@@ -8,7 +8,7 @@ const SITES = ['avbeat','avbeat','backlotbeat','broadcastengineering'];
function DnsCheck({ label, valid }: { label: string; valid: boolean | null }) { function DnsCheck({ label, valid }: { label: string; valid: boolean | null }) {
return ( return (
<span className={`inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded ${valid === true ? 'bg-green-400/10 text-green-400' : valid === false ? 'bg-red-400/10 text-red-400' : 'bg-[#231d18] text-[#555]'}`}> <span className={`inline-flex items-center gap-1 text-xs px-1.5 py-0.5 rounded ${valid === true ? 'bg-green-400/10 text-green-400' : valid === false ? 'bg-red-400/10 text-red-400' : 'bg-[#FFFFFF] text-[#555]'}`}>
{valid === true ? '✓' : valid === false ? '✗' : '?'} {label} {valid === true ? '✓' : valid === false ? '✗' : '?'} {label}
</span> </span>
); );
@@ -42,7 +42,7 @@ export default function EmailDashboard() {
domains: domains.filter((d: any) => d.site === site), domains: domains.filter((d: any) => d.site === site),
})); }));
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -61,7 +61,7 @@ export default function EmailDashboard() {
{ label: 'Total Suppressions', value: stats?.total_suppressions ?? 0, color: 'text-red-400' }, { label: 'Total Suppressions', value: stats?.total_suppressions ?? 0, color: 'text-red-400' },
{ label: 'Blacklisted Domains', value: stats?.blacklisted ?? 0, color: 'text-red-400' }, { label: 'Blacklisted Domains', value: stats?.blacklisted ?? 0, color: 'text-red-400' },
].map(c => ( ].map(c => (
<div key={c.label} className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div key={c.label} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<p className={`text-2xl font-bold ${c.color}`}>{c.value}</p> <p className={`text-2xl font-bold ${c.color}`}>{c.value}</p>
<p className="text-xs text-[#888] mt-1">{c.label}</p> <p className="text-xs text-[#888] mt-1">{c.label}</p>
</div> </div>
@@ -74,10 +74,10 @@ export default function EmailDashboard() {
const primary = siteDomains.find((d: any) => d.is_primary); const primary = siteDomains.find((d: any) => d.is_primary);
const suppCount = stats?.suppressions_by_site?.[site] ?? 0; const suppCount = stats?.suppressions_by_site?.[site] ?? 0;
return ( return (
<div key={site} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <div key={site} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-sm font-semibold capitalize">{site}</h3> <h3 className="text-sm font-semibold capitalize">{site}</h3>
<Link href={`/admin/email/domains?site=${site}`} className="text-xs text-[#F0A623] hover:underline">Manage </Link> <Link href={`/admin/email/domains?site=${site}`} className="text-xs text-[#1D4ED8] hover:underline">Manage </Link>
</div> </div>
<div className="text-xs text-[#888]"> <div className="text-xs text-[#888]">
<span className="text-white">{primary?.domain ?? 'No primary'}</span> <span className="text-white">{primary?.domain ?? 'No primary'}</span>
@@ -113,7 +113,7 @@ export default function EmailDashboard() {
{ label: 'Suppressions', href: '/admin/email/suppressions' }, { label: 'Suppressions', href: '/admin/email/suppressions' },
{ label: 'Send Log', href: '/admin/email/send-log' }, { label: 'Send Log', href: '/admin/email/send-log' },
].map(l => ( ].map(l => (
<Link key={l.href} href={l.href} className="bg-[#111] border border-[#3a322b] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#F0A623] transition-colors"> <Link key={l.href} href={l.href} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-3 text-center text-sm text-[#888] hover:text-white hover:border-[#1D4ED8] transition-colors">
{l.label} {l.label}
</Link> </Link>
))} ))}

View File

@@ -29,7 +29,7 @@ export default function SendLogPage() {
useEffect(() => { if (user) load(); }, [user, load]); useEffect(() => { if (user) load(); }, [user, load]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -41,20 +41,20 @@ export default function SendLogPage() {
</div> </div>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterSite} onChange={e => setFilterSite(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Sites</option> <option value="">All Sites</option>
{SITES.map(s => <option key={s} value={s}>{s}</option>)} {SITES.map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
<select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Statuses</option> <option value="">All Statuses</option>
{['queued','sent','failed','suppressed'].map(s => <option key={s} value={s}>{s}</option>)} {['queued','sent','failed','suppressed'].map(s => <option key={s} value={s}>{s}</option>)}
</select> </select>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Site','Recipient','Subject','Type','Domain','Status','Sent At','Opens','Clicks'].map(h => ( {['Site','Recipient','Subject','Type','Domain','Status','Sent At','Opens','Clicks'].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium whitespace-nowrap">{h}</th>
))} ))}
@@ -66,14 +66,14 @@ export default function SendLogPage() {
) : logs.length === 0 ? ( ) : logs.length === 0 ? (
<tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No send log entries</td></tr> <tr><td colSpan={9} className="px-4 py-8 text-center text-[#555] text-xs">No send log entries</td></tr>
) : logs.map((l: any) => ( ) : logs.map((l: any) => (
<tr key={l.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={l.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-[#888] text-xs capitalize">{l.site}</td> <td className="px-3 py-2 text-[#888] text-xs capitalize">{l.site}</td>
<td className="px-3 py-2 text-white text-xs">{l.recipient_email}</td> <td className="px-3 py-2 text-white text-xs">{l.recipient_email}</td>
<td className="px-3 py-2 text-[#ccc] text-xs max-w-xs truncate">{l.subject ?? '—'}</td> <td className="px-3 py-2 text-[#ccc] text-xs max-w-xs truncate">{l.subject ?? '—'}</td>
<td className="px-3 py-2 text-[#555] text-xs">{l.send_type}</td> <td className="px-3 py-2 text-[#555] text-xs">{l.send_type}</td>
<td className="px-3 py-2 text-[#555] text-xs">{l.rmp_email_sending_domains?.domain ?? '—'}</td> <td className="px-3 py-2 text-[#555] text-xs">{l.rmp_email_sending_domains?.domain ?? '—'}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${l.status === 'sent' ? 'bg-green-400/10 text-green-400' : l.status === 'failed' ? 'bg-red-400/10 text-red-400' : l.status === 'suppressed' ? 'bg-yellow-400/10 text-yellow-400' : 'bg-[#231d18] text-[#555]'}`}>{l.status}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${l.status === 'sent' ? 'bg-green-400/10 text-green-400' : l.status === 'failed' ? 'bg-red-400/10 text-red-400' : l.status === 'suppressed' ? 'bg-yellow-400/10 text-yellow-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{l.status}</span>
</td> </td>
<td className="px-3 py-2 text-[#555] text-xs">{l.sent_at ? new Date(l.sent_at).toLocaleString() : '—'}</td> <td className="px-3 py-2 text-[#555] text-xs">{l.sent_at ? new Date(l.sent_at).toLocaleString() : '—'}</td>
<td className="px-3 py-2 text-[#888] text-xs">{l.opens}</td> <td className="px-3 py-2 text-[#888] text-xs">{l.opens}</td>

View File

@@ -72,7 +72,7 @@ export default function SuppressionsPage() {
load(); load();
} }
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -84,29 +84,29 @@ export default function SuppressionsPage() {
<Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link> <Link href="/admin/email" className="text-xs text-[#555] hover:text-[#888]"> Email</Link>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500">+ Add Suppression</button> <button onClick={() => setShowAddForm(true)} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500">+ Add Suppression</button>
</div> </div>
</div> </div>
{/* Site selector */} {/* Site selector */}
<div className="flex gap-1 flex-wrap"> <div className="flex gap-1 flex-wrap">
{SITES.map(s => ( {SITES.map(s => (
<button key={s} onClick={() => setSite(s)} className={`px-3 py-1 rounded text-xs capitalize ${site === s ? 'bg-[#F0A623] text-white' : 'bg-[#111] border border-[#3a322b] text-[#888]'}`}>{s}</button> <button key={s} onClick={() => setSite(s)} className={`px-3 py-1 rounded text-xs capitalize ${site === s ? 'bg-[#1D4ED8] text-white' : 'bg-[#111] border border-[#DCE6F2] text-[#888]'}`}>{s}</button>
))} ))}
</div> </div>
{/* Stats */} {/* Stats */}
{stats && ( {stats && (
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-3 gap-3">
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-3"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-3">
<p className="text-xl font-bold text-red-400">{stats.total ?? 0}</p> <p className="text-xl font-bold text-red-400">{stats.total ?? 0}</p>
<p className="text-xs text-[#888] mt-0.5">Total Suppressed</p> <p className="text-xs text-[#888] mt-0.5">Total Suppressed</p>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-3"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-3">
<p className="text-xl font-bold text-yellow-400">{stats.added_this_month ?? 0}</p> <p className="text-xl font-bold text-yellow-400">{stats.added_this_month ?? 0}</p>
<p className="text-xs text-[#888] mt-0.5">Added This Month</p> <p className="text-xs text-[#888] mt-0.5">Added This Month</p>
</div> </div>
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-3"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-3">
<p className="text-xl font-bold text-orange-400">{stats.hostile_replies ?? 0}</p> <p className="text-xl font-bold text-orange-400">{stats.hostile_replies ?? 0}</p>
<p className="text-xs text-[#888] mt-0.5">Hostile Replies</p> <p className="text-xs text-[#888] mt-0.5">Hostile Replies</p>
</div> </div>
@@ -115,44 +115,44 @@ export default function SuppressionsPage() {
{/* Filters */} {/* Filters */}
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
<select value={filterType} onChange={e => setFilterType(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-[#888] focus:outline-none"> <select value={filterType} onChange={e => setFilterType(e.target.value)} className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-[#888] focus:outline-none">
<option value="">All Types</option> <option value="">All Types</option>
{SUPP_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)} {SUPP_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
</select> </select>
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search email…" className="px-2 py-1 bg-[#111] border border-[#3a322b] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#F0A623]" /> <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search email…" className="px-2 py-1 bg-[#111] border border-[#DCE6F2] rounded text-xs text-white placeholder-[#555] focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
{showAddForm && ( {showAddForm && (
<form onSubmit={handleAddSuppression} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <form onSubmit={handleAddSuppression} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<h3 className="text-sm font-semibold">Add Manual Suppression</h3> <h3 className="text-sm font-semibold">Add Manual Suppression</h3>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Email *</label> <label className="text-xs text-[#888] block mb-1">Email *</label>
<input required type="email" value={addForm.email} onChange={e => setAddForm(p => ({ ...p, email: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required type="email" value={addForm.email} onChange={e => setAddForm(p => ({ ...p, email: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
<select value={addForm.suppression_type} onChange={e => setAddForm(p => ({ ...p, suppression_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={addForm.suppression_type} onChange={e => setAddForm(p => ({ ...p, suppression_type: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
{SUPP_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)} {SUPP_TYPES.map(t => <option key={t} value={t}>{t.replace(/_/g, ' ')}</option>)}
</select> </select>
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="text-xs text-[#888] block mb-1">Notes</label> <label className="text-xs text-[#888] block mb-1">Notes</label>
<input value={addForm.notes} onChange={e => setAddForm(p => ({ ...p, notes: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input value={addForm.notes} onChange={e => setAddForm(p => ({ ...p, notes: e.target.value }))} className="w-full px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add'}</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">{saving ? 'Saving…' : 'Add'}</button>
<button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888] hover:text-white">Cancel</button> <button type="button" onClick={() => setShowAddForm(false)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888] hover:text-white">Cancel</button>
</div> </div>
</form> </form>
)} )}
{/* Suppressions table */} {/* Suppressions table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-x-auto"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-x-auto">
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
{['Email','Type','Source','Reply Text','Domain','Date',''].map(h => ( {['Email','Type','Source','Reply Text','Domain','Date',''].map(h => (
<th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th> <th key={h} className="px-3 py-2 text-left text-xs text-[#555] font-medium">{h}</th>
))} ))}
@@ -164,10 +164,10 @@ export default function SuppressionsPage() {
) : suppressions.length === 0 ? ( ) : suppressions.length === 0 ? (
<tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No suppressions found</td></tr> <tr><td colSpan={7} className="px-4 py-8 text-center text-[#555] text-xs">No suppressions found</td></tr>
) : suppressions.map((s: any) => ( ) : suppressions.map((s: any) => (
<tr key={s.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={s.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-3 py-2 text-white text-xs">{s.email}</td> <td className="px-3 py-2 text-white text-xs">{s.email}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${s.suppression_type === 'hostile_reply' ? 'bg-red-400/10 text-red-400' : s.suppression_type === 'unsubscribe' ? 'bg-yellow-400/10 text-yellow-400' : 'bg-[#231d18] text-[#555]'}`}>{s.suppression_type?.replace(/_/g, ' ')}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${s.suppression_type === 'hostile_reply' ? 'bg-red-400/10 text-red-400' : s.suppression_type === 'unsubscribe' ? 'bg-yellow-400/10 text-yellow-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{s.suppression_type?.replace(/_/g, ' ')}</span>
</td> </td>
<td className="px-3 py-2 text-[#555] text-xs">{s.source}</td> <td className="px-3 py-2 text-[#555] text-xs">{s.source}</td>
<td className="px-3 py-2 text-[#555] text-xs max-w-xs truncate">{s.reply_text ?? '—'}</td> <td className="px-3 py-2 text-[#555] text-xs max-w-xs truncate">{s.reply_text ?? '—'}</td>
@@ -183,40 +183,40 @@ export default function SuppressionsPage() {
</div> </div>
{/* Keyword Management */} {/* Keyword Management */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-4 py-3 border-b border-[#3a322b] flex items-center justify-between"> <div className="px-4 py-3 border-b border-[#DCE6F2] flex items-center justify-between">
<h2 className="text-sm font-semibold">Suppression Keywords</h2> <h2 className="text-sm font-semibold">Suppression Keywords</h2>
<button onClick={() => setShowKeywordForm(true)} className="text-xs text-[#F0A623] hover:underline">+ Add Keyword</button> <button onClick={() => setShowKeywordForm(true)} className="text-xs text-[#1D4ED8] hover:underline">+ Add Keyword</button>
</div> </div>
{showKeywordForm && ( {showKeywordForm && (
<form onSubmit={handleAddKeyword} className="px-4 py-3 border-b border-[#231d18] flex gap-2 items-end"> <form onSubmit={handleAddKeyword} className="px-4 py-3 border-b border-[#FFFFFF] flex gap-2 items-end">
<div> <div>
<label className="text-xs text-[#888] block mb-1">Keyword *</label> <label className="text-xs text-[#888] block mb-1">Keyword *</label>
<input required value={keywordForm.keyword} onChange={e => setKeywordForm(p => ({ ...p, keyword: e.target.value }))} className="px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none focus:border-[#F0A623]" /> <input required value={keywordForm.keyword} onChange={e => setKeywordForm(p => ({ ...p, keyword: e.target.value }))} className="px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none focus:border-[#1D4ED8]" />
</div> </div>
<div> <div>
<label className="text-xs text-[#888] block mb-1">Type</label> <label className="text-xs text-[#888] block mb-1">Type</label>
<select value={keywordForm.keyword_type} onChange={e => setKeywordForm(p => ({ ...p, keyword_type: e.target.value }))} className="px-2 py-1.5 bg-[#0a0a0a] border border-[#3a322b] rounded text-xs text-white focus:outline-none"> <select value={keywordForm.keyword_type} onChange={e => setKeywordForm(p => ({ ...p, keyword_type: e.target.value }))} className="px-2 py-1.5 bg-[#0a0a0a] border border-[#DCE6F2] rounded text-xs text-white focus:outline-none">
<option value="unsubscribe">unsubscribe</option> <option value="unsubscribe">unsubscribe</option>
<option value="hostile">hostile</option> <option value="hostile">hostile</option>
</select> </select>
</div> </div>
<button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#F0A623] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Add</button> <button type="submit" disabled={saving} className="px-3 py-1.5 bg-[#1D4ED8] rounded text-xs font-medium hover:bg-blue-500 disabled:opacity-50">Add</button>
<button type="button" onClick={() => setShowKeywordForm(false)} className="px-3 py-1.5 bg-[#231d18] border border-[#3a322b] rounded text-xs text-[#888]">Cancel</button> <button type="button" onClick={() => setShowKeywordForm(false)} className="px-3 py-1.5 bg-[#FFFFFF] border border-[#DCE6F2] rounded text-xs text-[#888]">Cancel</button>
</form> </form>
)} )}
<table className="w-full text-sm"> <table className="w-full text-sm">
<thead><tr className="border-b border-[#231d18]">{['Keyword','Type','Active',''].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead> <thead><tr className="border-b border-[#FFFFFF]">{['Keyword','Type','Active',''].map(h => <th key={h} className="px-4 py-2 text-left text-xs text-[#555]">{h}</th>)}</tr></thead>
<tbody> <tbody>
{keywords.map((k: any) => ( {keywords.map((k: any) => (
<tr key={k.id} className="border-b border-[#231d18] hover:bg-[#231d18]"> <tr key={k.id} className="border-b border-[#FFFFFF] hover:bg-[#FFFFFF]">
<td className="px-4 py-2 text-white text-xs">{k.keyword}</td> <td className="px-4 py-2 text-white text-xs">{k.keyword}</td>
<td className="px-4 py-2 text-[#888] text-xs">{k.keyword_type}</td> <td className="px-4 py-2 text-[#888] text-xs">{k.keyword_type}</td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<span className={`px-2 py-0.5 rounded-full text-xs ${k.active ? 'bg-green-400/10 text-green-400' : 'bg-[#231d18] text-[#555]'}`}>{k.active ? 'Active' : 'Inactive'}</span> <span className={`px-2 py-0.5 rounded-full text-xs ${k.active ? 'bg-green-400/10 text-green-400' : 'bg-[#FFFFFF] text-[#555]'}`}>{k.active ? 'Active' : 'Inactive'}</span>
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
<button onClick={() => toggleKeyword(k.id, k.active)} className="text-xs text-[#F0A623] hover:underline">{k.active ? 'Deactivate' : 'Activate'}</button> <button onClick={() => toggleKeyword(k.id, k.active)} className="text-xs text-[#1D4ED8] hover:underline">{k.active ? 'Deactivate' : 'Activate'}</button>
</td> </td>
</tr> </tr>
))} ))}

View File

@@ -91,7 +91,7 @@ interface StatCardProps {
function StatCard({ label, value, sub, accentColor, bgColor, icon, badge }: StatCardProps) { function StatCard({ label, value, sub, accentColor, bgColor, icon, badge }: StatCardProps) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5 flex flex-col gap-3 hover:border-[#333] transition-colors"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 flex flex-col gap-3 hover:border-[#333] transition-colors">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${bgColor}`}> <div className={`w-10 h-10 rounded-lg flex items-center justify-center ${bgColor}`}>
<span className={accentColor}>{icon}</span> <span className={accentColor}>{icon}</span>
@@ -116,7 +116,7 @@ function StatCard({ label, value, sub, accentColor, bgColor, icon, badge }: Stat
function ChartTooltip({ active, payload, label }: any) { function ChartTooltip({ active, payload, label }: any) {
if (!active || !payload?.length) return null; if (!active || !payload?.length) return null;
return ( return (
<div className="bg-[#231d18] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl"> <div className="bg-[#FFFFFF] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl">
{label && <p className="text-[#888] mb-1">{label}</p>} {label && <p className="text-[#888] mb-1">{label}</p>}
{payload.map((entry: any, i: number) => ( {payload.map((entry: any, i: number) => (
<p key={i} style={{ color: entry.color }} className="leading-5"> <p key={i} style={{ color: entry.color }} className="leading-5">
@@ -167,7 +167,7 @@ const EMPTY_STATS: ForumStats = {
}, },
}; };
const CATEGORY_COLORS = ['#F0A623', '#f59e0b', '#10b981', '#8b5cf6', '#ef4444', '#06b6d4', '#f97316', '#84cc16']; const CATEGORY_COLORS = ['#1D4ED8', '#f59e0b', '#10b981', '#8b5cf6', '#ef4444', '#06b6d4', '#f97316', '#84cc16'];
export default function ForumDashboardPage() { export default function ForumDashboardPage() {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
@@ -204,7 +204,7 @@ export default function ForumDashboardPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading forum dashboard</p> <p className="text-[#555] text-sm font-body">Loading forum dashboard</p>
</div> </div>
</div> </div>
@@ -236,7 +236,7 @@ export default function ForumDashboardPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
onClick={fetchStats} onClick={fetchStats}
className="flex items-center gap-2 px-3 py-2 bg-[#111] border border-[#3a322b] rounded-lg text-xs text-[#888] hover:text-white hover:border-[#333] transition-all font-body" className="flex items-center gap-2 px-3 py-2 bg-[#111] border border-[#DCE6F2] rounded-lg text-xs text-[#888] hover:text-white hover:border-[#333] transition-all font-body"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> <path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
@@ -245,7 +245,7 @@ export default function ForumDashboardPage() {
</button> </button>
<Link <Link
href="/admin/forum-moderation" href="/admin/forum-moderation"
className="flex items-center gap-2 px-3 py-2 bg-[#F0A623] rounded-lg text-xs text-white hover:bg-blue-500 transition-colors font-body font-semibold" className="flex items-center gap-2 px-3 py-2 bg-[#1D4ED8] rounded-lg text-xs text-white hover:bg-blue-500 transition-colors font-body font-semibold"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
@@ -386,7 +386,7 @@ export default function ForumDashboardPage() {
{/* Thread Growth Chart */} {/* Thread Growth Chart */}
<section className="mb-8"> <section className="mb-8">
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<SectionHeader <SectionHeader
title="Thread Growth" title="Thread Growth"
subtitle="Weekly new threads and replies over the past 8 weeks" subtitle="Weekly new threads and replies over the past 8 weeks"
@@ -398,7 +398,7 @@ export default function ForumDashboardPage() {
) : ( ) : (
<ResponsiveContainer width="100%" height={240}> <ResponsiveContainer width="100%" height={240}>
<LineChart data={weeklyGrowth} margin={{ top: 5, right: 10, left: -20, bottom: 5 }}> <LineChart data={weeklyGrowth} margin={{ top: 5, right: 10, left: -20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#231d18" /> <CartesianGrid strokeDasharray="3 3" stroke="#FFFFFF" />
<XAxis dataKey="week" tick={{ fill: '#555', fontSize: 11 }} axisLine={false} tickLine={false} /> <XAxis dataKey="week" tick={{ fill: '#555', fontSize: 11 }} axisLine={false} tickLine={false} />
<YAxis tick={{ fill: '#555', fontSize: 11 }} axisLine={false} tickLine={false} /> <YAxis tick={{ fill: '#555', fontSize: 11 }} axisLine={false} tickLine={false} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
@@ -407,7 +407,7 @@ export default function ForumDashboardPage() {
iconType="circle" iconType="circle"
iconSize={8} iconSize={8}
/> />
<Line type="monotone" dataKey="threads" stroke="#F0A623" strokeWidth={2} dot={{ fill: '#F0A623', r: 3 }} name="New Threads" /> <Line type="monotone" dataKey="threads" stroke="#1D4ED8" strokeWidth={2} dot={{ fill: '#1D4ED8', r: 3 }} name="New Threads" />
<Line type="monotone" dataKey="replies" stroke="#10b981" strokeWidth={2} dot={{ fill: '#10b981', r: 3 }} name="New Replies" /> <Line type="monotone" dataKey="replies" stroke="#10b981" strokeWidth={2} dot={{ fill: '#10b981', r: 3 }} name="New Replies" />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -419,33 +419,33 @@ export default function ForumDashboardPage() {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{/* Top Categories */} {/* Top Categories */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#231d18] flex items-center justify-between"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex items-center justify-between">
<div> <div>
<h2 className="text-sm font-display font-bold text-white">Top Categories</h2> <h2 className="text-sm font-display font-bold text-white">Top Categories</h2>
<p className="text-xs text-[#555] font-body mt-0.5">By thread count</p> <p className="text-xs text-[#555] font-body mt-0.5">By thread count</p>
</div> </div>
<Link href="/forum" className="text-xs text-[#F0A623] hover:text-blue-300 font-body transition-colors">View forum </Link> <Link href="/forum" className="text-xs text-[#1D4ED8] hover:text-blue-300 font-body transition-colors">View forum </Link>
</div> </div>
{topCategories.length === 0 ? ( {topCategories.length === 0 ? (
<div className="px-5 py-10 text-center"> <div className="px-5 py-10 text-center">
<p className="text-[#555] text-sm font-body">No categories found</p> <p className="text-[#555] text-sm font-body">No categories found</p>
</div> </div>
) : ( ) : (
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{topCategories.map((cat, idx) => { {topCategories.map((cat, idx) => {
const maxThreads = topCategories[0]?.thread_count ?? 1; const maxThreads = topCategories[0]?.thread_count ?? 1;
const pct = maxThreads > 0 ? Math.round((cat.thread_count / maxThreads) * 100) : 0; const pct = maxThreads > 0 ? Math.round((cat.thread_count / maxThreads) * 100) : 0;
const color = CATEGORY_COLORS[idx % CATEGORY_COLORS.length]; const color = CATEGORY_COLORS[idx % CATEGORY_COLORS.length];
return ( return (
<div key={cat.id} className="px-5 py-3 hover:bg-[#1c1815] transition-colors"> <div key={cat.id} className="px-5 py-3 hover:bg-[#F8FAFC] transition-colors">
<div className="flex items-center gap-3 mb-1.5"> <div className="flex items-center gap-3 mb-1.5">
<span className="text-base leading-none">{cat.icon}</span> <span className="text-base leading-none">{cat.icon}</span>
<span className="text-sm text-white font-body flex-1 truncate">{cat.name}</span> <span className="text-sm text-white font-body flex-1 truncate">{cat.name}</span>
<span className="text-xs text-[#888] font-body font-semibold">{cat.thread_count} threads</span> <span className="text-xs text-[#888] font-body font-semibold">{cat.thread_count} threads</span>
<span className="text-xs text-[#555] font-body">{cat.post_count} posts</span> <span className="text-xs text-[#555] font-body">{cat.post_count} posts</span>
</div> </div>
<div className="h-1.5 bg-[#231d18] rounded-full overflow-hidden ml-7"> <div className="h-1.5 bg-[#FFFFFF] rounded-full overflow-hidden ml-7">
<div <div
className="h-full rounded-full transition-all duration-500" className="h-full rounded-full transition-all duration-500"
style={{ width: `${pct}%`, backgroundColor: color }} style={{ width: `${pct}%`, backgroundColor: color }}
@@ -459,8 +459,8 @@ export default function ForumDashboardPage() {
</div> </div>
{/* Active Users */} {/* Active Users */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#231d18]"> <div className="px-5 py-4 border-b border-[#FFFFFF]">
<h2 className="text-sm font-display font-bold text-white">Active Users</h2> <h2 className="text-sm font-display font-bold text-white">Active Users</h2>
<p className="text-xs text-[#555] font-body mt-0.5">Most active in the last 30 days</p> <p className="text-xs text-[#555] font-body mt-0.5">Most active in the last 30 days</p>
</div> </div>
@@ -469,13 +469,13 @@ export default function ForumDashboardPage() {
<p className="text-[#555] text-sm font-body">No active users found</p> <p className="text-[#555] text-sm font-body">No active users found</p>
</div> </div>
) : ( ) : (
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{activeUsers.map((u, idx) => ( {activeUsers.map((u, idx) => (
<div key={u.author_id} className="px-5 py-3 flex items-center gap-3 hover:bg-[#1c1815] transition-colors"> <div key={u.author_id} className="px-5 py-3 flex items-center gap-3 hover:bg-[#F8FAFC] transition-colors">
<div className="w-6 h-6 rounded-full bg-[#231d18] flex items-center justify-center flex-shrink-0 text-xs font-bold font-body text-[#555]"> <div className="w-6 h-6 rounded-full bg-[#FFFFFF] flex items-center justify-center flex-shrink-0 text-xs font-bold font-body text-[#555]">
{idx + 1} {idx + 1}
</div> </div>
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#F0A623] to-[#8b5cf6] flex items-center justify-center flex-shrink-0"> <div className="w-8 h-8 rounded-full bg-gradient-to-br from-[#1D4ED8] to-[#8b5cf6] flex items-center justify-center flex-shrink-0">
<span className="text-xs font-bold text-white font-body"> <span className="text-xs font-bold text-white font-body">
{u.author_name.charAt(0).toUpperCase()} {u.author_name.charAt(0).toUpperCase()}
</span> </span>
@@ -498,14 +498,14 @@ export default function ForumDashboardPage() {
{/* Category Bar Chart */} {/* Category Bar Chart */}
{topCategories.length > 0 && ( {topCategories.length > 0 && (
<section className="mb-8"> <section className="mb-8">
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<SectionHeader <SectionHeader
title="Category Activity" title="Category Activity"
subtitle="Thread and post distribution across categories" subtitle="Thread and post distribution across categories"
/> />
<ResponsiveContainer width="100%" height={220}> <ResponsiveContainer width="100%" height={220}>
<BarChart data={topCategories} margin={{ top: 5, right: 10, left: -20, bottom: 5 }}> <BarChart data={topCategories} margin={{ top: 5, right: 10, left: -20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#231d18" /> <CartesianGrid strokeDasharray="3 3" stroke="#FFFFFF" />
<XAxis <XAxis
dataKey="name" dataKey="name"
tick={{ fill: '#555', fontSize: 10 }} tick={{ fill: '#555', fontSize: 10 }}
@@ -523,7 +523,7 @@ export default function ForumDashboardPage() {
iconType="circle" iconType="circle"
iconSize={8} iconSize={8}
/> />
<Bar dataKey="thread_count" fill="#F0A623" radius={[3, 3, 0, 0]} name="Threads" /> <Bar dataKey="thread_count" fill="#1D4ED8" radius={[3, 3, 0, 0]} name="Threads" />
<Bar dataKey="post_count" fill="#10b981" radius={[3, 3, 0, 0]} name="Posts" /> <Bar dataKey="post_count" fill="#10b981" radius={[3, 3, 0, 0]} name="Posts" />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -533,22 +533,22 @@ export default function ForumDashboardPage() {
{/* Moderation Stats */} {/* Moderation Stats */}
<section className="mb-8"> <section className="mb-8">
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#231d18] flex items-center justify-between"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex items-center justify-between">
<div> <div>
<h2 className="text-sm font-display font-bold text-white">Moderation Stats</h2> <h2 className="text-sm font-display font-bold text-white">Moderation Stats</h2>
<p className="text-xs text-[#555] font-body mt-0.5">Current moderation state across all forum content</p> <p className="text-xs text-[#555] font-body mt-0.5">Current moderation state across all forum content</p>
</div> </div>
<Link href="/admin/forum-moderation" className="text-xs text-[#F0A623] hover:text-blue-300 font-body transition-colors">Open moderation </Link> <Link href="/admin/forum-moderation" className="text-xs text-[#1D4ED8] hover:text-blue-300 font-body transition-colors">Open moderation </Link>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 divide-x divide-y sm:divide-y-0 divide-[#231d18]"> <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 divide-x divide-y sm:divide-y-0 divide-[#FFFFFF]">
{[ {[
{ label: 'Flagged Threads', value: moderation.flaggedThreads, color: 'text-red-400', bg: 'bg-red-500/10' }, { label: 'Flagged Threads', value: moderation.flaggedThreads, color: 'text-red-400', bg: 'bg-red-500/10' },
{ label: 'Flagged Replies', value: moderation.flaggedReplies, color: 'text-red-400', bg: 'bg-red-500/10' }, { label: 'Flagged Replies', value: moderation.flaggedReplies, color: 'text-red-400', bg: 'bg-red-500/10' },
{ label: 'Locked Threads', value: moderation.lockedThreads, color: 'text-orange-400', bg: 'bg-orange-500/10' }, { label: 'Locked Threads', value: moderation.lockedThreads, color: 'text-orange-400', bg: 'bg-orange-500/10' },
{ label: 'Pinned Threads', value: moderation.pinnedThreads, color: 'text-yellow-400', bg: 'bg-yellow-500/10' }, { label: 'Pinned Threads', value: moderation.pinnedThreads, color: 'text-yellow-400', bg: 'bg-yellow-500/10' },
{ label: 'Featured Threads', value: moderation.featuredThreads, color: 'text-blue-400', bg: 'bg-blue-500/10' }, { label: 'Featured Threads', value: moderation.featuredThreads, color: 'text-blue-400', bg: 'bg-blue-500/10' },
{ label: 'Hidden Replies', value: moderation.hiddenReplies, color: 'text-[#888]', bg: 'bg-[#231d18]' }, { label: 'Hidden Replies', value: moderation.hiddenReplies, color: 'text-[#888]', bg: 'bg-[#FFFFFF]' },
].map(({ label, value, color, bg }) => ( ].map(({ label, value, color, bg }) => (
<div key={label} className="px-4 py-5 flex flex-col items-center gap-1 text-center"> <div key={label} className="px-4 py-5 flex flex-col items-center gap-1 text-center">
<p className={`text-2xl font-bold font-display ${color}`}>{value}</p> <p className={`text-2xl font-bold font-display ${color}`}>{value}</p>

View File

@@ -62,7 +62,7 @@ function FlagModal({ onConfirm, onCancel }: FlagModalProps) {
const [reason, setReason] = useState(''); const [reason, setReason] = useState('');
return ( return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 px-4"> <div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 px-4">
<div className="bg-[#231d18] border border-[#333] rounded-xl p-6 w-full max-w-md"> <div className="bg-[#FFFFFF] border border-[#333] rounded-xl p-6 w-full max-w-md">
<h3 className="text-white font-heading font-bold text-lg mb-2">Flag Content</h3> <h3 className="text-white font-heading font-bold text-lg mb-2">Flag Content</h3>
<p className="text-[#888] font-body text-sm mb-4">Provide a reason for flagging this content (optional).</p> <p className="text-[#888] font-body text-sm mb-4">Provide a reason for flagging this content (optional).</p>
<textarea <textarea
@@ -70,10 +70,10 @@ function FlagModal({ onConfirm, onCancel }: FlagModalProps) {
onChange={e => setReason(e.target.value)} onChange={e => setReason(e.target.value)}
rows={3} rows={3}
placeholder="e.g. Spam, harassment, off-topic..." placeholder="e.g. Spam, harassment, off-topic..."
className="w-full bg-[#1c1815] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ef4444] resize-none mb-4" className="w-full bg-[#F8FAFC] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#ef4444] resize-none mb-4"
/> />
<div className="flex gap-3 justify-end"> <div className="flex gap-3 justify-end">
<button onClick={onCancel} className="px-4 py-2 text-sm font-body text-[#888] hover:text-white bg-[#3a322b] hover:bg-[#333] rounded-lg transition-colors"> <button onClick={onCancel} className="px-4 py-2 text-sm font-body text-[#888] hover:text-white bg-[#DCE6F2] hover:bg-[#333] rounded-lg transition-colors">
Cancel Cancel
</button> </button>
<button onClick={() => onConfirm(reason)} className="px-4 py-2 text-sm font-body font-semibold text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors"> <button onClick={() => onConfirm(reason)} className="px-4 py-2 text-sm font-body font-semibold text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors">
@@ -119,14 +119,14 @@ function ThreadRow({ thread, onAction, loadingId }: ThreadRowProps) {
onCancel={() => setShowFlagModal(false)} onCancel={() => setShowFlagModal(false)}
/> />
)} )}
<div className={`bg-[#111] border rounded-lg p-4 transition-colors ${isAiFlagged ? 'border-purple-500/50' : thread.is_flagged ? 'border-red-500/40' : 'border-[#3a322b]'}`}> <div className={`bg-[#111] border rounded-lg p-4 transition-colors ${isAiFlagged ? 'border-purple-500/50' : thread.is_flagged ? 'border-red-500/40' : 'border-[#DCE6F2]'}`}>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{/* Title + badges */} {/* Title + badges */}
<div className="flex flex-wrap items-center gap-2 mb-1"> <div className="flex flex-wrap items-center gap-2 mb-1">
<Link <Link
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="text-white font-body font-semibold text-sm hover:text-[#F0A623] transition-colors truncate max-w-xs" className="text-white font-body font-semibold text-sm hover:text-[#1D4ED8] transition-colors truncate max-w-xs"
target="_blank" target="_blank"
> >
{thread.title} {thread.title}
@@ -159,7 +159,7 @@ function ThreadRow({ thread, onAction, loadingId }: ThreadRowProps) {
onClick={() => onAction(thread.id, thread.is_pinned ? 'unpin' : 'pin')} onClick={() => onAction(thread.id, thread.is_pinned ? 'unpin' : 'pin')}
className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${ className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${
thread.is_pinned thread.is_pinned
? 'bg-amber-400/20 text-amber-400 hover:bg-amber-400/30' :'bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-amber-400' ? 'bg-amber-400/20 text-amber-400 hover:bg-amber-400/30' :'bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-amber-400'
}`} }`}
> >
{thread.is_pinned ? 'Unpin' : 'Pin'} {thread.is_pinned ? 'Unpin' : 'Pin'}
@@ -169,7 +169,7 @@ function ThreadRow({ thread, onAction, loadingId }: ThreadRowProps) {
onClick={() => onAction(thread.id, thread.is_featured ? 'unfeature' : 'feature')} onClick={() => onAction(thread.id, thread.is_featured ? 'unfeature' : 'feature')}
className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${ className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${
thread.is_featured thread.is_featured
? 'bg-yellow-400/20 text-yellow-400 hover:bg-yellow-400/30' :'bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-yellow-400' ? 'bg-yellow-400/20 text-yellow-400 hover:bg-yellow-400/30' :'bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-yellow-400'
}`} }`}
> >
{thread.is_featured ? 'Unfeature' : 'Feature'} {thread.is_featured ? 'Unfeature' : 'Feature'}
@@ -179,7 +179,7 @@ function ThreadRow({ thread, onAction, loadingId }: ThreadRowProps) {
onClick={() => onAction(thread.id, thread.is_locked ? 'unlock' : 'lock')} onClick={() => onAction(thread.id, thread.is_locked ? 'unlock' : 'lock')}
className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${ className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${
thread.is_locked thread.is_locked
? 'bg-orange-400/20 text-orange-400 hover:bg-orange-400/30' :'bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-orange-400' ? 'bg-orange-400/20 text-orange-400 hover:bg-orange-400/30' :'bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-orange-400'
}`} }`}
> >
{thread.is_locked ? 'Unlock' : 'Lock'} {thread.is_locked ? 'Unlock' : 'Lock'}
@@ -199,7 +199,7 @@ function ThreadRow({ thread, onAction, loadingId }: ThreadRowProps) {
<button <button
disabled={isLoading} disabled={isLoading}
onClick={() => setShowFlagModal(true)} onClick={() => setShowFlagModal(true)}
className="px-2.5 py-1 text-xs font-body font-semibold rounded-md bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-red-400 transition-colors disabled:opacity-50" className="px-2.5 py-1 text-xs font-body font-semibold rounded-md bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-red-400 transition-colors disabled:opacity-50"
> >
Flag Flag
</button> </button>
@@ -235,7 +235,7 @@ function ReplyRow({ reply, onAction, loadingId }: ReplyRowProps) {
onCancel={() => setShowFlagModal(false)} onCancel={() => setShowFlagModal(false)}
/> />
)} )}
<div className={`bg-[#111] border rounded-lg p-4 transition-colors ${isAiFlagged ? 'border-purple-500/50' : reply.is_flagged ? 'border-red-500/40' : reply.is_hidden ? 'border-[#333] opacity-60' : 'border-[#3a322b]'}`}> <div className={`bg-[#111] border rounded-lg p-4 transition-colors ${isAiFlagged ? 'border-purple-500/50' : reply.is_flagged ? 'border-red-500/40' : reply.is_hidden ? 'border-[#333] opacity-60' : 'border-[#DCE6F2]'}`}>
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
{/* Thread link + badges */} {/* Thread link + badges */}
@@ -243,13 +243,13 @@ function ReplyRow({ reply, onAction, loadingId }: ReplyRowProps) {
{reply.forum_threads && ( {reply.forum_threads && (
<Link <Link
href={`/forum/thread/${reply.forum_threads.id}`} href={`/forum/thread/${reply.forum_threads.id}`}
className="text-[#F0A623] font-body text-xs hover:underline truncate max-w-xs" className="text-[#1D4ED8] font-body text-xs hover:underline truncate max-w-xs"
target="_blank" target="_blank"
> >
{reply.forum_threads.title} {reply.forum_threads.title}
</Link> </Link>
)} )}
{reply.is_hidden && <StatusBadge label="👁 Hidden" color="text-[#888] bg-[#3a322b]" />} {reply.is_hidden && <StatusBadge label="👁 Hidden" color="text-[#888] bg-[#DCE6F2]" />}
{isAiFlagged && <StatusBadge label="🤖 AI Flagged" color="text-purple-400 bg-purple-400/10" />} {isAiFlagged && <StatusBadge label="🤖 AI Flagged" color="text-purple-400 bg-purple-400/10" />}
{reply.is_flagged && !isAiFlagged && <StatusBadge label="🚩 Flagged" color="text-red-400 bg-red-400/10" />} {reply.is_flagged && !isAiFlagged && <StatusBadge label="🚩 Flagged" color="text-red-400 bg-red-400/10" />}
</div> </div>
@@ -273,8 +273,8 @@ function ReplyRow({ reply, onAction, loadingId }: ReplyRowProps) {
onClick={() => onAction(reply.id, reply.is_hidden ? 'unhide' : 'hide')} onClick={() => onAction(reply.id, reply.is_hidden ? 'unhide' : 'hide')}
className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${ className={`px-2.5 py-1 text-xs font-body font-semibold rounded-md transition-colors disabled:opacity-50 ${
reply.is_hidden reply.is_hidden
? 'bg-[#3a322b] text-[#aaa] hover:bg-[#333]' ? 'bg-[#DCE6F2] text-[#aaa] hover:bg-[#333]'
: 'bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-white' : 'bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-white'
}`} }`}
> >
{reply.is_hidden ? 'Unhide' : 'Hide'} {reply.is_hidden ? 'Unhide' : 'Hide'}
@@ -294,7 +294,7 @@ function ReplyRow({ reply, onAction, loadingId }: ReplyRowProps) {
<button <button
disabled={isLoading} disabled={isLoading}
onClick={() => setShowFlagModal(true)} onClick={() => setShowFlagModal(true)}
className="px-2.5 py-1 text-xs font-body font-semibold rounded-md bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-red-400 transition-colors disabled:opacity-50" className="px-2.5 py-1 text-xs font-body font-semibold rounded-md bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-red-400 transition-colors disabled:opacity-50"
> >
Flag Flag
</button> </button>
@@ -398,7 +398,7 @@ export default function ForumModerationPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -435,10 +435,10 @@ export default function ForumModerationPage() {
)} )}
{/* Page Header */} {/* Page Header */}
<div className="bg-[#231d18] border-b border-[#2a3a50]"> <div className="bg-[#FFFFFF] border-b border-[#2a3a50]">
<div className="max-w-7xl mx-auto px-4 py-6"> <div className="max-w-7xl mx-auto px-4 py-6">
<nav className="text-sm font-body text-[#666] mb-2"> <nav className="text-sm font-body text-[#666] mb-2">
<Link href="/admin" className="hover:text-[#F0A623] transition-colors">Admin</Link> <Link href="/admin" className="hover:text-[#1D4ED8] transition-colors">Admin</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<span className="text-[#aab4c4]">Forum Moderation</span> <span className="text-[#aab4c4]">Forum Moderation</span>
</nav> </nav>
@@ -453,7 +453,7 @@ export default function ForumModerationPage() {
<span className="text-xs text-purple-400 font-body bg-purple-400/10 border border-purple-400/20 px-3 py-1.5 rounded-lg"> <span className="text-xs text-purple-400 font-body bg-purple-400/10 border border-purple-400/20 px-3 py-1.5 rounded-lg">
🤖 Claude AI Auto-Moderation Active 🤖 Claude AI Auto-Moderation Active
</span> </span>
<span className="text-xs text-[#555] font-body bg-[#231d18] border border-[#3a322b] px-3 py-1.5 rounded-lg"> <span className="text-xs text-[#555] font-body bg-[#FFFFFF] border border-[#DCE6F2] px-3 py-1.5 rounded-lg">
🛡 Moderator View 🛡 Moderator View
</span> </span>
</div> </div>
@@ -463,14 +463,14 @@ export default function ForumModerationPage() {
<div className="max-w-7xl mx-auto px-4 py-6"> <div className="max-w-7xl mx-auto px-4 py-6">
{/* Tabs */} {/* Tabs */}
<div className="flex gap-1 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit mb-6"> <div className="flex gap-1 bg-[#111] border border-[#DCE6F2] rounded-lg p-1 w-fit mb-6">
{(['threads', 'replies'] as Tab[]).map(tab => ( {(['threads', 'replies'] as Tab[]).map(tab => (
<button <button
key={tab} key={tab}
onClick={() => { setActiveTab(tab); setPage(1); }} onClick={() => { setActiveTab(tab); setPage(1); }}
className={`px-5 py-2 text-sm font-body font-semibold rounded-md transition-colors capitalize ${ className={`px-5 py-2 text-sm font-body font-semibold rounded-md transition-colors capitalize ${
activeTab === tab activeTab === tab
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -488,8 +488,8 @@ export default function ForumModerationPage() {
onClick={() => { setThreadFilter(f.value); setPage(1); }} onClick={() => { setThreadFilter(f.value); setPage(1); }}
className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${ className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${
threadFilter === f.value threadFilter === f.value
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-white border border-[#3a322b]' : 'bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-white border border-[#DCE6F2]'
}`} }`}
> >
{f.label} {f.label}
@@ -501,8 +501,8 @@ export default function ForumModerationPage() {
onClick={() => { setReplyFilter(f.value); setPage(1); }} onClick={() => { setReplyFilter(f.value); setPage(1); }}
className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${ className={`px-3 py-1.5 text-xs font-body font-semibold rounded-lg transition-colors ${
replyFilter === f.value replyFilter === f.value
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'bg-[#231d18] text-[#888] hover:bg-[#3a322b] hover:text-white border border-[#3a322b]' : 'bg-[#FFFFFF] text-[#888] hover:bg-[#DCE6F2] hover:text-white border border-[#DCE6F2]'
}`} }`}
> >
{f.label} {f.label}
@@ -515,7 +515,7 @@ export default function ForumModerationPage() {
{/* Content */} {/* Content */}
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<div className="w-7 h-7 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-7 h-7 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : activeTab === 'threads' ? ( ) : activeTab === 'threads' ? (
<> <>
@@ -559,7 +559,7 @@ export default function ForumModerationPage() {
<button <button
disabled={page === 1} disabled={page === 1}
onClick={() => setPage(p => p - 1)} onClick={() => setPage(p => p - 1)}
className="px-4 py-2 text-sm font-body text-[#888] bg-[#231d18] border border-[#3a322b] rounded-lg hover:bg-[#3a322b] disabled:opacity-40 transition-colors" className="px-4 py-2 text-sm font-body text-[#888] bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg hover:bg-[#DCE6F2] disabled:opacity-40 transition-colors"
> >
Prev Prev
</button> </button>
@@ -567,7 +567,7 @@ export default function ForumModerationPage() {
<button <button
disabled={page === totalPages} disabled={page === totalPages}
onClick={() => setPage(p => p + 1)} onClick={() => setPage(p => p + 1)}
className="px-4 py-2 text-sm font-body text-[#888] bg-[#231d18] border border-[#3a322b] rounded-lg hover:bg-[#3a322b] disabled:opacity-40 transition-colors" className="px-4 py-2 text-sm font-body text-[#888] bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg hover:bg-[#DCE6F2] disabled:opacity-40 transition-colors"
> >
Next Next
</button> </button>

View File

@@ -136,13 +136,13 @@ export default function AdminForumSeedPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<div className="bg-[#231d18] border-b border-[#2a3a50]"> <div className="bg-[#FFFFFF] border-b border-[#2a3a50]">
<div className="max-w-container mx-auto px-4 py-6"> <div className="max-w-container mx-auto px-4 py-6">
<nav className="text-sm font-body text-[#666] mb-2"> <nav className="text-sm font-body text-[#666] mb-2">
<Link href="/admin" className="hover:text-[#F0A623] transition-colors">Admin</Link> <Link href="/admin" className="hover:text-[#1D4ED8] transition-colors">Admin</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<Link href="/forum" className="hover:text-[#F0A623] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#1D4ED8] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<span className="text-[#aab4c4]">Seed Tool</span> <span className="text-[#aab4c4]">Seed Tool</span>
</nav> </nav>
@@ -157,17 +157,17 @@ export default function AdminForumSeedPage() {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left: Editor */} {/* Left: Editor */}
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="flex border-b border-[#3a322b]"> <div className="flex border-b border-[#DCE6F2]">
<button <button
onClick={() => setActiveTab('import')} onClick={() => setActiveTab('import')}
className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'import' ? 'text-white border-b-2 border-[#F0A623]' : 'text-[#666] hover:text-[#aaa]'}`} className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'import' ? 'text-white border-b-2 border-[#1D4ED8]' : 'text-[#666] hover:text-[#aaa]'}`}
> >
Import Editor Import Editor
</button> </button>
<button <button
onClick={() => setActiveTab('json')} onClick={() => setActiveTab('json')}
className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'json' ? 'text-white border-b-2 border-[#F0A623]' : 'text-[#666] hover:text-[#aaa]'}`} className={`px-4 py-3 font-body text-sm font-semibold transition-colors ${activeTab === 'json' ? 'text-white border-b-2 border-[#1D4ED8]' : 'text-[#666] hover:text-[#aaa]'}`}
> >
JSON Schema JSON Schema
</button> </button>
@@ -182,20 +182,20 @@ export default function AdminForumSeedPage() {
value={seedData} value={seedData}
onChange={e => setSeedData(e.target.value)} onChange={e => setSeedData(e.target.value)}
rows={24} rows={24}
className="w-full bg-[#1c1815] border border-[#333] rounded-lg px-3 py-3 text-[#d0d0d0] font-mono text-xs focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#F8FAFC] border border-[#333] rounded-lg px-3 py-3 text-[#d0d0d0] font-mono text-xs focus:outline-none focus:border-[#1D4ED8] resize-none"
spellCheck={false} spellCheck={false}
/> />
<div className="flex gap-3 mt-3"> <div className="flex gap-3 mt-3">
<button <button
onClick={handleImport} onClick={handleImport}
disabled={importing} disabled={importing}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
{importing ? 'Importing...' : 'Import Threads'} {importing ? 'Importing...' : 'Import Threads'}
</button> </button>
<button <button
onClick={() => setSeedData(JSON.stringify(SEED_TEMPLATES, null, 2))} onClick={() => setSeedData(JSON.stringify(SEED_TEMPLATES, null, 2))}
className="bg-[#3a322b] hover:bg-[#333] text-[#aaa] font-body text-sm px-4 py-2 rounded-lg transition-colors" className="bg-[#DCE6F2] hover:bg-[#333] text-[#aaa] font-body text-sm px-4 py-2 rounded-lg transition-colors"
> >
Reset to Templates Reset to Templates
</button> </button>
@@ -206,7 +206,7 @@ export default function AdminForumSeedPage() {
{activeTab === 'json' && ( {activeTab === 'json' && (
<div className="p-4"> <div className="p-4">
<p className="text-[#888] font-body text-xs mb-3">Required JSON structure for seed data:</p> <p className="text-[#888] font-body text-xs mb-3">Required JSON structure for seed data:</p>
<pre className="bg-[#1c1815] border border-[#333] rounded-lg p-4 text-[#d0d0d0] font-mono text-xs overflow-auto"> <pre className="bg-[#F8FAFC] border border-[#333] rounded-lg p-4 text-[#d0d0d0] font-mono text-xs overflow-auto">
{`[ {`[
{ {
"category_slug": "live-production", "category_slug": "live-production",
@@ -226,7 +226,7 @@ export default function AdminForumSeedPage() {
<h4 className="text-white font-body font-semibold text-sm mb-2">Available Category Slugs</h4> <h4 className="text-white font-body font-semibold text-sm mb-2">Available Category Slugs</h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{['live-production','ip-cloud','audio','cameras','storage-mam','streaming','ai-automation','post-production','nab-ibc','career-jobs','gear-reviews','general'].map(slug => ( {['live-production','ip-cloud','audio','cameras','storage-mam','streaming','ai-automation','post-production','nab-ibc','career-jobs','gear-reviews','general'].map(slug => (
<code key={slug} className="bg-[#1c1815] border border-[#333] text-[#F0A623] font-mono text-xs px-2 py-1 rounded">{slug}</code> <code key={slug} className="bg-[#F8FAFC] border border-[#333] text-[#1D4ED8] font-mono text-xs px-2 py-1 rounded">{slug}</code>
))} ))}
</div> </div>
</div> </div>
@@ -239,7 +239,7 @@ export default function AdminForumSeedPage() {
<div className="space-y-4"> <div className="space-y-4">
{/* Import Results */} {/* Import Results */}
{results.length > 0 && ( {results.length > 0 && (
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg p-4"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-4">
<h3 className="text-white font-heading font-semibold mb-3">Import Results</h3> <h3 className="text-white font-heading font-semibold mb-3">Import Results</h3>
<div className="flex gap-4 mb-3"> <div className="flex gap-4 mb-3">
<div className="text-center"> <div className="text-center">
@@ -265,20 +265,20 @@ export default function AdminForumSeedPage() {
)} )}
{/* Quick Links */} {/* Quick Links */}
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg p-4"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-4">
<h3 className="text-white font-heading font-semibold mb-3">Quick Links</h3> <h3 className="text-white font-heading font-semibold mb-3">Quick Links</h3>
<div className="space-y-2"> <div className="space-y-2">
<Link href="/forum" className="flex items-center gap-2 text-[#F0A623] hover:underline font-body text-sm"> <Link href="/forum" className="flex items-center gap-2 text-[#1D4ED8] hover:underline font-body text-sm">
<span></span> View Forum <span></span> View Forum
</Link> </Link>
<Link href="/admin" className="flex items-center gap-2 text-[#F0A623] hover:underline font-body text-sm"> <Link href="/admin" className="flex items-center gap-2 text-[#1D4ED8] hover:underline font-body text-sm">
<span></span> Admin Dashboard <span></span> Admin Dashboard
</Link> </Link>
</div> </div>
</div> </div>
{/* Tips */} {/* Tips */}
<div className="bg-[#231d18] border border-[#2a3a50] rounded-lg p-4"> <div className="bg-[#FFFFFF] border border-[#2a3a50] rounded-lg p-4">
<h3 className="text-white font-heading font-semibold mb-2 text-sm">Tips</h3> <h3 className="text-white font-heading font-semibold mb-2 text-sm">Tips</h3>
<ul className="text-[#aab4c4] font-body text-xs space-y-1.5"> <ul className="text-[#aab4c4] font-body text-xs space-y-1.5">
<li> Threads are imported as real posts they appear immediately in the forum</li> <li> Threads are imported as real posts they appear immediately in the forum</li>

View File

@@ -134,7 +134,7 @@ export default function WPImportPage() {
if (loading || (!user && !loading)) { if (loading || (!user && !loading)) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -142,10 +142,10 @@ export default function WPImportPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] text-[#cccccc]"> <div className="min-h-screen bg-[#0a0a0a] text-[#cccccc]">
{/* Page Header */} {/* Page Header */}
<div className="bg-[#1c1815] border-b border-[#3a322b]"> <div className="bg-[#F8FAFC] border-b border-[#DCE6F2]">
<div className="max-w-6xl mx-auto px-4 py-5"> <div className="max-w-6xl mx-auto px-4 py-5">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
<Link href="/home-page" className="text-[#555] hover:text-[#F0A623] transition-colors text-sm"> <Link href="/home-page" className="text-[#555] hover:text-[#1D4ED8] transition-colors text-sm">
Home Home
</Link> </Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
@@ -158,14 +158,14 @@ export default function WPImportPage() {
<h1 className="text-2xl font-bold text-white font-heading">WordPress Post Importer</h1> <h1 className="text-2xl font-bold text-white font-heading">WordPress Post Importer</h1>
<p className="text-[#666] text-sm mt-1"> <p className="text-[#666] text-sm mt-1">
Import posts from{' '} Import posts from{' '}
<a href="https://www.avbeat.com" target="_blank" rel="noopener noreferrer" className="text-[#F0A623] hover:underline"> <a href="https://www.avbeat.com" target="_blank" rel="noopener noreferrer" className="text-[#1D4ED8] hover:underline">
avbeat.com avbeat.com
</a>{' '} </a>{' '}
via WordPress REST API via WordPress REST API
</p> </p>
</div> </div>
<div className="flex items-center gap-2 text-xs text-[#555] bg-[#231d18] border border-[#3a322b] rounded px-3 py-2"> <div className="flex items-center gap-2 text-xs text-[#555] bg-[#FFFFFF] border border-[#DCE6F2] rounded px-3 py-2">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#F0A623]"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#1D4ED8]">
<circle cx="12" cy="12" r="10" /><path d="M12 8v4l3 3" /> <circle cx="12" cy="12" r="10" /><path d="M12 8v4l3 3" />
</svg> </svg>
{posts.length} posts imported {posts.length} posts imported
@@ -176,9 +176,9 @@ export default function WPImportPage() {
<div className="max-w-6xl mx-auto px-4 py-6 space-y-6"> <div className="max-w-6xl mx-auto px-4 py-6 space-y-6">
{/* Import Controls */} {/* Import Controls */}
<div className="bg-[#1c1815] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg p-5">
<h2 className="text-base font-bold text-white font-heading mb-4 flex items-center gap-2"> <h2 className="text-base font-bold text-white font-heading mb-4 flex items-center gap-2">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#F0A623]"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-[#1D4ED8]">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" />
</svg> </svg>
Run Import Run Import
@@ -191,7 +191,7 @@ export default function WPImportPage() {
value={maxPages} value={maxPages}
onChange={(e) => setMaxPages(Number(e.target.value))} onChange={(e) => setMaxPages(Number(e.target.value))}
disabled={isImporting} disabled={isImporting}
className="bg-[#231d18] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] disabled:opacity-50"> className="bg-[#FFFFFF] border border-[#333] text-[#cccccc] text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] disabled:opacity-50">
<option value={1}>1 page (~20 posts)</option> <option value={1}>1 page (~20 posts)</option>
<option value={3}>3 pages (~60 posts)</option> <option value={3}>3 pages (~60 posts)</option>
<option value={5}>5 pages (~100 posts)</option> <option value={5}>5 pages (~100 posts)</option>
@@ -204,7 +204,7 @@ export default function WPImportPage() {
<button <button
onClick={handleImport} onClick={handleImport}
disabled={isImporting} disabled={isImporting}
className="flex items-center gap-2 bg-[#F0A623] hover:bg-[#BA7517] disabled:bg-[#412402] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors"> className="flex items-center gap-2 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:bg-[#DCE6F2] disabled:cursor-not-allowed text-white text-sm font-bold px-5 py-2 rounded transition-colors">
{isImporting ? ( {isImporting ? (
<> <>
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
@@ -223,16 +223,16 @@ export default function WPImportPage() {
{/* Import Progress / Result */} {/* Import Progress / Result */}
{isImporting && ( {isImporting && (
<div className="mt-4 bg-[#0d1a2e] border border-[#412402] rounded p-4"> <div className="mt-4 bg-[#0d1a2e] border border-[#DCE6F2] rounded p-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-5 h-5 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin flex-shrink-0" /> <div className="w-5 h-5 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin flex-shrink-0" />
<div> <div>
<p className="text-[#F0A623] text-sm font-bold">Import in progress</p> <p className="text-[#1D4ED8] text-sm font-bold">Import in progress</p>
<p className="text-[#666] text-xs mt-0.5">Fetching posts from avbeat.com and saving to database...</p> <p className="text-[#666] text-xs mt-0.5">Fetching posts from avbeat.com and saving to database...</p>
</div> </div>
</div> </div>
<div className="mt-3 h-1.5 bg-[#231d18] rounded-full overflow-hidden"> <div className="mt-3 h-1.5 bg-[#FFFFFF] rounded-full overflow-hidden">
<div className="h-full bg-[#F0A623] rounded-full animate-pulse w-2/3" /> <div className="h-full bg-[#1D4ED8] rounded-full animate-pulse w-2/3" />
</div> </div>
</div> </div>
)} )}
@@ -261,7 +261,7 @@ export default function WPImportPage() {
{[ {[
{ label: 'Fetched', value: importResult.totalFetched, color: 'text-[#cccccc]' }, { label: 'Fetched', value: importResult.totalFetched, color: 'text-[#cccccc]' },
{ label: 'Imported', value: importResult.totalImported, color: 'text-green-400' }, { label: 'Imported', value: importResult.totalImported, color: 'text-green-400' },
{ label: 'Pages', value: importResult.pagesProcessed, color: 'text-[#F0A623]' }, { label: 'Pages', value: importResult.pagesProcessed, color: 'text-[#1D4ED8]' },
{ label: 'Errors', value: importResult.totalErrors, color: importResult.totalErrors ? 'text-red-400' : 'text-[#555]' }, { label: 'Errors', value: importResult.totalErrors, color: importResult.totalErrors ? 'text-red-400' : 'text-[#555]' },
].map((stat) => ( ].map((stat) => (
<div key={stat.label} className="bg-[#111] rounded p-2.5 text-center"> <div key={stat.label} className="bg-[#111] rounded p-2.5 text-center">
@@ -277,14 +277,14 @@ export default function WPImportPage() {
</div> </div>
{/* Tabs */} {/* Tabs */}
<div className="flex gap-0 border-b border-[#3a322b]"> <div className="flex gap-0 border-b border-[#DCE6F2]">
{(['posts', 'logs'] as const).map((tab) => ( {(['posts', 'logs'] as const).map((tab) => (
<button <button
key={tab} key={tab}
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-5 py-2.5 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${ className={`px-5 py-2.5 text-sm font-bold uppercase tracking-wider border-b-2 transition-colors ${
activeTab === tab activeTab === tab
? 'border-[#F0A623] text-[#F0A623]' ? 'border-[#1D4ED8] text-[#1D4ED8]'
: 'border-transparent text-[#666] hover:text-[#999]' : 'border-transparent text-[#666] hover:text-[#999]'
}`}> }`}>
{tab === 'posts' ? `Imported Posts (${posts.length})` : `Import History (${logs.length})`} {tab === 'posts' ? `Imported Posts (${posts.length})` : `Import History (${logs.length})`}
@@ -306,17 +306,17 @@ export default function WPImportPage() {
placeholder="Search posts..." placeholder="Search posts..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#3a322b] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="w-full bg-[#111] border border-[#DCE6F2] text-[#cccccc] text-sm rounded pl-9 pr-4 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
/> />
</div> </div>
</div> </div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filteredPosts.length === 0 ? ( ) : filteredPosts.length === 0 ? (
<div className="text-center py-16 bg-[#111] border border-[#3a322b] rounded-lg"> <div className="text-center py-16 bg-[#111] border border-[#DCE6F2] rounded-lg">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333] mx-auto mb-3"> <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333] mx-auto mb-3">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" /> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" />
</svg> </svg>
@@ -327,10 +327,10 @@ export default function WPImportPage() {
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{filteredPosts.map((post) => ( {filteredPosts.map((post) => (
<div key={post.id} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 flex items-start gap-4 hover:border-[#333] transition-colors"> <div key={post.id} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 flex items-start gap-4 hover:border-[#333] transition-colors">
{/* Thumbnail */} {/* Thumbnail */}
{post.featured_image ? ( {post.featured_image ? (
<div className="flex-shrink-0 w-16 h-12 rounded overflow-hidden bg-[#231d18]"> <div className="flex-shrink-0 w-16 h-12 rounded overflow-hidden bg-[#FFFFFF]">
<AppImage <AppImage
src={post.featured_image} src={post.featured_image}
alt={post.featured_image_alt || post.title} alt={post.featured_image_alt || post.title}
@@ -340,7 +340,7 @@ export default function WPImportPage() {
/> />
</div> </div>
) : ( ) : (
<div className="flex-shrink-0 w-16 h-12 rounded bg-[#231d18] flex items-center justify-center"> <div className="flex-shrink-0 w-16 h-12 rounded bg-[#FFFFFF] flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333]"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[#333]">
<rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /> <rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" />
</svg> </svg>
@@ -354,7 +354,7 @@ export default function WPImportPage() {
<h3 className="text-sm font-bold text-[#cccccc] leading-snug line-clamp-1">{post.title}</h3> <h3 className="text-sm font-bold text-[#cccccc] leading-snug line-clamp-1">{post.title}</h3>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-1"> <div className="flex flex-wrap items-center gap-x-3 gap-y-1 mt-1">
{post.category && ( {post.category && (
<span className="text-[10px] font-bold uppercase tracking-wider text-[#F0A623] bg-[#0d1a2e] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-bold uppercase tracking-wider text-[#1D4ED8] bg-[#0d1a2e] px-1.5 py-0.5 rounded">
{post.category} {post.category}
</span> </span>
)} )}
@@ -368,7 +368,7 @@ export default function WPImportPage() {
href={`https://www.avbeat.com/${post.wp_slug}`} href={`https://www.avbeat.com/${post.wp_slug}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#555] hover:text-[#F0A623] transition-colors p-1" className="text-[#555] hover:text-[#1D4ED8] transition-colors p-1"
title="View on avbeat.com"> title="View on avbeat.com">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />
@@ -402,16 +402,16 @@ export default function WPImportPage() {
<div> <div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : logs.length === 0 ? ( ) : logs.length === 0 ? (
<div className="text-center py-16 bg-[#111] border border-[#3a322b] rounded-lg"> <div className="text-center py-16 bg-[#111] border border-[#DCE6F2] rounded-lg">
<p className="text-[#555] text-sm">No import history yet.</p> <p className="text-[#555] text-sm">No import history yet.</p>
</div> </div>
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{logs.map((log) => ( {logs.map((log) => (
<div key={log.id} className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div key={log.id} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<div className="flex items-start justify-between gap-4"> <div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{log.status === 'completed' ? ( {log.status === 'completed' ? (
@@ -439,7 +439,7 @@ export default function WPImportPage() {
{ label: 'Skipped', value: log.total_skipped }, { label: 'Skipped', value: log.total_skipped },
{ label: 'Errors', value: log.total_errors }, { label: 'Errors', value: log.total_errors },
].map((stat) => ( ].map((stat) => (
<div key={stat.label} className="bg-[#1c1815] rounded p-2 text-center"> <div key={stat.label} className="bg-[#F8FAFC] rounded p-2 text-center">
<p className="text-base font-bold text-[#cccccc]">{stat.value}</p> <p className="text-base font-bold text-[#cccccc]">{stat.value}</p>
<p className="text-[10px] text-[#444] uppercase tracking-wider">{stat.label}</p> <p className="text-[10px] text-[#444] uppercase tracking-wider">{stat.label}</p>
</div> </div>

View File

@@ -67,11 +67,11 @@ function fmtDate(dateStr: string): string {
// ─── Sub-components ─────────────────────────────────────────────────────────── // ─── Sub-components ───────────────────────────────────────────────────────────
function StatCard({ label, value, sub, accent = '#F0A623', icon }: { function StatCard({ label, value, sub, accent = '#1D4ED8', icon }: {
label: string; value: string; sub: string; accent?: string; icon: React.ReactNode; label: string; value: string; sub: string; accent?: string; icon: React.ReactNode;
}) { }) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-5 flex items-start gap-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-5 flex items-start gap-4">
<div <div
className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0" className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0"
style={{ background: `${accent}18` }} style={{ background: `${accent}18` }}
@@ -90,7 +90,7 @@ function StatCard({ label, value, sub, accent = '#F0A623', icon }: {
function ChartTooltip({ active, payload, label }: any) { function ChartTooltip({ active, payload, label }: any) {
if (!active || !payload?.length) return null; if (!active || !payload?.length) return null;
return ( return (
<div className="bg-[#231d18] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl"> <div className="bg-[#FFFFFF] border border-[#333] rounded-lg px-3 py-2 text-xs font-body shadow-xl">
{label && <p className="text-[#888] mb-1">{label}</p>} {label && <p className="text-[#888] mb-1">{label}</p>}
{payload.map((entry: any, i: number) => ( {payload.map((entry: any, i: number) => (
<p key={i} style={{ color: entry.color }} className="leading-5"> <p key={i} style={{ color: entry.color }} className="leading-5">
@@ -243,7 +243,7 @@ export default function NewsletterAnalyticsPage() {
if (loading || loadingData) { if (loading || loadingData) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -258,7 +258,7 @@ export default function NewsletterAnalyticsPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] text-white"> <div className="min-h-screen bg-[#0a0a0a] text-white">
{/* Header */} {/* Header */}
<div className="border-b border-[#231d18] bg-[#1c1815] sticky top-0 z-10"> <div className="border-b border-[#FFFFFF] bg-[#F8FAFC] sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between"> <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-white transition-colors"> <Link href="/admin" className="text-[#555] hover:text-white transition-colors">
@@ -266,7 +266,7 @@ export default function NewsletterAnalyticsPage() {
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" /> <path strokeLinecap="round" strokeLinejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
</svg> </svg>
</Link> </Link>
<div className="w-px h-5 bg-[#3a322b]" /> <div className="w-px h-5 bg-[#DCE6F2]" />
<Link href="/admin/newsletter" className="text-[#555] hover:text-white text-sm font-body transition-colors"> <Link href="/admin/newsletter" className="text-[#555] hover:text-white text-sm font-body transition-colors">
Newsletter Newsletter
</Link> </Link>
@@ -278,14 +278,14 @@ export default function NewsletterAnalyticsPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link <Link
href="/admin/newsletter/templates" href="/admin/newsletter/templates"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg> </svg>
Templates Templates
</Link> </Link>
<div className="w-px h-8 bg-[#3a322b]" /> <div className="w-px h-8 bg-[#DCE6F2]" />
<div className="text-right"> <div className="text-right">
<p className="text-white text-sm font-bold font-heading">{fmt(totalSubscribers)}</p> <p className="text-white text-sm font-bold font-heading">{fmt(totalSubscribers)}</p>
<p className="text-[#555] text-xs font-body">active subscribers</p> <p className="text-[#555] text-xs font-body">active subscribers</p>
@@ -308,7 +308,7 @@ export default function NewsletterAnalyticsPage() {
label="Avg Open Rate" label="Avg Open Rate"
value={pct(avgOpenRate)} value={pct(avgOpenRate)}
sub={`across ${digestData.length} digests`} sub={`across ${digestData.length} digests`}
accent="#F0A623" accent="#1D4ED8"
icon={ icon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
@@ -352,7 +352,7 @@ export default function NewsletterAnalyticsPage() {
</div> </div>
{/* Open & Click Rate Trend */} {/* Open & Click Rate Trend */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<SectionHeader title="Open & Click Rate Trend" subtitle="Rate per digest over time" /> <SectionHeader title="Open & Click Rate Trend" subtitle="Rate per digest over time" />
<div className="h-64"> <div className="h-64">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
@@ -362,7 +362,7 @@ export default function NewsletterAnalyticsPage() {
<YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => `${v}%`} /> <YAxis tick={{ fill: '#555', fontSize: 10 }} tickLine={false} axisLine={false} tickFormatter={(v) => `${v}%`} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
<Legend wrapperStyle={{ fontSize: 11, color: '#888' }} /> <Legend wrapperStyle={{ fontSize: 11, color: '#888' }} />
<Line type="monotone" dataKey="Open Rate" stroke="#F0A623" strokeWidth={2} dot={{ r: 3, fill: '#F0A623' }} activeDot={{ r: 5 }} /> <Line type="monotone" dataKey="Open Rate" stroke="#1D4ED8" strokeWidth={2} dot={{ r: 3, fill: '#1D4ED8' }} activeDot={{ r: 5 }} />
<Line type="monotone" dataKey="Click Rate" stroke="#10b981" strokeWidth={2} dot={{ r: 3, fill: '#10b981' }} activeDot={{ r: 5 }} /> <Line type="monotone" dataKey="Click Rate" stroke="#10b981" strokeWidth={2} dot={{ r: 3, fill: '#10b981' }} activeDot={{ r: 5 }} />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -370,7 +370,7 @@ export default function NewsletterAnalyticsPage() {
</div> </div>
{/* Subscriber Growth Chart */} {/* Subscriber Growth Chart */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-6"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-6">
<SectionHeader title="Subscriber Growth" subtitle="Monthly net new subscribers vs unsubscribes" /> <SectionHeader title="Subscriber Growth" subtitle="Monthly net new subscribers vs unsubscribes" />
<div className="h-64"> <div className="h-64">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
@@ -380,7 +380,7 @@ export default function NewsletterAnalyticsPage() {
<YAxis tick={{ fill: '#555', fontSize: 11 }} tickLine={false} axisLine={false} /> <YAxis tick={{ fill: '#555', fontSize: 11 }} tickLine={false} axisLine={false} />
<Tooltip content={<ChartTooltip />} /> <Tooltip content={<ChartTooltip />} />
<Legend wrapperStyle={{ fontSize: 11, color: '#888' }} /> <Legend wrapperStyle={{ fontSize: 11, color: '#888' }} />
<Bar dataKey="subscribers" name="Total Subscribers" fill="#F0A623" radius={[3, 3, 0, 0]} /> <Bar dataKey="subscribers" name="Total Subscribers" fill="#1D4ED8" radius={[3, 3, 0, 0]} />
<Bar dataKey="unsubscribes" name="Unsubscribes" fill="#ef4444" radius={[3, 3, 0, 0]} /> <Bar dataKey="unsubscribes" name="Unsubscribes" fill="#ef4444" radius={[3, 3, 0, 0]} />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -388,7 +388,7 @@ export default function NewsletterAnalyticsPage() {
</div> </div>
{/* Per-Digest Performance Table */} {/* Per-Digest Performance Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-6 py-5 border-b border-[#1e1e1e]"> <div className="px-6 py-5 border-b border-[#1e1e1e]">
<SectionHeader title="Per-Digest Performance" subtitle="Click column headers to sort" /> <SectionHeader title="Per-Digest Performance" subtitle="Click column headers to sort" />
</div> </div>
@@ -421,7 +421,7 @@ export default function NewsletterAnalyticsPage() {
{sortedDigests.map((digest, i) => ( {sortedDigests.map((digest, i) => (
<tr <tr
key={digest.id} key={digest.id}
className={`border-b border-[#231d18] hover:bg-[#231d18] transition-colors ${i % 2 === 0 ? '' : 'bg-[#1c1815]'}`} className={`border-b border-[#FFFFFF] hover:bg-[#FFFFFF] transition-colors ${i % 2 === 0 ? '' : 'bg-[#F8FAFC]'}`}
> >
<td className="px-4 py-3 text-white max-w-xs"> <td className="px-4 py-3 text-white max-w-xs">
<p className="truncate font-medium" title={digest.subject}>{digest.subject}</p> <p className="truncate font-medium" title={digest.subject}>{digest.subject}</p>

View File

@@ -252,7 +252,7 @@ export default function CampaignEditorPage() {
article_blocks: payload.article_blocks, article_blocks: payload.article_blocks,
layout: 'featured', layout: 'featured',
style: 'dark', style: 'dark',
accent_color: '#F0A623', accent_color: '#1D4ED8',
is_preset: false, is_preset: false,
}), }),
}); });
@@ -296,7 +296,7 @@ export default function CampaignEditorPage() {
)} )}
{/* ── Top Bar ── */} {/* ── Top Bar ── */}
<div className="sticky top-0 z-40 bg-[#0a0a0a]/95 backdrop-blur border-b border-[#231d18]"> <div className="sticky top-0 z-40 bg-[#0a0a0a]/95 backdrop-blur border-b border-[#FFFFFF]">
<div className="max-w-screen-xl mx-auto px-4 h-14 flex items-center justify-between gap-4"> <div className="max-w-screen-xl mx-auto px-4 h-14 flex items-center justify-between gap-4">
<div className="flex items-center gap-3 min-w-0"> <div className="flex items-center gap-3 min-w-0">
<Link href="/admin/newsletter" className="text-[#555] hover:text-white transition-colors flex-shrink-0"> <Link href="/admin/newsletter" className="text-[#555] hover:text-white transition-colors flex-shrink-0">
@@ -313,7 +313,7 @@ export default function CampaignEditorPage() {
className="bg-transparent text-sm font-medium text-white placeholder-[#444] outline-none min-w-0 w-48" className="bg-transparent text-sm font-medium text-white placeholder-[#444] outline-none min-w-0 w-48"
/> />
<span className={`text-xs px-2 py-0.5 rounded-full font-medium flex-shrink-0 ${ <span className={`text-xs px-2 py-0.5 rounded-full font-medium flex-shrink-0 ${
campaign.status === 'draft' ? 'bg-[#231d18] text-[#666]' : campaign.status === 'draft' ? 'bg-[#FFFFFF] text-[#666]' :
campaign.status === 'scheduled'? 'bg-yellow-400/10 text-yellow-400' : 'bg-green-400/10 text-green-400' campaign.status === 'scheduled'? 'bg-yellow-400/10 text-yellow-400' : 'bg-green-400/10 text-green-400'
}`}> }`}>
{campaign.status} {campaign.status}
@@ -339,7 +339,7 @@ export default function CampaignEditorPage() {
<button <button
onClick={() => handleSave('draft')} onClick={() => handleSave('draft')}
disabled={saving} disabled={saving}
className="px-3 py-1.5 text-xs font-medium bg-[#231d18] hover:bg-[#222] border border-[#3a322b] text-[#aaa] rounded-lg transition-colors disabled:opacity-50" className="px-3 py-1.5 text-xs font-medium bg-[#FFFFFF] hover:bg-[#222] border border-[#DCE6F2] text-[#aaa] rounded-lg transition-colors disabled:opacity-50"
> >
{saving ? 'Saving…' : 'Save Draft'} {saving ? 'Saving…' : 'Save Draft'}
</button> </button>
@@ -393,9 +393,9 @@ export default function CampaignEditorPage() {
<div className="space-y-2"> <div className="space-y-2">
{loadingArticles ? ( {loadingArticles ? (
Array.from({ length: 5 }).map((_, i) => ( Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="bg-[#111] border border-[#231d18] rounded-xl p-4 animate-pulse"> <div key={i} className="bg-[#111] border border-[#FFFFFF] rounded-xl p-4 animate-pulse">
<div className="h-3 bg-[#1e1e1e] rounded w-3/4 mb-2" /> <div className="h-3 bg-[#1e1e1e] rounded w-3/4 mb-2" />
<div className="h-2.5 bg-[#231d18] rounded w-1/2" /> <div className="h-2.5 bg-[#FFFFFF] rounded w-1/2" />
</div> </div>
)) ))
) : articles.length === 0 ? ( ) : articles.length === 0 ? (
@@ -409,7 +409,7 @@ export default function CampaignEditorPage() {
onClick={() => toggleArticle(article)} onClick={() => toggleArticle(article)}
className={`group relative bg-[#111] border rounded-xl p-4 cursor-pointer transition-all ${ className={`group relative bg-[#111] border rounded-xl p-4 cursor-pointer transition-all ${
selected selected
? 'border-white/20 bg-white/5' :'border-[#231d18] hover:border-[#3a322b]' ? 'border-white/20 bg-white/5' :'border-[#FFFFFF] hover:border-[#DCE6F2]'
}`} }`}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
@@ -427,7 +427,7 @@ export default function CampaignEditorPage() {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
{article.category && ( {article.category && (
<span className="text-[10px] font-semibold uppercase tracking-wider text-[#555] bg-[#231d18] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-semibold uppercase tracking-wider text-[#555] bg-[#FFFFFF] px-1.5 py-0.5 rounded">
{article.category} {article.category}
</span> </span>
)} )}
@@ -454,7 +454,7 @@ export default function CampaignEditorPage() {
{(activePanel === 'settings' || activePanel === 'articles') && ( {(activePanel === 'settings' || activePanel === 'articles') && (
<> <>
{/* Subject Line */} {/* Subject Line */}
<div className="bg-[#111] border border-[#231d18] rounded-xl p-4"> <div className="bg-[#111] border border-[#FFFFFF] rounded-xl p-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="text-xs font-semibold text-white uppercase tracking-wider">Subject Line</h3> <h3 className="text-xs font-semibold text-white uppercase tracking-wider">Subject Line</h3>
<span className={`text-[10px] font-mono ${subjectCharCount > 60 ? 'text-yellow-400' : 'text-[#444]'}`}> <span className={`text-[10px] font-mono ${subjectCharCount > 60 ? 'text-yellow-400' : 'text-[#444]'}`}>
@@ -477,7 +477,7 @@ export default function CampaignEditorPage() {
</div> </div>
{/* Preview Text */} {/* Preview Text */}
<div className="bg-[#111] border border-[#231d18] rounded-xl p-4"> <div className="bg-[#111] border border-[#FFFFFF] rounded-xl p-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="text-xs font-semibold text-white uppercase tracking-wider">Preview Text</h3> <h3 className="text-xs font-semibold text-white uppercase tracking-wider">Preview Text</h3>
<span className={`text-[10px] font-mono ${previewCharCount > 90 ? 'text-yellow-400' : 'text-[#444]'}`}> <span className={`text-[10px] font-mono ${previewCharCount > 90 ? 'text-yellow-400' : 'text-[#444]'}`}>
@@ -497,7 +497,7 @@ export default function CampaignEditorPage() {
</div> </div>
{/* Recipient Segment */} {/* Recipient Segment */}
<div className="bg-[#111] border border-[#231d18] rounded-xl p-4"> <div className="bg-[#111] border border-[#FFFFFF] rounded-xl p-4">
<h3 className="text-xs font-semibold text-white uppercase tracking-wider mb-3">Recipient Segment</h3> <h3 className="text-xs font-semibold text-white uppercase tracking-wider mb-3">Recipient Segment</h3>
<div className="space-y-2"> <div className="space-y-2">
{SEGMENTS.map((seg) => { {SEGMENTS.map((seg) => {
@@ -509,7 +509,7 @@ export default function CampaignEditorPage() {
onClick={() => setCampaign((p) => ({ ...p, segment: seg.id }))} onClick={() => setCampaign((p) => ({ ...p, segment: seg.id }))}
className={`w-full text-left px-3 py-2.5 rounded-lg border transition-all ${ className={`w-full text-left px-3 py-2.5 rounded-lg border transition-all ${
active active
? 'bg-white/5 border-white/20 text-white' :'bg-[#0a0a0a] border-[#231d18] text-[#888] hover:border-[#3a322b] hover:text-white' ? 'bg-white/5 border-white/20 text-white' :'bg-[#0a0a0a] border-[#FFFFFF] text-[#888] hover:border-[#DCE6F2] hover:text-white'
}`} }`}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -529,7 +529,7 @@ export default function CampaignEditorPage() {
})} })}
</div> </div>
{recipientCount > 0 && ( {recipientCount > 0 && (
<div className="mt-3 pt-3 border-t border-[#231d18] flex items-center gap-2"> <div className="mt-3 pt-3 border-t border-[#FFFFFF] flex items-center gap-2">
<svg className="w-3.5 h-3.5 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
</svg> </svg>
@@ -543,7 +543,7 @@ export default function CampaignEditorPage() {
)} )}
{/* ── Selected Articles ── */} {/* ── Selected Articles ── */}
<div className="bg-[#111] border border-[#231d18] rounded-xl p-4"> <div className="bg-[#111] border border-[#FFFFFF] rounded-xl p-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="text-xs font-semibold text-white uppercase tracking-wider">Selected Articles</h3> <h3 className="text-xs font-semibold text-white uppercase tracking-wider">Selected Articles</h3>
<span className="text-[10px] text-[#555]">{campaign.articles.length} article{campaign.articles.length !== 1 ? 's' : ''}</span> <span className="text-[10px] text-[#555]">{campaign.articles.length} article{campaign.articles.length !== 1 ? 's' : ''}</span>
@@ -559,7 +559,7 @@ export default function CampaignEditorPage() {
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
{campaign.articles.map((article, idx) => ( {campaign.articles.map((article, idx) => (
<div key={article.id} className="bg-[#0a0a0a] border border-[#231d18] rounded-lg p-3"> <div key={article.id} className="bg-[#0a0a0a] border border-[#FFFFFF] rounded-lg p-3">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
{/* Order controls */} {/* Order controls */}
<div className="flex flex-col gap-0.5 flex-shrink-0 mt-0.5"> <div className="flex flex-col gap-0.5 flex-shrink-0 mt-0.5">
@@ -618,16 +618,16 @@ export default function CampaignEditorPage() {
{/* ── Preview Panel ── */} {/* ── Preview Panel ── */}
{activePanel === 'preview' && ( {activePanel === 'preview' && (
<div className="bg-[#111] border border-[#231d18] rounded-xl overflow-hidden"> <div className="bg-[#111] border border-[#FFFFFF] rounded-xl overflow-hidden">
<div className="px-4 py-3 border-b border-[#231d18] flex items-center justify-between"> <div className="px-4 py-3 border-b border-[#FFFFFF] flex items-center justify-between">
<h3 className="text-xs font-semibold text-white uppercase tracking-wider">Email Preview</h3> <h3 className="text-xs font-semibold text-white uppercase tracking-wider">Email Preview</h3>
<span className="text-[10px] text-[#555]">Inbox view</span> <span className="text-[10px] text-[#555]">Inbox view</span>
</div> </div>
{/* Inbox row preview */} {/* Inbox row preview */}
<div className="px-4 py-3 border-b border-[#231d18] bg-[#1c1815]"> <div className="px-4 py-3 border-b border-[#FFFFFF] bg-[#F8FAFC]">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<div className="w-6 h-6 rounded-full bg-[#231d18] flex items-center justify-center flex-shrink-0"> <div className="w-6 h-6 rounded-full bg-[#FFFFFF] flex items-center justify-center flex-shrink-0">
<span className="text-[8px] font-bold text-[#666]">BB</span> <span className="text-[8px] font-bold text-[#666]">BB</span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -644,7 +644,7 @@ export default function CampaignEditorPage() {
{/* Email body preview */} {/* Email body preview */}
<div className="p-4 space-y-3 max-h-[400px] overflow-y-auto"> <div className="p-4 space-y-3 max-h-[400px] overflow-y-auto">
{/* Header */} {/* Header */}
<div className="bg-[#0a0a0a] rounded-lg p-3 text-center border border-[#231d18]"> <div className="bg-[#0a0a0a] rounded-lg p-3 text-center border border-[#FFFFFF]">
<p className="text-xs font-bold text-white tracking-widest uppercase">AV Beat</p> <p className="text-xs font-bold text-white tracking-widest uppercase">AV Beat</p>
{campaign.previewText && ( {campaign.previewText && (
<p className="text-[10px] text-[#555] mt-1">{campaign.previewText}</p> <p className="text-[10px] text-[#555] mt-1">{campaign.previewText}</p>
@@ -657,7 +657,7 @@ export default function CampaignEditorPage() {
) : ( ) : (
campaign.articles.map((article, idx) => ( campaign.articles.map((article, idx) => (
<div key={article.id} className={`rounded-lg border p-3 ${ <div key={article.id} className={`rounded-lg border p-3 ${
article.featured ? 'border-white/10 bg-white/3' : 'border-[#231d18] bg-[#0a0a0a]' article.featured ? 'border-white/10 bg-white/3' : 'border-[#FFFFFF] bg-[#0a0a0a]'
}`}> }`}>
{article.featured && ( {article.featured && (
<span className="text-[9px] font-semibold uppercase tracking-wider text-yellow-400 mb-1 block">Featured Story</span> <span className="text-[9px] font-semibold uppercase tracking-wider text-yellow-400 mb-1 block">Featured Story</span>
@@ -679,7 +679,7 @@ export default function CampaignEditorPage() {
)} )}
{/* Footer */} {/* Footer */}
<div className="text-center pt-2 border-t border-[#231d18]"> <div className="text-center pt-2 border-t border-[#FFFFFF]">
<p className="text-[9px] text-[#333]">AV Beat · Unsubscribe · View in browser</p> <p className="text-[9px] text-[#333]">AV Beat · Unsubscribe · View in browser</p>
</div> </div>
</div> </div>
@@ -687,7 +687,7 @@ export default function CampaignEditorPage() {
)} )}
{/* ── Publish Summary ── */} {/* ── Publish Summary ── */}
<div className="bg-[#111] border border-[#231d18] rounded-xl p-4"> <div className="bg-[#111] border border-[#FFFFFF] rounded-xl p-4">
<h3 className="text-xs font-semibold text-white uppercase tracking-wider mb-3">Campaign Summary</h3> <h3 className="text-xs font-semibold text-white uppercase tracking-wider mb-3">Campaign Summary</h3>
<div className="space-y-2"> <div className="space-y-2">
{[ {[
@@ -710,7 +710,7 @@ export default function CampaignEditorPage() {
<button <button
onClick={() => handleSave('draft')} onClick={() => handleSave('draft')}
disabled={saving} disabled={saving}
className="flex-1 py-2 text-xs font-medium bg-[#231d18] hover:bg-[#222] border border-[#3a322b] text-[#aaa] rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 text-xs font-medium bg-[#FFFFFF] hover:bg-[#222] border border-[#DCE6F2] text-[#aaa] rounded-lg transition-colors disabled:opacity-50"
> >
{saving ? 'Saving…' : 'Save Draft'} {saving ? 'Saving…' : 'Save Draft'}
</button> </button>

View File

@@ -356,7 +356,7 @@ export default function AdminNewsletterPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -373,7 +373,7 @@ export default function AdminNewsletterPage() {
)} )}
{/* Header */} {/* Header */}
<div className="bg-[#111] border-b border-[#3a322b] px-6 py-4"> <div className="bg-[#111] border-b border-[#DCE6F2] px-6 py-4">
<div className="max-w-6xl mx-auto flex items-center justify-between"> <div className="max-w-6xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-[#888] transition-colors"> <Link href="/admin" className="text-[#555] hover:text-[#888] transition-colors">
@@ -389,7 +389,7 @@ export default function AdminNewsletterPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link <Link
href="/admin/newsletter/templates" href="/admin/newsletter/templates"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors"> className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg> </svg>
@@ -397,7 +397,7 @@ export default function AdminNewsletterPage() {
</Link> </Link>
<Link <Link
href="/admin/newsletter/campaign-editor" href="/admin/newsletter/campaign-editor"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-white bg-[#F0A623] hover:bg-[#BA7517] rounded-lg transition-colors"> className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-white bg-[#1D4ED8] hover:bg-[#1E3A8A] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" /> <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
</svg> </svg>
@@ -405,18 +405,18 @@ export default function AdminNewsletterPage() {
</Link> </Link>
<Link <Link
href="/admin/newsletter/analytics" href="/admin/newsletter/analytics"
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors"> className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z" />
</svg> </svg>
Analytics Analytics
</Link> </Link>
<div className="w-px h-8 bg-[#3a322b]" /> <div className="w-px h-8 bg-[#DCE6F2]" />
<div className="text-right"> <div className="text-right">
<p className="text-white font-display font-bold text-xl">{activeCount}</p> <p className="text-white font-display font-bold text-xl">{activeCount}</p>
<p className="text-[#555] text-xs font-body">active subscribers</p> <p className="text-[#555] text-xs font-body">active subscribers</p>
</div> </div>
<div className="w-px h-8 bg-[#3a322b]" /> <div className="w-px h-8 bg-[#DCE6F2]" />
<div className="text-right"> <div className="text-right">
<p className="text-white font-display font-bold text-xl">{totalCount}</p> <p className="text-white font-display font-bold text-xl">{totalCount}</p>
<p className="text-[#555] text-xs font-body">total</p> <p className="text-[#555] text-xs font-body">total</p>
@@ -427,15 +427,15 @@ export default function AdminNewsletterPage() {
<div className="max-w-6xl mx-auto px-6 py-6"> <div className="max-w-6xl mx-auto px-6 py-6">
{/* Tabs */} {/* Tabs */}
<div className="flex gap-1 mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit"> <div className="flex gap-1 mb-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-1 w-fit">
<button <button
onClick={() => setActiveTab('subscribers')} onClick={() => setActiveTab('subscribers')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'subscribers' ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white'}`}> className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'subscribers' ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'}`}>
Subscribers Subscribers
</button> </button>
<button <button
onClick={() => setActiveTab('send-digest')} onClick={() => setActiveTab('send-digest')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'send-digest' ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white'}`}> className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors ${activeTab === 'send-digest' ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'}`}>
Send Digest Send Digest
</button> </button>
</div> </div>
@@ -452,12 +452,12 @@ export default function AdminNewsletterPage() {
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="search-input flex-1 py-2 px-3 text-sm" className="search-input flex-1 py-2 px-3 text-sm"
/> />
<div className="flex gap-1 bg-[#111] border border-[#3a322b] rounded-lg p-1"> <div className="flex gap-1 bg-[#111] border border-[#DCE6F2] rounded-lg p-1">
{(['all', 'active', 'unsubscribed'] as const).map((s) => ( {(['all', 'active', 'unsubscribed'] as const).map((s) => (
<button <button
key={s} key={s}
onClick={() => setStatusFilter(s)} onClick={() => setStatusFilter(s)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-colors ${statusFilter === s ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white'}`} className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold capitalize transition-colors ${statusFilter === s ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'}`}
> >
{s} {s}
</button> </button>
@@ -467,7 +467,7 @@ export default function AdminNewsletterPage() {
<button <button
onClick={handleExportCSV} onClick={handleExportCSV}
disabled={subscribers.length === 0} disabled={subscribers.length === 0}
className="flex items-center gap-1.5 px-3 py-2 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap" className="flex items-center gap-1.5 px-3 py-2 text-xs font-body font-semibold text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 015.25 21h13.5A2.25 2.25 0 0121 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" /> <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 015.25 21h13.5A2.25 2.25 0 0121 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
@@ -478,9 +478,9 @@ export default function AdminNewsletterPage() {
{/* Bulk Actions Bar */} {/* Bulk Actions Bar */}
{someSelected && ( {someSelected && (
<div className="flex items-center justify-between bg-[#231d18] border border-[#412402] rounded-lg px-4 py-3 mb-4"> <div className="flex items-center justify-between bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-sm font-body font-semibold text-[#F0A623]"> <span className="text-sm font-body font-semibold text-[#1D4ED8]">
{selectedIds.size} selected {selectedIds.size} selected
</span> </span>
<button <button
@@ -512,17 +512,17 @@ export default function AdminNewsletterPage() {
Delete Delete
</button> </button>
{bulkLoading && ( {bulkLoading && (
<div className="w-4 h-4 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
)} )}
</div> </div>
</div> </div>
)} )}
{/* Table */} {/* Table */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : subscribers.length === 0 ? ( ) : subscribers.length === 0 ? (
<div className="text-center py-16"> <div className="text-center py-16">
@@ -534,13 +534,13 @@ export default function AdminNewsletterPage() {
) : ( ) : (
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-[#3a322b]"> <tr className="border-b border-[#DCE6F2]">
<th className="px-4 py-3 w-10"> <th className="px-4 py-3 w-10">
<input <input
type="checkbox" type="checkbox"
checked={allSelected} checked={allSelected}
onChange={toggleSelectAll} onChange={toggleSelectAll}
className="w-4 h-4 rounded border-[#444] bg-[#231d18] accent-[#F0A623] cursor-pointer" className="w-4 h-4 rounded border-[#444] bg-[#FFFFFF] accent-[#1D4ED8] cursor-pointer"
/> />
</th> </th>
<th className="text-left px-4 py-3 text-xs font-body font-bold text-[#555] uppercase tracking-wider">Email</th> <th className="text-left px-4 py-3 text-xs font-body font-bold text-[#555] uppercase tracking-wider">Email</th>
@@ -554,21 +554,21 @@ export default function AdminNewsletterPage() {
{subscribers.map((sub) => ( {subscribers.map((sub) => (
<tr <tr
key={sub.id} key={sub.id}
className={`border-b border-[#231d18] transition-colors ${selectedIds.has(sub.id) ? 'bg-[#0d1a2d]' : 'hover:bg-[#1c1815]'}`} className={`border-b border-[#FFFFFF] transition-colors ${selectedIds.has(sub.id) ? 'bg-[#0d1a2d]' : 'hover:bg-[#F8FAFC]'}`}
> >
<td className="px-4 py-3 w-10"> <td className="px-4 py-3 w-10">
<input <input
type="checkbox" type="checkbox"
checked={selectedIds.has(sub.id)} checked={selectedIds.has(sub.id)}
onChange={() => toggleSelect(sub.id)} onChange={() => toggleSelect(sub.id)}
className="w-4 h-4 rounded border-[#444] bg-[#231d18] accent-[#F0A623] cursor-pointer" className="w-4 h-4 rounded border-[#444] bg-[#FFFFFF] accent-[#1D4ED8] cursor-pointer"
/> />
</td> </td>
<td className="px-4 py-3 text-sm font-body text-[#e0e0e0]">{sub.email}</td> <td className="px-4 py-3 text-sm font-body text-[#e0e0e0]">{sub.email}</td>
<td className="px-4 py-3 hidden md:table-cell"> <td className="px-4 py-3 hidden md:table-cell">
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{sub.topics?.slice(0, 3).map((t) => ( {sub.topics?.slice(0, 3).map((t) => (
<span key={t} className="text-xs bg-[#231d18] text-[#F0A623] px-2 py-0.5 rounded-full font-body">{t}</span> <span key={t} className="text-xs bg-[#FFFFFF] text-[#1D4ED8] px-2 py-0.5 rounded-full font-body">{t}</span>
))} ))}
{sub.topics?.length > 3 && ( {sub.topics?.length > 3 && (
<span className="text-xs text-[#555] font-body">+{sub.topics.length - 3}</span> <span className="text-xs text-[#555] font-body">+{sub.topics.length - 3}</span>
@@ -576,7 +576,7 @@ export default function AdminNewsletterPage() {
</div> </div>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className={`text-xs font-body font-bold px-2 py-1 rounded-full ${sub.status === 'active' ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#231d18]'}`}> <span className={`text-xs font-body font-bold px-2 py-1 rounded-full ${sub.status === 'active' ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#FFFFFF]'}`}>
{sub.status} {sub.status}
</span> </span>
</td> </td>
@@ -616,21 +616,21 @@ export default function AdminNewsletterPage() {
{activeTab === 'send-digest' && ( {activeTab === 'send-digest' && (
<div className="max-w-2xl"> <div className="max-w-2xl">
{/* SMTP warning if not configured */} {/* SMTP warning if not configured */}
<div className="bg-[#231d18] border border-[#412402] rounded-lg p-4 mb-6 flex gap-3"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-4 mb-6 flex gap-3">
<svg className="w-5 h-5 text-[#F0A623] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-[#1D4ED8] flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
<div> <div>
<p className="text-[#e0e0e0] text-sm font-body font-semibold">SMTP Required</p> <p className="text-[#e0e0e0] text-sm font-body font-semibold">SMTP Required</p>
<p className="text-[#777] text-xs font-body mt-0.5"> <p className="text-[#777] text-xs font-body mt-0.5">
Set <code className="text-[#F0A623]">SMTP_HOST</code>, <code className="text-[#F0A623]">SMTP_USER</code>, <code className="text-[#F0A623]">SMTP_PASS</code>, and <code className="text-[#F0A623]">SMTP_FROM_EMAIL</code> in your environment variables to enable sending. Set <code className="text-[#1D4ED8]">SMTP_HOST</code>, <code className="text-[#1D4ED8]">SMTP_USER</code>, <code className="text-[#1D4ED8]">SMTP_PASS</code>, and <code className="text-[#1D4ED8]">SMTP_FROM_EMAIL</code> in your environment variables to enable sending.
</p> </p>
</div> </div>
</div> </div>
{/* Applied template badge */} {/* Applied template badge */}
{appliedTemplate && ( {appliedTemplate && (
<div className="flex items-center justify-between bg-[#231d18] border border-[#412402] rounded-lg px-4 py-3 mb-4"> <div className="flex items-center justify-between bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg px-4 py-3 mb-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5" style={{ backgroundColor: appliedTemplate.accent_color + '20' }}> <div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5" style={{ backgroundColor: appliedTemplate.accent_color + '20' }}>
<span style={{ color: appliedTemplate.accent_color }}> <span style={{ color: appliedTemplate.accent_color }}>
@@ -662,7 +662,7 @@ export default function AdminNewsletterPage() {
<p className="text-xs font-body text-[#555]">Fill in the fields below or start from a template</p> <p className="text-xs font-body text-[#555]">Fill in the fields below or start from a template</p>
<button <button
onClick={() => { fetchTemplates(); setShowTemplateDrawer(true); }} onClick={() => { fetchTemplates(); setShowTemplateDrawer(true); }}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#F0A623] border border-[#412402] rounded-lg hover:bg-[#231d18] transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-semibold text-[#1D4ED8] border border-[#DCE6F2] rounded-lg hover:bg-[#FFFFFF] transition-colors"
> >
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
@@ -725,14 +725,14 @@ export default function AdminNewsletterPage() {
<button <button
type="button" type="button"
onClick={addArticle} onClick={addArticle}
className="text-xs text-[#F0A623] hover:text-blue-400 font-body font-semibold transition-colors" className="text-xs text-[#1D4ED8] hover:text-blue-400 font-body font-semibold transition-colors"
> >
+ Add Article + Add Article
</button> </button>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
{digestArticles.map((article, index) => ( {digestArticles.map((article, index) => (
<div key={index} className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div key={index} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<span className="text-xs font-body text-[#555]">Article {index + 1}</span> <span className="text-xs font-body text-[#555]">Article {index + 1}</span>
{digestArticles.length > 1 && ( {digestArticles.length > 1 && (
@@ -803,11 +803,11 @@ export default function AdminNewsletterPage() {
{/* Template Picker Drawer */} {/* Template Picker Drawer */}
{showTemplateDrawer && ( {showTemplateDrawer && (
<div className="fixed inset-0 z-50 bg-black/70 flex items-end sm:items-center justify-center p-0 sm:p-4" onClick={() => setShowTemplateDrawer(false)}> <div className="fixed inset-0 z-50 bg-black/70 flex items-end sm:items-center justify-center p-0 sm:p-4" onClick={() => setShowTemplateDrawer(false)}>
<div className="bg-[#111] border border-[#3a322b] rounded-t-2xl sm:rounded-xl w-full sm:max-w-2xl max-h-[80vh] overflow-hidden flex flex-col" onClick={(e) => e.stopPropagation()}> <div className="bg-[#111] border border-[#DCE6F2] rounded-t-2xl sm:rounded-xl w-full sm:max-w-2xl max-h-[80vh] overflow-hidden flex flex-col" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between px-6 py-4 border-b border-[#3a322b] flex-shrink-0"> <div className="flex items-center justify-between px-6 py-4 border-b border-[#DCE6F2] flex-shrink-0">
<h2 className="font-display font-bold text-white text-base">Choose a Template</h2> <h2 className="font-display font-bold text-white text-base">Choose a Template</h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin/newsletter/templates" className="text-xs text-[#F0A623] font-body hover:text-blue-400 transition-colors"> <Link href="/admin/newsletter/templates" className="text-xs text-[#1D4ED8] font-body hover:text-blue-400 transition-colors">
Manage templates Manage templates
</Link> </Link>
<button onClick={() => setShowTemplateDrawer(false)} className="text-[#555] hover:text-white transition-colors"> <button onClick={() => setShowTemplateDrawer(false)} className="text-[#555] hover:text-white transition-colors">
@@ -820,12 +820,12 @@ export default function AdminNewsletterPage() {
<div className="overflow-y-auto flex-1 p-4"> <div className="overflow-y-auto flex-1 p-4">
{loadingTemplates ? ( {loadingTemplates ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : templates.length === 0 ? ( ) : templates.length === 0 ? (
<div className="text-center py-12"> <div className="text-center py-12">
<p className="text-[#555] font-body text-sm">No templates found.</p> <p className="text-[#555] font-body text-sm">No templates found.</p>
<Link href="/admin/newsletter/templates" className="text-[#F0A623] text-sm font-body hover:text-blue-400 mt-2 inline-block"> <Link href="/admin/newsletter/templates" className="text-[#1D4ED8] text-sm font-body hover:text-blue-400 mt-2 inline-block">
Create your first template Create your first template
</Link> </Link>
</div> </div>
@@ -835,7 +835,7 @@ export default function AdminNewsletterPage() {
<button <button
key={tpl.id} key={tpl.id}
onClick={() => applyTemplate(tpl)} onClick={() => applyTemplate(tpl)}
className="flex items-start gap-3 p-4 bg-[#1c1815] border border-[#3a322b] rounded-xl text-left hover:border-[#F0A623] hover:bg-[#231d18] transition-colors group" className="flex items-start gap-3 p-4 bg-[#F8FAFC] border border-[#DCE6F2] rounded-xl text-left hover:border-[#1D4ED8] hover:bg-[#FFFFFF] transition-colors group"
> >
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5" style={{ backgroundColor: tpl.accent_color + '20' }}> <div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5" style={{ backgroundColor: tpl.accent_color + '20' }}>
<span style={{ color: tpl.accent_color }}> <span style={{ color: tpl.accent_color }}>
@@ -851,8 +851,8 @@ export default function AdminNewsletterPage() {
</div> </div>
{tpl.description && <p className="text-xs text-[#777] font-body line-clamp-1">{tpl.description}</p>} {tpl.description && <p className="text-xs text-[#777] font-body line-clamp-1">{tpl.description}</p>}
<div className="flex gap-1.5 mt-1.5"> <div className="flex gap-1.5 mt-1.5">
<span className="text-xs bg-[#231d18] text-[#888] px-1.5 py-0.5 rounded font-body capitalize">{tpl.layout}</span> <span className="text-xs bg-[#FFFFFF] text-[#888] px-1.5 py-0.5 rounded font-body capitalize">{tpl.layout}</span>
<span className="text-xs bg-[#231d18] text-[#888] px-1.5 py-0.5 rounded font-body">{tpl.article_blocks?.length || 0} slots</span> <span className="text-xs bg-[#FFFFFF] text-[#888] px-1.5 py-0.5 rounded font-body">{tpl.article_blocks?.length || 0} slots</span>
</div> </div>
</div> </div>
</button> </button>

View File

@@ -42,7 +42,7 @@ const STYLE_OPTIONS = [
]; ];
const ACCENT_COLORS = [ const ACCENT_COLORS = [
'#F0A623', '#ef4444', '#10b981', '#8b5cf6', '#f59e0b', '#ec4899', '#06b6d4', '#f97316', '#1D4ED8', '#ef4444', '#10b981', '#8b5cf6', '#f59e0b', '#ec4899', '#06b6d4', '#f97316',
]; ];
const LAYOUT_ICONS: Record<string, React.ReactNode> = { const LAYOUT_ICONS: Record<string, React.ReactNode> = {
@@ -83,7 +83,7 @@ const emptyForm = (): Omit<NewsletterTemplate, 'id' | 'is_preset' | 'created_at'
subject_prefix: '', subject_prefix: '',
header_text: '', header_text: '',
footer_text: '', footer_text: '',
accent_color: '#F0A623', accent_color: '#1D4ED8',
article_blocks: [{ title: '', excerpt: '', url: '', category: '' }], article_blocks: [{ title: '', excerpt: '', url: '', category: '' }],
}); });
@@ -226,7 +226,7 @@ export default function NewsletterTemplatesPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -242,7 +242,7 @@ export default function NewsletterTemplatesPage() {
)} )}
{/* Header */} {/* Header */}
<div className="bg-[#111] border-b border-[#3a322b] px-6 py-4"> <div className="bg-[#111] border-b border-[#DCE6F2] px-6 py-4">
<div className="max-w-6xl mx-auto flex items-center justify-between"> <div className="max-w-6xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin/newsletter" className="text-[#555] hover:text-[#888] transition-colors"> <Link href="/admin/newsletter" className="text-[#555] hover:text-[#888] transition-colors">
@@ -272,11 +272,11 @@ export default function NewsletterTemplatesPage() {
<div className="mb-8"> <div className="mb-8">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<h2 className="font-display font-bold text-white text-base">Preset Templates</h2> <h2 className="font-display font-bold text-white text-base">Preset Templates</h2>
<span className="text-xs bg-[#231d18] text-[#F0A623] px-2 py-0.5 rounded-full font-body">{presets.length}</span> <span className="text-xs bg-[#FFFFFF] text-[#1D4ED8] px-2 py-0.5 rounded-full font-body">{presets.length}</span>
</div> </div>
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@@ -298,15 +298,15 @@ export default function NewsletterTemplatesPage() {
<div> <div>
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<h2 className="font-display font-bold text-white text-base">Custom Templates</h2> <h2 className="font-display font-bold text-white text-base">Custom Templates</h2>
<span className="text-xs bg-[#231d18] text-[#F0A623] px-2 py-0.5 rounded-full font-body">{custom.length}</span> <span className="text-xs bg-[#FFFFFF] text-[#1D4ED8] px-2 py-0.5 rounded-full font-body">{custom.length}</span>
</div> </div>
{!loadingData && custom.length === 0 ? ( {!loadingData && custom.length === 0 ? (
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-10 text-center"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-10 text-center">
<svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24"> <svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg> </svg>
<p className="text-[#555] font-body text-sm mb-3">No custom templates yet</p> <p className="text-[#555] font-body text-sm mb-3">No custom templates yet</p>
<button onClick={openNew} className="text-[#F0A623] text-sm font-body font-semibold hover:text-blue-400 transition-colors"> <button onClick={openNew} className="text-[#1D4ED8] text-sm font-body font-semibold hover:text-blue-400 transition-colors">
Create your first template Create your first template
</button> </button>
</div> </div>
@@ -330,8 +330,8 @@ export default function NewsletterTemplatesPage() {
{/* Template Form Modal */} {/* Template Form Modal */}
{showForm && ( {showForm && (
<div className="fixed inset-0 z-50 bg-black/70 flex items-start justify-center overflow-y-auto py-8 px-4"> <div className="fixed inset-0 z-50 bg-black/70 flex items-start justify-center overflow-y-auto py-8 px-4">
<div className="bg-[#111] border border-[#3a322b] rounded-xl w-full max-w-2xl"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl w-full max-w-2xl">
<div className="flex items-center justify-between px-6 py-4 border-b border-[#3a322b]"> <div className="flex items-center justify-between px-6 py-4 border-b border-[#DCE6F2]">
<h2 className="font-display font-bold text-white text-base"> <h2 className="font-display font-bold text-white text-base">
{editingId ? 'Edit Template' : 'New Template'} {editingId ? 'Edit Template' : 'New Template'}
</h2> </h2>
@@ -376,8 +376,8 @@ export default function NewsletterTemplatesPage() {
key={opt.value} key={opt.value}
type="button" type="button"
onClick={() => setForm((f) => ({ ...f, layout: opt.value as NewsletterTemplate['layout'] }))} onClick={() => setForm((f) => ({ ...f, layout: opt.value as NewsletterTemplate['layout'] }))}
className={`flex items-start gap-3 p-3 rounded-lg border text-left transition-colors ${form.layout === opt.value ? 'border-[#F0A623] bg-[#231d18]' : 'border-[#3a322b] bg-[#1c1815] hover:border-[#333]'}`}> className={`flex items-start gap-3 p-3 rounded-lg border text-left transition-colors ${form.layout === opt.value ? 'border-[#1D4ED8] bg-[#FFFFFF]' : 'border-[#DCE6F2] bg-[#F8FAFC] hover:border-[#333]'}`}>
<span className={`mt-0.5 ${form.layout === opt.value ? 'text-[#F0A623]' : 'text-[#555]'}`}> <span className={`mt-0.5 ${form.layout === opt.value ? 'text-[#1D4ED8]' : 'text-[#555]'}`}>
{LAYOUT_ICONS[opt.value]} {LAYOUT_ICONS[opt.value]}
</span> </span>
<div> <div>
@@ -398,7 +398,7 @@ export default function NewsletterTemplatesPage() {
key={opt.value} key={opt.value}
type="button" type="button"
onClick={() => setForm((f) => ({ ...f, style: opt.value as NewsletterTemplate['style'] }))} onClick={() => setForm((f) => ({ ...f, style: opt.value as NewsletterTemplate['style'] }))}
className={`flex-1 py-2.5 px-3 rounded-lg border text-sm font-body font-semibold transition-colors ${form.style === opt.value ? 'border-[#F0A623] bg-[#231d18] text-white' : 'border-[#3a322b] bg-[#1c1815] text-[#888] hover:border-[#333]'}`}> className={`flex-1 py-2.5 px-3 rounded-lg border text-sm font-body font-semibold transition-colors ${form.style === opt.value ? 'border-[#1D4ED8] bg-[#FFFFFF] text-white' : 'border-[#DCE6F2] bg-[#F8FAFC] text-[#888] hover:border-[#333]'}`}>
{opt.label} {opt.label}
</button> </button>
))} ))}
@@ -469,14 +469,14 @@ export default function NewsletterTemplatesPage() {
<div> <div>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<label className="text-xs font-body font-bold text-[#888] uppercase tracking-wider">Article Block Slots</label> <label className="text-xs font-body font-bold text-[#888] uppercase tracking-wider">Article Block Slots</label>
<button type="button" onClick={addBlock} className="text-xs text-[#F0A623] hover:text-blue-400 font-body font-semibold transition-colors"> <button type="button" onClick={addBlock} className="text-xs text-[#1D4ED8] hover:text-blue-400 font-body font-semibold transition-colors">
+ Add Slot + Add Slot
</button> </button>
</div> </div>
<p className="text-xs text-[#555] font-body mb-3">Define placeholder slots for articles. These will be pre-filled when using the template in Send Digest.</p> <p className="text-xs text-[#555] font-body mb-3">Define placeholder slots for articles. These will be pre-filled when using the template in Send Digest.</p>
<div className="space-y-2"> <div className="space-y-2">
{form.article_blocks.map((block, i) => ( {form.article_blocks.map((block, i) => (
<div key={i} className="bg-[#1c1815] border border-[#3a322b] rounded-lg p-3"> <div key={i} className="bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg p-3">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs font-body text-[#555]">Slot {i + 1}</span> <span className="text-xs font-body text-[#555]">Slot {i + 1}</span>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -486,7 +486,7 @@ export default function NewsletterTemplatesPage() {
type="checkbox" type="checkbox"
checked={!!block.featured} checked={!!block.featured}
onChange={(e) => updateBlock(i, 'featured', e.target.checked)} onChange={(e) => updateBlock(i, 'featured', e.target.checked)}
className="w-3 h-3 accent-[#F0A623]" className="w-3 h-3 accent-[#1D4ED8]"
/> />
<span className="text-xs font-body text-[#888]">Featured</span> <span className="text-xs font-body text-[#888]">Featured</span>
</label> </label>
@@ -527,7 +527,7 @@ export default function NewsletterTemplatesPage() {
</div> </div>
</div> </div>
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[#3a322b]"> <div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[#DCE6F2]">
<button onClick={() => setShowForm(false)} className="px-4 py-2 text-sm font-body text-[#888] hover:text-white transition-colors"> <button onClick={() => setShowForm(false)} className="px-4 py-2 text-sm font-body text-[#888] hover:text-white transition-colors">
Cancel Cancel
</button> </button>
@@ -545,8 +545,8 @@ export default function NewsletterTemplatesPage() {
{/* Preview Modal */} {/* Preview Modal */}
{previewTemplate && ( {previewTemplate && (
<div className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-4" onClick={() => setPreviewTemplate(null)}> <div className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-4" onClick={() => setPreviewTemplate(null)}>
<div className="bg-[#111] border border-[#3a322b] rounded-xl w-full max-w-lg" onClick={(e) => e.stopPropagation()}> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl w-full max-w-lg" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between px-6 py-4 border-b border-[#3a322b]"> <div className="flex items-center justify-between px-6 py-4 border-b border-[#DCE6F2]">
<h2 className="font-display font-bold text-white text-base">Template Preview</h2> <h2 className="font-display font-bold text-white text-base">Template Preview</h2>
<button onClick={() => setPreviewTemplate(null)} className="text-[#555] hover:text-white transition-colors"> <button onClick={() => setPreviewTemplate(null)} className="text-[#555] hover:text-white transition-colors">
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -565,27 +565,27 @@ export default function NewsletterTemplatesPage() {
</div> </div>
</div> </div>
<div className="grid grid-cols-3 gap-3 text-center"> <div className="grid grid-cols-3 gap-3 text-center">
<div className="bg-[#1c1815] rounded-lg p-3"> <div className="bg-[#F8FAFC] rounded-lg p-3">
<p className="text-xs text-[#555] font-body mb-1">Layout</p> <p className="text-xs text-[#555] font-body mb-1">Layout</p>
<p className="text-sm font-body font-semibold text-white capitalize">{previewTemplate.layout}</p> <p className="text-sm font-body font-semibold text-white capitalize">{previewTemplate.layout}</p>
</div> </div>
<div className="bg-[#1c1815] rounded-lg p-3"> <div className="bg-[#F8FAFC] rounded-lg p-3">
<p className="text-xs text-[#555] font-body mb-1">Style</p> <p className="text-xs text-[#555] font-body mb-1">Style</p>
<p className="text-sm font-body font-semibold text-white capitalize">{previewTemplate.style}</p> <p className="text-sm font-body font-semibold text-white capitalize">{previewTemplate.style}</p>
</div> </div>
<div className="bg-[#1c1815] rounded-lg p-3"> <div className="bg-[#F8FAFC] rounded-lg p-3">
<p className="text-xs text-[#555] font-body mb-1">Slots</p> <p className="text-xs text-[#555] font-body mb-1">Slots</p>
<p className="text-sm font-body font-semibold text-white">{previewTemplate.article_blocks?.length || 0}</p> <p className="text-sm font-body font-semibold text-white">{previewTemplate.article_blocks?.length || 0}</p>
</div> </div>
</div> </div>
{previewTemplate.subject_prefix && ( {previewTemplate.subject_prefix && (
<div className="bg-[#1c1815] rounded-lg p-3"> <div className="bg-[#F8FAFC] rounded-lg p-3">
<p className="text-xs text-[#555] font-body mb-1">Subject Prefix</p> <p className="text-xs text-[#555] font-body mb-1">Subject Prefix</p>
<p className="text-sm font-body text-[#e0e0e0]">{previewTemplate.subject_prefix}</p> <p className="text-sm font-body text-[#e0e0e0]">{previewTemplate.subject_prefix}</p>
</div> </div>
)} )}
{previewTemplate.header_text && ( {previewTemplate.header_text && (
<div className="bg-[#1c1815] rounded-lg p-3"> <div className="bg-[#F8FAFC] rounded-lg p-3">
<p className="text-xs text-[#555] font-body mb-1">Header Text</p> <p className="text-xs text-[#555] font-body mb-1">Header Text</p>
<p className="text-sm font-body text-[#e0e0e0]">{previewTemplate.header_text}</p> <p className="text-sm font-body text-[#e0e0e0]">{previewTemplate.header_text}</p>
</div> </div>
@@ -595,9 +595,9 @@ export default function NewsletterTemplatesPage() {
<p className="text-xs text-[#555] font-body mb-2">Article Slots ({previewTemplate.article_blocks.length})</p> <p className="text-xs text-[#555] font-body mb-2">Article Slots ({previewTemplate.article_blocks.length})</p>
<div className="space-y-1.5"> <div className="space-y-1.5">
{previewTemplate.article_blocks.map((b, i) => ( {previewTemplate.article_blocks.map((b, i) => (
<div key={i} className="flex items-center gap-2 bg-[#1c1815] rounded px-3 py-2"> <div key={i} className="flex items-center gap-2 bg-[#F8FAFC] rounded px-3 py-2">
<span className="text-xs text-[#555] font-body w-12 flex-shrink-0">Slot {i + 1}</span> <span className="text-xs text-[#555] font-body w-12 flex-shrink-0">Slot {i + 1}</span>
{b.featured && <span className="text-xs text-[#F0A623] font-body bg-[#231d18] px-1.5 py-0.5 rounded">Featured</span>} {b.featured && <span className="text-xs text-[#1D4ED8] font-body bg-[#FFFFFF] px-1.5 py-0.5 rounded">Featured</span>}
{b.category && <span className="text-xs text-[#888] font-body">{b.category}</span>} {b.category && <span className="text-xs text-[#888] font-body">{b.category}</span>}
{b.title && <span className="text-xs text-[#e0e0e0] font-body truncate">{b.title}</span>} {b.title && <span className="text-xs text-[#e0e0e0] font-body truncate">{b.title}</span>}
</div> </div>
@@ -614,7 +614,7 @@ export default function NewsletterTemplatesPage() {
{!previewTemplate.is_preset && ( {!previewTemplate.is_preset && (
<button <button
onClick={() => { setPreviewTemplate(null); openEdit(previewTemplate); }} onClick={() => { setPreviewTemplate(null); openEdit(previewTemplate); }}
className="px-4 py-2 text-sm font-body text-[#888] hover:text-white border border-[#3a322b] rounded-lg transition-colors"> className="px-4 py-2 text-sm font-body text-[#888] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors">
Edit Edit
</button> </button>
)} )}
@@ -637,7 +637,7 @@ interface TemplateCardProps {
function TemplateCard({ template, onEdit, onDelete, onPreview, deletingId }: TemplateCardProps) { function TemplateCard({ template, onEdit, onDelete, onPreview, deletingId }: TemplateCardProps) {
return ( return (
<div className="bg-[#111] border border-[#3a322b] rounded-xl overflow-hidden hover:border-[#333] transition-colors group"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl overflow-hidden hover:border-[#333] transition-colors group">
{/* Color bar */} {/* Color bar */}
<div className="h-1" style={{ backgroundColor: template.accent_color }} /> <div className="h-1" style={{ backgroundColor: template.accent_color }} />
<div className="p-4"> <div className="p-4">
@@ -660,27 +660,27 @@ function TemplateCard({ template, onEdit, onDelete, onPreview, deletingId }: Tem
)} )}
<div className="flex flex-wrap gap-1.5 mb-4"> <div className="flex flex-wrap gap-1.5 mb-4">
<span className="text-xs bg-[#231d18] text-[#888] px-2 py-0.5 rounded-full font-body capitalize">{template.layout}</span> <span className="text-xs bg-[#FFFFFF] text-[#888] px-2 py-0.5 rounded-full font-body capitalize">{template.layout}</span>
<span className="text-xs bg-[#231d18] text-[#888] px-2 py-0.5 rounded-full font-body capitalize">{template.style}</span> <span className="text-xs bg-[#FFFFFF] text-[#888] px-2 py-0.5 rounded-full font-body capitalize">{template.style}</span>
<span className="text-xs bg-[#231d18] text-[#888] px-2 py-0.5 rounded-full font-body">{template.article_blocks?.length || 0} slots</span> <span className="text-xs bg-[#FFFFFF] text-[#888] px-2 py-0.5 rounded-full font-body">{template.article_blocks?.length || 0} slots</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => onPreview(template)} onClick={() => onPreview(template)}
className="flex-1 py-1.5 text-xs font-body font-semibold text-[#F0A623] border border-[#412402] rounded-lg hover:bg-[#231d18] transition-colors"> className="flex-1 py-1.5 text-xs font-body font-semibold text-[#1D4ED8] border border-[#DCE6F2] rounded-lg hover:bg-[#FFFFFF] transition-colors">
Preview Preview
</button> </button>
<Link <Link
href={`/admin/newsletter?template=${template.id}`} href={`/admin/newsletter?template=${template.id}`}
className="flex-1 py-1.5 text-xs font-body font-semibold text-center text-white bg-[#231d18] border border-[#412402] rounded-lg hover:bg-[#412402] transition-colors"> className="flex-1 py-1.5 text-xs font-body font-semibold text-center text-white bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg hover:bg-[#DCE6F2] transition-colors">
Use Use
</Link> </Link>
{!template.is_preset && ( {!template.is_preset && (
<> <>
<button <button
onClick={() => onEdit(template)} onClick={() => onEdit(template)}
className="p-1.5 text-[#555] hover:text-white border border-[#3a322b] rounded-lg transition-colors"> className="p-1.5 text-[#555] hover:text-white border border-[#DCE6F2] rounded-lg transition-colors">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125" /> <path strokeLinecap="round" strokeLinejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125" />
</svg> </svg>
@@ -688,7 +688,7 @@ function TemplateCard({ template, onEdit, onDelete, onPreview, deletingId }: Tem
<button <button
onClick={() => onDelete(template.id)} onClick={() => onDelete(template.id)}
disabled={deletingId === template.id} disabled={deletingId === template.id}
className="p-1.5 text-[#555] hover:text-red-400 border border-[#3a322b] rounded-lg transition-colors disabled:opacity-50"> className="p-1.5 text-[#555] hover:text-red-400 border border-[#DCE6F2] rounded-lg transition-colors disabled:opacity-50">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /> <path strokeLinecap="round" strokeLinejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg> </svg>

View File

@@ -166,7 +166,7 @@ function StatCard({ label, value, icon, color, active, onClick }: StatCardProps)
className={`w-full text-left p-4 rounded-lg border transition-all ${ className={`w-full text-left p-4 rounded-lg border transition-all ${
active active
? `${color} border-current/30` ? `${color} border-current/30`
: 'bg-[#111] border-[#3a322b] hover:border-[#333]' : 'bg-[#111] border-[#DCE6F2] hover:border-[#333]'
}`} }`}
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
@@ -191,7 +191,7 @@ function NotificationRow({ notification: n, onMarkRead, onDismiss }: Notificatio
return ( return (
<div <div
className={`relative p-4 rounded-lg border transition-all ${ className={`relative p-4 rounded-lg border transition-all ${
n.read ? 'bg-[#1c1815] border-[#1e1e1e]' : `${cfg.bg} ${cfg.border}` n.read ? 'bg-[#F8FAFC] border-[#1e1e1e]' : `${cfg.bg} ${cfg.border}`
}`} }`}
> >
{/* Unread dot */} {/* Unread dot */}
@@ -202,7 +202,7 @@ function NotificationRow({ notification: n, onMarkRead, onDismiss }: Notificatio
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
{/* Icon */} {/* Icon */}
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${ <div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
n.read ? 'bg-[#231d18]' : cfg.badge n.read ? 'bg-[#FFFFFF]' : cfg.badge
}`}> }`}>
<TypeIcon type={n.type} /> <TypeIcon type={n.type} />
</div> </div>
@@ -244,7 +244,7 @@ function NotificationRow({ notification: n, onMarkRead, onDismiss }: Notificatio
{n.actionUrl && ( {n.actionUrl && (
<Link <Link
href={n.actionUrl} href={n.actionUrl}
className="text-xs font-body font-bold text-[#F0A623] hover:text-blue-300 transition-colors" className="text-xs font-body font-bold text-[#1D4ED8] hover:text-blue-300 transition-colors"
> >
{n.actionLabel || 'View →'} {n.actionLabel || 'View →'}
</Link> </Link>
@@ -474,7 +474,7 @@ export default function NotificationsPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin mx-auto mb-3" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin mx-auto mb-3" />
<p className="text-[#555] text-sm font-body">Loading notifications</p> <p className="text-[#555] text-sm font-body">Loading notifications</p>
</div> </div>
</div> </div>
@@ -512,7 +512,7 @@ export default function NotificationsPage() {
{stats.unread > 0 && ( {stats.unread > 0 && (
<button <button
onClick={handleMarkAllRead} onClick={handleMarkAllRead}
className="px-3 py-1.5 text-xs font-body font-bold text-[#F0A623] border border-[#F0A623]/30 rounded hover:bg-[#F0A623]/10 transition-colors" className="px-3 py-1.5 text-xs font-body font-bold text-[#1D4ED8] border border-[#1D4ED8]/30 rounded hover:bg-[#1D4ED8]/10 transition-colors"
> >
Mark all read Mark all read
</button> </button>
@@ -520,7 +520,7 @@ export default function NotificationsPage() {
<button <button
onClick={handleRefresh} onClick={handleRefresh}
disabled={refreshing} disabled={refreshing}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body text-[#888] border border-[#3a322b] rounded hover:border-[#333] hover:text-white transition-colors disabled:opacity-50" className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body text-[#888] border border-[#DCE6F2] rounded hover:border-[#333] hover:text-white transition-colors disabled:opacity-50"
> >
<RefreshIcon className={`w-3.5 h-3.5 ${refreshing ? 'animate-spin' : ''}`} /> <RefreshIcon className={`w-3.5 h-3.5 ${refreshing ? 'animate-spin' : ''}`} />
Refresh Refresh
@@ -558,7 +558,7 @@ export default function NotificationsPage() {
label="Unread" label="Unread"
value={stats.unread} value={stats.unread}
icon={<BellIcon className="w-4 h-4" />} icon={<BellIcon className="w-4 h-4" />}
color="text-[#F0A623] bg-[#F0A623]/5 border-[#F0A623]/30" color="text-[#1D4ED8] bg-[#1D4ED8]/5 border-[#1D4ED8]/30"
active={filter === 'unread'} active={filter === 'unread'}
onClick={() => setFilter(filter === 'unread' ? 'all' : 'unread')} onClick={() => setFilter(filter === 'unread' ? 'all' : 'unread')}
/> />
@@ -580,7 +580,7 @@ export default function NotificationsPage() {
onClick={() => setFilter(f)} onClick={() => setFilter(f)}
className={`px-3 py-1.5 text-xs font-body font-bold rounded transition-colors ${ className={`px-3 py-1.5 text-xs font-body font-bold rounded transition-colors ${
filter === f filter === f
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#555] hover:text-[#888] hover:bg-[#111]' : 'text-[#555] hover:text-[#888] hover:bg-[#111]'
}`} }`}
> >

View File

@@ -44,8 +44,8 @@ function timeAgo(dateStr: string): string {
const STATUS_COLORS: Record<string, string> = { const STATUS_COLORS: Record<string, string> = {
published: 'text-green-400 bg-green-400/10', published: 'text-green-400 bg-green-400/10',
pending: 'text-yellow-400 bg-yellow-400/10', pending: 'text-yellow-400 bg-yellow-400/10',
draft: 'text-[#888] bg-[#231d18]', draft: 'text-[#888] bg-[#FFFFFF]',
archived: 'text-[#555] bg-[#231d18]', archived: 'text-[#555] bg-[#FFFFFF]',
imported: 'text-blue-400 bg-blue-400/10', imported: 'text-blue-400 bg-blue-400/10',
}; };
@@ -91,7 +91,7 @@ interface MetricCardProps {
function MetricCard({ label, value, sub, accent, icon, href, badge }: MetricCardProps) { function MetricCard({ label, value, sub, accent, icon, href, badge }: MetricCardProps) {
const content = ( const content = (
<div className={`bg-[#111] border border-[#3a322b] rounded-lg p-5 flex flex-col gap-3 hover:border-[#333] transition-colors ${href ? 'cursor-pointer' : ''}`}> <div className={`bg-[#111] border border-[#DCE6F2] rounded-lg p-5 flex flex-col gap-3 hover:border-[#333] transition-colors ${href ? 'cursor-pointer' : ''}`}>
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${accent}`}> <div className={`w-10 h-10 rounded-lg flex items-center justify-center ${accent}`}>
{icon} {icon}
@@ -125,15 +125,15 @@ interface QuickActionProps {
function QuickAction({ label, description, href, icon, accent }: QuickActionProps) { function QuickAction({ label, description, href, icon, accent }: QuickActionProps) {
return ( return (
<Link href={href} className="flex items-center gap-3 p-3 bg-[#111] border border-[#3a322b] rounded-lg hover:border-[#F0A623] hover:bg-[#0d1a2d] transition-all group"> <Link href={href} className="flex items-center gap-3 p-3 bg-[#111] border border-[#DCE6F2] rounded-lg hover:border-[#1D4ED8] hover:bg-[#0d1a2d] transition-all group">
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${accent} group-hover:scale-105 transition-transform`}> <div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${accent} group-hover:scale-105 transition-transform`}>
{icon} {icon}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<p className="text-sm font-body font-semibold text-white group-hover:text-[#F0A623] transition-colors">{label}</p> <p className="text-sm font-body font-semibold text-white group-hover:text-[#1D4ED8] transition-colors">{label}</p>
<p className="text-xs text-[#555] font-body truncate">{description}</p> <p className="text-xs text-[#555] font-body truncate">{description}</p>
</div> </div>
<svg className="w-4 h-4 text-[#444] group-hover:text-[#F0A623] ml-auto flex-shrink-0 transition-colors" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#444] group-hover:text-[#1D4ED8] ml-auto flex-shrink-0 transition-colors" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg> </svg>
</Link> </Link>
@@ -239,7 +239,7 @@ export default function AdminDashboardPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading dashboard</p> <p className="text-[#555] text-sm font-body">Loading dashboard</p>
</div> </div>
</div> </div>
@@ -345,7 +345,7 @@ export default function AdminDashboardPage() {
<MetricCard <MetricCard
label="Drafts" label="Drafts"
value={metrics.draftArticles} value={metrics.draftArticles}
accent="bg-[#231d18]" accent="bg-[#FFFFFF]"
href="/admin/articles" href="/admin/articles"
icon={ icon={
<svg className="w-5 h-5 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-[#888]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -356,7 +356,7 @@ export default function AdminDashboardPage() {
<MetricCard <MetricCard
label="Archived" label="Archived"
value={metrics.archivedArticles} value={metrics.archivedArticles}
accent="bg-[#231d18]" accent="bg-[#FFFFFF]"
href="/admin/articles" href="/admin/articles"
icon={ icon={
<svg className="w-5 h-5 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="w-5 h-5 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
@@ -372,20 +372,20 @@ export default function AdminDashboardPage() {
{/* Recent Activity Feed */} {/* Recent Activity Feed */}
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#231d18] flex items-center justify-between"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex items-center justify-between">
<h2 className="text-sm font-display font-bold text-white">Recent Activity</h2> <h2 className="text-sm font-display font-bold text-white">Recent Activity</h2>
<Link href="/admin/articles" className="text-xs text-[#F0A623] hover:text-blue-300 font-body transition-colors">View all </Link> <Link href="/admin/articles" className="text-xs text-[#1D4ED8] hover:text-blue-300 font-body transition-colors">View all </Link>
</div> </div>
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{recentActivity.length === 0 ? ( {recentActivity.length === 0 ? (
<div className="px-5 py-10 text-center"> <div className="px-5 py-10 text-center">
<p className="text-[#555] text-sm font-body">No recent activity found</p> <p className="text-[#555] text-sm font-body">No recent activity found</p>
</div> </div>
) : ( ) : (
recentActivity.map((item) => ( recentActivity.map((item) => (
<div key={`${item.source}-${item.id}`} className="px-5 py-3 flex items-start gap-3 hover:bg-[#1c1815] transition-colors"> <div key={`${item.source}-${item.id}`} className="px-5 py-3 flex items-start gap-3 hover:bg-[#F8FAFC] transition-colors">
<div className="w-6 h-6 rounded-full bg-[#231d18] flex items-center justify-center flex-shrink-0 mt-0.5"> <div className="w-6 h-6 rounded-full bg-[#FFFFFF] flex items-center justify-center flex-shrink-0 mt-0.5">
{ACTIVITY_ICONS[item.type] || ACTIVITY_ICONS['draft']} {ACTIVITY_ICONS[item.type] || ACTIVITY_ICONS['draft']}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
@@ -412,8 +412,8 @@ export default function AdminDashboardPage() {
{/* Quick Actions */} {/* Quick Actions */}
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
<div className="px-5 py-4 border-b border-[#231d18]"> <div className="px-5 py-4 border-b border-[#FFFFFF]">
<h2 className="text-sm font-display font-bold text-white">Quick Actions</h2> <h2 className="text-sm font-display font-bold text-white">Quick Actions</h2>
</div> </div>
<div className="p-4 flex flex-col gap-2"> <div className="p-4 flex flex-col gap-2">
@@ -587,21 +587,21 @@ export default function AdminDashboardPage() {
</div> </div>
{/* Status Summary Mini-Card */} {/* Status Summary Mini-Card */}
<div className="bg-[#111] border border-[#3a322b] rounded-lg p-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4">
<h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">Content Status</h3> <h3 className="text-xs font-body font-bold text-[#555] uppercase tracking-widest mb-3">Content Status</h3>
<div className="space-y-2"> <div className="space-y-2">
{[ {[
{ label: 'Published', value: metrics.publishedArticles, color: 'bg-green-400', total: metrics.totalArticles }, { label: 'Published', value: metrics.publishedArticles, color: 'bg-green-400', total: metrics.totalArticles },
{ label: 'Pending', value: metrics.pendingApprovals, color: 'bg-yellow-400', total: metrics.totalArticles }, { label: 'Pending', value: metrics.pendingApprovals, color: 'bg-yellow-400', total: metrics.totalArticles },
{ label: 'Draft', value: metrics.draftArticles, color: 'bg-[#444]', total: metrics.totalArticles }, { label: 'Draft', value: metrics.draftArticles, color: 'bg-[#444]', total: metrics.totalArticles },
{ label: 'Archived', value: metrics.archivedArticles, color: 'bg-[#3a322b]', total: metrics.totalArticles }, { label: 'Archived', value: metrics.archivedArticles, color: 'bg-[#DCE6F2]', total: metrics.totalArticles },
].map(({ label, value, color, total }) => ( ].map(({ label, value, color, total }) => (
<div key={label}> <div key={label}>
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="text-xs text-[#888] font-body">{label}</span> <span className="text-xs text-[#888] font-body">{label}</span>
<span className="text-xs text-white font-body font-semibold">{value}</span> <span className="text-xs text-white font-body font-semibold">{value}</span>
</div> </div>
<div className="h-1.5 bg-[#231d18] rounded-full overflow-hidden"> <div className="h-1.5 bg-[#FFFFFF] rounded-full overflow-hidden">
<div <div
className={`h-full rounded-full ${color} transition-all duration-500`} className={`h-full rounded-full ${color} transition-all duration-500`}
style={{ width: total > 0 ? `${Math.round((value / total) * 100)}%` : '0%' }} style={{ width: total > 0 ? `${Math.round((value / total) * 100)}%` : '0%' }}

View File

@@ -41,7 +41,7 @@ export default function ReviewActions({ id, currentStatus }: Props) {
value={note} value={note}
onChange={(e) => setNote(e.target.value)} onChange={(e) => setNote(e.target.value)}
placeholder="Reviewer note (optional)" placeholder="Reviewer note (optional)"
className="w-full rounded border border-[#1f2937] bg-[#070a10] p-2 text-sm text-[#e5e7eb] focus:border-[#F0A623] focus:outline-none" className="w-full rounded border border-[#1f2937] bg-[#070a10] p-2 text-sm text-[#e5e7eb] focus:border-[#1D4ED8] focus:outline-none"
rows={2} rows={2}
/> />
<div className="mt-3 flex flex-wrap gap-3 text-sm"> <div className="mt-3 flex flex-wrap gap-3 text-sm">

View File

@@ -41,12 +41,12 @@ export default async function ReviewDetailPage({ params }: { params: Promise<{ i
return ( return (
<main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]"> <main className="mx-auto max-w-6xl px-6 py-10 text-[#e5e7eb]">
<Link href="/admin/review-queue" className="text-sm text-[#F0A623] hover:underline"> <Link href="/admin/review-queue" className="text-sm text-[#1D4ED8] hover:underline">
Back to queue Back to queue
</Link> </Link>
<header className="mt-4 mb-6 border-b border-[#222] pb-4"> <header className="mt-4 mb-6 border-b border-[#222] pb-4">
<div className="flex items-center gap-3 text-xs uppercase tracking-wider text-[#F0A623]"> <div className="flex items-center gap-3 text-xs uppercase tracking-wider text-[#1D4ED8]">
<span>{persona?.name || "—"}</span> <span>{persona?.name || "—"}</span>
<span className="text-[#4b5563]">·</span> <span className="text-[#4b5563]">·</span>
<span>{art.category}</span> <span>{art.category}</span>

View File

@@ -70,7 +70,7 @@ export default async function ReviewQueuePage() {
return ( return (
<li <li
key={r.id} key={r.id}
className="rounded border border-[#1f2937] bg-[#0b0f17] p-5 hover:border-[#F0A623] transition-colors" className="rounded border border-[#1f2937] bg-[#0b0f17] p-5 hover:border-[#1D4ED8] transition-colors"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
{persona?.avatar_url ? ( {persona?.avatar_url ? (
@@ -85,7 +85,7 @@ export default async function ReviewQueuePage() {
<div className="h-14 w-14 rounded-full bg-[#1f2937]" /> <div className="h-14 w-14 rounded-full bg-[#1f2937]" />
)} )}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-xs uppercase tracking-wider text-[#F0A623]"> <div className="text-xs uppercase tracking-wider text-[#1D4ED8]">
{persona?.name || "—"} · {r.category} {persona?.name || "—"} · {r.category}
</div> </div>
<h2 className="text-xl font-semibold mt-1">{r.title}</h2> <h2 className="text-xl font-semibold mt-1">{r.title}</h2>
@@ -100,7 +100,7 @@ export default async function ReviewQueuePage() {
<div className="mt-4 flex flex-wrap items-center gap-3 text-xs"> <div className="mt-4 flex flex-wrap items-center gap-3 text-xs">
<Link <Link
href={`/admin/review-queue/${r.id}`} href={`/admin/review-queue/${r.id}`}
className="rounded bg-[#1d4ed8] px-3 py-1.5 font-medium text-white hover:bg-[#BA7517]" className="rounded bg-[#1d4ed8] px-3 py-1.5 font-medium text-white hover:bg-[#1E3A8A]"
> >
Review Review
</Link> </Link>

View File

@@ -12,7 +12,7 @@ export default function RmpHubPage() {
useEffect(() => { if (!loading && !user) router?.push('/login'); }, [user, loading, router]); useEffect(() => { if (!loading && !user) router?.push('/login'); }, [user, loading, router]);
if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /></div>; if (loading) return <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"><div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" /></div>;
if (!user) return null; if (!user) return null;
return ( return (
@@ -68,7 +68,7 @@ export default function RmpHubPage() {
], ],
}, },
]?.map(section => ( ]?.map(section => (
<div key={section?.title} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 space-y-3"> <div key={section?.title} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 space-y-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-2xl">{section?.icon}</span> <span className="text-2xl">{section?.icon}</span>
<div> <div>
@@ -78,19 +78,19 @@ export default function RmpHubPage() {
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
{section?.links?.map(l => ( {section?.links?.map(l => (
<Link key={l?.href} href={l?.href} className="block px-2 py-1 rounded text-xs text-[#888] hover:text-white hover:bg-[#231d18] transition-colors"> <Link key={l?.href} href={l?.href} className="block px-2 py-1 rounded text-xs text-[#888] hover:text-white hover:bg-[#FFFFFF] transition-colors">
{l?.label} {l?.label}
</Link> </Link>
))} ))}
</div> </div>
<Link href={section?.href} className="block w-full text-center px-3 py-1.5 bg-[#231d18] border border-[#2a3a50] rounded text-xs text-[#F0A623] hover:bg-[#0d1a2d] transition-colors"> <Link href={section?.href} className="block w-full text-center px-3 py-1.5 bg-[#FFFFFF] border border-[#2a3a50] rounded text-xs text-[#1D4ED8] hover:bg-[#0d1a2d] transition-colors">
Open {section?.title} Open {section?.title}
</Link> </Link>
</div> </div>
))} ))}
</div> </div>
<div className="bg-[#0d1117] border border-[#231d18] rounded-lg p-4"> <div className="bg-[#0d1117] border border-[#FFFFFF] rounded-lg p-4">
<p className="text-xs text-[#555] mb-3">Quick Navigation</p> <p className="text-xs text-[#555] mb-3">Quick Navigation</p>
<RmpAdminNav /> <RmpAdminNav />
</div> </div>

View File

@@ -61,7 +61,7 @@ const ROLE_COLORS: Record<string, string> = {
editor: 'text-purple-400 bg-purple-400/10 border-purple-400/20', editor: 'text-purple-400 bg-purple-400/10 border-purple-400/20',
moderator: 'text-orange-400 bg-orange-400/10 border-orange-400/20', moderator: 'text-orange-400 bg-orange-400/10 border-orange-400/20',
contributor: 'text-blue-400 bg-blue-400/10 border-blue-400/20', contributor: 'text-blue-400 bg-blue-400/10 border-blue-400/20',
reader: 'text-[#888] bg-[#231d18] border-[#333]', reader: 'text-[#888] bg-[#FFFFFF] border-[#333]',
}; };
const ROLE_ICONS: Record<string, React.ReactNode> = { const ROLE_ICONS: Record<string, React.ReactNode> = {
@@ -324,7 +324,7 @@ export default function AdminRolesPage() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="flex flex-col items-center gap-3"> <div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
<p className="text-[#555] text-sm font-body">Loading roles panel</p> <p className="text-[#555] text-sm font-body">Loading roles panel</p>
</div> </div>
</div> </div>
@@ -376,7 +376,7 @@ export default function AdminRolesPage() {
key={role} key={role}
onClick={() => { setSelectedRole(role === selectedRole ? 'all' : role); setActiveTab('assign'); }} onClick={() => { setSelectedRole(role === selectedRole ? 'all' : role); setActiveTab('assign'); }}
className={`bg-[#111] border rounded-lg p-4 text-left hover:border-[#333] transition-all ${ className={`bg-[#111] border rounded-lg p-4 text-left hover:border-[#333] transition-all ${
selectedRole === role ? 'border-[#F0A623] bg-[#0d1a2d]' : 'border-[#3a322b]' selectedRole === role ? 'border-[#1D4ED8] bg-[#0d1a2d]' : 'border-[#DCE6F2]'
}`} }`}
> >
<div className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-body font-semibold border mb-2 ${color}`}> <div className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-body font-semibold border mb-2 ${color}`}>
@@ -391,7 +391,7 @@ export default function AdminRolesPage() {
</div> </div>
{/* Tabs */} {/* Tabs */}
<div className="flex gap-1 mb-6 bg-[#111] border border-[#3a322b] rounded-lg p-1 w-fit"> <div className="flex gap-1 mb-6 bg-[#111] border border-[#DCE6F2] rounded-lg p-1 w-fit">
{([ {([
{ id: 'assign', label: 'Assign Roles', icon: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z' }, { id: 'assign', label: 'Assign Roles', icon: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z' },
{ id: 'permissions', label: 'Permissions', icon: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z' }, { id: 'permissions', label: 'Permissions', icon: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z' },
@@ -402,7 +402,7 @@ export default function AdminRolesPage() {
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-body font-medium transition-all ${ className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-body font-medium transition-all ${
activeTab === tab.id activeTab === tab.id
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -416,9 +416,9 @@ export default function AdminRolesPage() {
{/* ── Tab: Assign Roles ── */} {/* ── Tab: Assign Roles ── */}
{activeTab === 'assign' && ( {activeTab === 'assign' && (
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{/* Toolbar */} {/* Toolbar */}
<div className="px-5 py-4 border-b border-[#231d18] flex flex-col sm:flex-row gap-3"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex flex-col sm:flex-row gap-3">
<div className="relative flex-1"> <div className="relative flex-1">
<svg className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"> <svg className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[#555]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
@@ -428,13 +428,13 @@ export default function AdminRolesPage() {
placeholder="Search users…" placeholder="Search users…"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder-[#555] font-body focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg pl-9 pr-4 py-2 text-sm text-white placeholder-[#555] font-body focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<select <select
value={selectedRole} value={selectedRole}
onChange={(e) => setSelectedRole(e.target.value)} onChange={(e) => setSelectedRole(e.target.value)}
className="bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#F0A623]" className="bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-sm text-white font-body focus:outline-none focus:border-[#1D4ED8]"
> >
<option value="all">All Roles</option> <option value="all">All Roles</option>
{ROLES.map((r) => ( {ROLES.map((r) => (
@@ -447,7 +447,7 @@ export default function AdminRolesPage() {
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-[#231d18]"> <tr className="border-b border-[#FFFFFF]">
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">User</th> <th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">User</th>
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">Current Role</th> <th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">Current Role</th>
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider hidden sm:table-cell">Status</th> <th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider hidden sm:table-cell">Status</th>
@@ -455,7 +455,7 @@ export default function AdminRolesPage() {
<th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">Assign Role</th> <th className="px-5 py-3 text-left text-xs font-body font-bold text-[#555] uppercase tracking-wider">Assign Role</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-[#231d18]"> <tbody className="divide-y divide-[#FFFFFF]">
{filteredUsers.length === 0 ? ( {filteredUsers.length === 0 ? (
<tr> <tr>
<td colSpan={5} className="px-5 py-10 text-center text-[#555] text-sm font-body"> <td colSpan={5} className="px-5 py-10 text-center text-[#555] text-sm font-body">
@@ -464,10 +464,10 @@ export default function AdminRolesPage() {
</tr> </tr>
) : ( ) : (
filteredUsers.map((u) => ( filteredUsers.map((u) => (
<tr key={u.id} className="hover:bg-[#1c1815] transition-colors"> <tr key={u.id} className="hover:bg-[#F8FAFC] transition-colors">
<td className="px-5 py-3"> <td className="px-5 py-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#231d18] flex items-center justify-center flex-shrink-0 text-xs font-body font-bold text-[#888]"> <div className="w-8 h-8 rounded-full bg-[#FFFFFF] flex items-center justify-center flex-shrink-0 text-xs font-body font-bold text-[#888]">
{u.avatar_url {u.avatar_url
? <img src={u.avatar_url} alt={u.full_name} className="w-8 h-8 rounded-full object-cover" /> ? <img src={u.avatar_url} alt={u.full_name} className="w-8 h-8 rounded-full object-cover" />
: getInitials(u.full_name || u.email) : getInitials(u.full_name || u.email)
@@ -484,7 +484,7 @@ export default function AdminRolesPage() {
</td> </td>
<td className="px-5 py-3 hidden sm:table-cell"> <td className="px-5 py-3 hidden sm:table-cell">
<span className={`text-xs font-body font-semibold px-2 py-0.5 rounded-full ${ <span className={`text-xs font-body font-semibold px-2 py-0.5 rounded-full ${
u.is_active ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#231d18]' u.is_active ? 'text-green-400 bg-green-400/10' : 'text-[#555] bg-[#FFFFFF]'
}`}> }`}>
{u.is_active ? 'Active' : 'Inactive'} {u.is_active ? 'Active' : 'Inactive'}
</span> </span>
@@ -500,14 +500,14 @@ export default function AdminRolesPage() {
defaultValue={u.role} defaultValue={u.role}
onChange={(e) => handleAssignRole(u.id, e.target.value)} onChange={(e) => handleAssignRole(u.id, e.target.value)}
disabled={processingUserId === u.id} disabled={processingUserId === u.id}
className="bg-[#1c1815] border border-[#3a322b] rounded-lg px-2 py-1.5 text-xs text-white font-body focus:outline-none focus:border-[#F0A623] disabled:opacity-50" className="bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-2 py-1.5 text-xs text-white font-body focus:outline-none focus:border-[#1D4ED8] disabled:opacity-50"
> >
{ROLES.map((r) => ( {ROLES.map((r) => (
<option key={r} value={r} className="capitalize">{r.charAt(0).toUpperCase() + r.slice(1)}</option> <option key={r} value={r} className="capitalize">{r.charAt(0).toUpperCase() + r.slice(1)}</option>
))} ))}
</select> </select>
{processingUserId === u.id && ( {processingUserId === u.id && (
<div className="w-4 h-4 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
)} )}
</div> </div>
</td> </td>
@@ -519,7 +519,7 @@ export default function AdminRolesPage() {
</div> </div>
{filteredUsers.length > 0 && ( {filteredUsers.length > 0 && (
<div className="px-5 py-3 border-t border-[#231d18]"> <div className="px-5 py-3 border-t border-[#FFFFFF]">
<p className="text-xs text-[#555] font-body">{filteredUsers.length} user{filteredUsers.length !== 1 ? 's' : ''} shown</p> <p className="text-xs text-[#555] font-body">{filteredUsers.length} user{filteredUsers.length !== 1 ? 's' : ''} shown</p>
</div> </div>
)} )}
@@ -535,9 +535,9 @@ export default function AdminRolesPage() {
const color = ROLE_COLORS[perm.role] || ROLE_COLORS['reader']; const color = ROLE_COLORS[perm.role] || ROLE_COLORS['reader'];
return ( return (
<div key={perm.role} className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div key={perm.role} className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{/* Role Header */} {/* Role Header */}
<div className="px-5 py-4 border-b border-[#231d18] flex items-center justify-between"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-body font-semibold border ${color}`}> <div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-body font-semibold border ${color}`}>
{ROLE_ICONS[perm.role]} {ROLE_ICONS[perm.role]}
@@ -557,7 +557,7 @@ export default function AdminRolesPage() {
<button <button
onClick={handleSavePermissions} onClick={handleSavePermissions}
disabled={savingPermissions} disabled={savingPermissions}
className="px-3 py-1.5 text-xs font-body font-semibold bg-[#F0A623] hover:bg-blue-500 text-white rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5" className="px-3 py-1.5 text-xs font-body font-semibold bg-[#1D4ED8] hover:bg-blue-500 text-white rounded-lg transition-colors disabled:opacity-50 flex items-center gap-1.5"
> >
{savingPermissions && <div className="w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" />} {savingPermissions && <div className="w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" />}
Save Changes Save Changes
@@ -593,12 +593,12 @@ export default function AdminRolesPage() {
{isEditing ? ( {isEditing ? (
<button <button
onClick={() => setEditedPermissions((prev) => ({ ...prev, [key]: !val }))} onClick={() => setEditedPermissions((prev) => ({ ...prev, [key]: !val }))}
className={`relative w-9 h-5 rounded-full transition-colors ${val ? 'bg-[#F0A623]' : 'bg-[#333]'}`} className={`relative w-9 h-5 rounded-full transition-colors ${val ? 'bg-[#1D4ED8]' : 'bg-[#333]'}`}
> >
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${val ? 'translate-x-4' : 'translate-x-0.5'}`} /> <span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-transform ${val ? 'translate-x-4' : 'translate-x-0.5'}`} />
</button> </button>
) : ( ) : (
<span className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 ${val ? 'bg-green-400/10' : 'bg-[#231d18]'}`}> <span className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 ${val ? 'bg-green-400/10' : 'bg-[#FFFFFF]'}`}>
{val ? ( {val ? (
<svg className="w-3 h-3 text-green-400" fill="none" stroke="currentColor" strokeWidth="3" viewBox="0 0 24 24"> <svg className="w-3 h-3 text-green-400" fill="none" stroke="currentColor" strokeWidth="3" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /> <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
@@ -631,9 +631,9 @@ export default function AdminRolesPage() {
{/* ── Tab: Activity Logs ── */} {/* ── Tab: Activity Logs ── */}
{activeTab === 'activity' && ( {activeTab === 'activity' && (
<div className="bg-[#111] border border-[#3a322b] rounded-lg overflow-hidden"> <div className="bg-[#111] border border-[#DCE6F2] rounded-lg overflow-hidden">
{/* Toolbar */} {/* Toolbar */}
<div className="px-5 py-4 border-b border-[#231d18] flex flex-col sm:flex-row gap-3 items-start sm:items-center justify-between"> <div className="px-5 py-4 border-b border-[#FFFFFF] flex flex-col sm:flex-row gap-3 items-start sm:items-center justify-between">
<h2 className="text-sm font-display font-bold text-white">Role-Based Activity</h2> <h2 className="text-sm font-display font-bold text-white">Role-Based Activity</h2>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs text-[#555] font-body">Filter by role:</span> <span className="text-xs text-[#555] font-body">Filter by role:</span>
@@ -644,8 +644,8 @@ export default function AdminRolesPage() {
onClick={() => setActivityRoleFilter(r)} onClick={() => setActivityRoleFilter(r)}
className={`px-2.5 py-1 rounded-md text-xs font-body font-medium transition-colors capitalize ${ className={`px-2.5 py-1 rounded-md text-xs font-body font-medium transition-colors capitalize ${
activityRoleFilter === r activityRoleFilter === r
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'bg-[#231d18] text-[#888] hover:text-white' : 'bg-[#FFFFFF] text-[#888] hover:text-white'
}`} }`}
> >
{r} {r}
@@ -656,7 +656,7 @@ export default function AdminRolesPage() {
</div> </div>
{/* Log List */} {/* Log List */}
<div className="divide-y divide-[#231d18]"> <div className="divide-y divide-[#FFFFFF]">
{activityLogs.length === 0 ? ( {activityLogs.length === 0 ? (
<div className="px-5 py-12 text-center"> <div className="px-5 py-12 text-center">
<svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24"> <svg className="w-10 h-10 text-[#333] mx-auto mb-3" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
@@ -666,13 +666,13 @@ export default function AdminRolesPage() {
</div> </div>
) : ( ) : (
activityLogs.map((log) => { activityLogs.map((log) => {
const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#231d18]'; const actionColor = ACTION_COLORS[log.action] || 'text-[#888] bg-[#FFFFFF]';
const actionLabel = ACTION_LABELS[log.action] || log.action.replace(/_/g, ' '); const actionLabel = ACTION_LABELS[log.action] || log.action.replace(/_/g, ' ');
const performer = log.user_profiles?.full_name || log.performed_by_email || 'System'; const performer = log.user_profiles?.full_name || log.performed_by_email || 'System';
const performerRole = log.user_profiles?.role; const performerRole = log.user_profiles?.role;
return ( return (
<div key={log.id} className="px-5 py-3.5 flex items-start gap-4 hover:bg-[#1c1815] transition-colors"> <div key={log.id} className="px-5 py-3.5 flex items-start gap-4 hover:bg-[#F8FAFC] transition-colors">
{/* Action badge */} {/* Action badge */}
<div className={`mt-0.5 px-2 py-0.5 rounded text-xs font-body font-semibold flex-shrink-0 ${actionColor}`}> <div className={`mt-0.5 px-2 py-0.5 rounded text-xs font-body font-semibold flex-shrink-0 ${actionColor}`}>
{actionLabel} {actionLabel}
@@ -720,7 +720,7 @@ export default function AdminRolesPage() {
</div> </div>
{activityLogs.length > 0 && ( {activityLogs.length > 0 && (
<div className="px-5 py-3 border-t border-[#231d18]"> <div className="px-5 py-3 border-t border-[#FFFFFF]">
<p className="text-xs text-[#555] font-body">{activityLogs.length} log entries shown</p> <p className="text-xs text-[#555] font-body">{activityLogs.length} log entries shown</p>
</div> </div>
)} )}

View File

@@ -223,7 +223,7 @@ export default function AdminSuspensionsPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center"> <div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -288,7 +288,7 @@ export default function AdminSuspensionsPage() {
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${ className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${
activeTab === tab activeTab === tab
? 'text-[#F0A623] border-[#F0A623]' ? 'text-[#1D4ED8] border-[#1D4ED8]'
: 'text-[#555] border-transparent hover:text-[#888]' : 'text-[#555] border-transparent hover:text-[#888]'
}`} }`}
> >
@@ -304,7 +304,7 @@ export default function AdminSuspensionsPage() {
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<> <>
@@ -336,7 +336,7 @@ export default function AdminSuspensionsPage() {
<tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] font-bold text-sm flex-shrink-0 overflow-hidden">
{s.user?.avatar_url ? ( {s.user?.avatar_url ? (
<img src={s.user.avatar_url} alt={s.user.full_name || s.user.email} className="w-full h-full object-cover" /> <img src={s.user.avatar_url} alt={s.user.full_name || s.user.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -415,7 +415,7 @@ export default function AdminSuspensionsPage() {
<tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={s.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] text-xs font-bold flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] text-xs font-bold flex-shrink-0">
{(s.user?.full_name?.[0] || s.user?.email?.[0] || '?').toUpperCase()} {(s.user?.full_name?.[0] || s.user?.email?.[0] || '?').toUpperCase()}
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
@@ -465,7 +465,7 @@ export default function AdminSuspensionsPage() {
placeholder="Search users..." placeholder="Search users..."
value={userSearch} value={userSearch}
onChange={(e) => setUserSearch(e.target.value)} onChange={(e) => setUserSearch(e.target.value)}
className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
</div> </div>
@@ -488,7 +488,7 @@ export default function AdminSuspensionsPage() {
<tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] font-bold text-sm flex-shrink-0 overflow-hidden">
{u.avatar_url ? ( {u.avatar_url ? (
<img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" /> <img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -572,7 +572,7 @@ export default function AdminSuspensionsPage() {
<form onSubmit={handleSuspend} className="px-6 py-5 space-y-4"> <form onSubmit={handleSuspend} className="px-6 py-5 space-y-4">
{/* User info */} {/* User info */}
<div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3"> <div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3">
<div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] font-bold text-sm flex-shrink-0 overflow-hidden">
{selectedUser.avatar_url ? ( {selectedUser.avatar_url ? (
<img src={selectedUser.avatar_url} alt={selectedUser.full_name || selectedUser.email} className="w-full h-full object-cover" /> <img src={selectedUser.avatar_url} alt={selectedUser.full_name || selectedUser.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -637,7 +637,7 @@ export default function AdminSuspensionsPage() {
<select <select
value={suspendDuration ?? ''} value={suspendDuration ?? ''}
onChange={(e) => setSuspendDuration(e.target.value ? parseInt(e.target.value) : null)} onChange={(e) => setSuspendDuration(e.target.value ? parseInt(e.target.value) : null)}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
{DURATION_OPTIONS.filter(d => d.value !== null).map((d) => ( {DURATION_OPTIONS.filter(d => d.value !== null).map((d) => (
<option key={d.value} value={d.value!}>{d.label}</option> <option key={d.value} value={d.value!}>{d.label}</option>
@@ -657,7 +657,7 @@ export default function AdminSuspensionsPage() {
onChange={(e) => setSuspendReason(e.target.value)} onChange={(e) => setSuspendReason(e.target.value)}
placeholder="Explain the reason for this action..." placeholder="Explain the reason for this action..."
rows={3} rows={3}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#1D4ED8] resize-none"
/> />
</div> </div>
@@ -729,7 +729,7 @@ export default function AdminSuspensionsPage() {
onChange={(e) => setLiftReason(e.target.value)} onChange={(e) => setLiftReason(e.target.value)}
placeholder="Reason for lifting this restriction..." placeholder="Reason for lifting this restriction..."
rows={2} rows={2}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#1D4ED8] resize-none"
/> />
</div> </div>
<div className="flex items-center gap-3 pt-1"> <div className="flex items-center gap-3 pt-1">

View File

@@ -31,14 +31,14 @@ interface Invitation {
type Tab = 'users' | 'invitations'; type Tab = 'users' | 'invitations';
const ROLE_OPTIONS = [ const ROLE_OPTIONS = [
{ value: 'reader', label: 'Reader', color: 'text-[#888] bg-[#231d18]' }, { value: 'reader', label: 'Reader', color: 'text-[#888] bg-[#FFFFFF]' },
{ value: 'contributor', label: 'Contributor', color: 'text-blue-400 bg-blue-400/10' }, { value: 'contributor', label: 'Contributor', color: 'text-blue-400 bg-blue-400/10' },
{ value: 'editor', label: 'Editor', color: 'text-purple-400 bg-purple-400/10' }, { value: 'editor', label: 'Editor', color: 'text-purple-400 bg-purple-400/10' },
{ value: 'admin', label: 'Admin', color: 'text-red-400 bg-red-400/10' }, { value: 'admin', label: 'Admin', color: 'text-red-400 bg-red-400/10' },
]; ];
const ROLE_COLORS: Record<string, string> = { const ROLE_COLORS: Record<string, string> = {
reader: 'text-[#888] bg-[#231d18]', reader: 'text-[#888] bg-[#FFFFFF]',
contributor: 'text-blue-400 bg-blue-400/10', contributor: 'text-blue-400 bg-blue-400/10',
editor: 'text-purple-400 bg-purple-400/10', editor: 'text-purple-400 bg-purple-400/10',
admin: 'text-red-400 bg-red-400/10', admin: 'text-red-400 bg-red-400/10',
@@ -47,7 +47,7 @@ const ROLE_COLORS: Record<string, string> = {
const STATUS_COLORS: Record<string, string> = { const STATUS_COLORS: Record<string, string> = {
pending: 'text-yellow-400 bg-yellow-400/10', pending: 'text-yellow-400 bg-yellow-400/10',
accepted: 'text-green-400 bg-green-400/10', accepted: 'text-green-400 bg-green-400/10',
revoked: 'text-[#555] bg-[#231d18]', revoked: 'text-[#555] bg-[#FFFFFF]',
expired: 'text-orange-400 bg-orange-400/10', expired: 'text-orange-400 bg-orange-400/10',
email_failed: 'text-red-400 bg-red-400/10', email_failed: 'text-red-400 bg-red-400/10',
}; };
@@ -246,7 +246,7 @@ export default function AdminUsersPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center"> <div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -276,7 +276,7 @@ export default function AdminUsersPage() {
</div> </div>
<button <button
onClick={() => setShowInviteModal(true)} onClick={() => setShowInviteModal(true)}
className="flex items-center gap-2 bg-[#F0A623] hover:bg-[#BA7517] text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors" className="flex items-center gap-2 bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.22h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6 6l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 21.5 16.5z" /> <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.22h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6 6l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 21.5 16.5z" />
@@ -306,7 +306,7 @@ export default function AdminUsersPage() {
onClick={() => setActiveTab(tab)} onClick={() => setActiveTab(tab)}
className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${ className={`px-4 py-2.5 text-sm font-semibold capitalize transition-colors border-b-2 -mb-px ${
activeTab === tab activeTab === tab
? 'text-[#F0A623] border-[#F0A623]' ? 'text-[#1D4ED8] border-[#1D4ED8]'
: 'text-[#555] border-transparent hover:text-[#888]' : 'text-[#555] border-transparent hover:text-[#888]'
}`} }`}
> >
@@ -331,14 +331,14 @@ export default function AdminUsersPage() {
placeholder={activeTab === 'users' ? 'Search users...' : 'Search by email...'} placeholder={activeTab === 'users' ? 'Search users...' : 'Search by email...'}
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
{activeTab === 'users' && ( {activeTab === 'users' && (
<select <select
value={roleFilter} value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value)} onChange={(e) => setRoleFilter(e.target.value)}
className="bg-[#111827] border border-[#1f2937] text-[#ccc] text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#F0A623]" className="bg-[#111827] border border-[#1f2937] text-[#ccc] text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value="all">All Roles</option> <option value="all">All Roles</option>
{ROLE_OPTIONS.map((r) => ( {ROLE_OPTIONS.map((r) => (
@@ -353,7 +353,7 @@ export default function AdminUsersPage() {
<div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden"> <div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filteredUsers.length === 0 ? ( ) : filteredUsers.length === 0 ? (
<div className="text-center py-16 text-[#555]"> <div className="text-center py-16 text-[#555]">
@@ -379,7 +379,7 @@ export default function AdminUsersPage() {
<tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={u.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] font-bold text-sm flex-shrink-0 overflow-hidden">
{u.avatar_url ? ( {u.avatar_url ? (
<img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" /> <img src={u.avatar_url} alt={u.full_name || u.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -393,7 +393,7 @@ export default function AdminUsersPage() {
</div> </div>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wide ${ROLE_COLORS[u.role] || 'text-[#888] bg-[#231d18]'}`}> <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wide ${ROLE_COLORS[u.role] || 'text-[#888] bg-[#FFFFFF]'}`}>
{u.role} {u.role}
</span> </span>
</td> </td>
@@ -418,7 +418,7 @@ export default function AdminUsersPage() {
<button <button
onClick={() => openEditRole(u)} onClick={() => openEditRole(u)}
disabled={processingIds.has(u.id)} disabled={processingIds.has(u.id)}
className="text-[#555] hover:text-[#F0A623] transition-colors text-xs font-medium px-2 py-1 rounded hover:bg-[#1f2937] disabled:opacity-50" className="text-[#555] hover:text-[#1D4ED8] transition-colors text-xs font-medium px-2 py-1 rounded hover:bg-[#1f2937] disabled:opacity-50"
> >
Edit Role Edit Role
</button> </button>
@@ -455,7 +455,7 @@ export default function AdminUsersPage() {
<div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden"> <div className="bg-[#111827] border border-[#1f2937] rounded-xl overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filteredInvitations.length === 0 ? ( ) : filteredInvitations.length === 0 ? (
<div className="text-center py-16 text-[#555]"> <div className="text-center py-16 text-[#555]">
@@ -465,7 +465,7 @@ export default function AdminUsersPage() {
<p className="text-sm">No invitations sent yet</p> <p className="text-sm">No invitations sent yet</p>
<button <button
onClick={() => setShowInviteModal(true)} onClick={() => setShowInviteModal(true)}
className="mt-3 text-[#F0A623] hover:text-[#F0A623] text-sm transition-colors" className="mt-3 text-[#1D4ED8] hover:text-[#1D4ED8] text-sm transition-colors"
> >
Send your first invitation Send your first invitation
</button> </button>
@@ -491,19 +491,19 @@ export default function AdminUsersPage() {
<tr key={inv.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors"> <tr key={inv.id} className="border-b border-[#1a2030] hover:bg-[#0d1117]/50 transition-colors">
<td className="px-5 py-3.5"> <td className="px-5 py-3.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] text-xs font-bold flex-shrink-0"> <div className="w-7 h-7 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] text-xs font-bold flex-shrink-0">
{inv.email[0].toUpperCase()} {inv.email[0].toUpperCase()}
</div> </div>
<span className="text-white text-sm">{inv.email}</span> <span className="text-white text-sm">{inv.email}</span>
</div> </div>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wide ${ROLE_COLORS[inv.role] || 'text-[#888] bg-[#231d18]'}`}> <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold uppercase tracking-wide ${ROLE_COLORS[inv.role] || 'text-[#888] bg-[#FFFFFF]'}`}>
{inv.role} {inv.role}
</span> </span>
</td> </td>
<td className="px-4 py-3.5"> <td className="px-4 py-3.5">
<span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold capitalize ${STATUS_COLORS[displayStatus] || 'text-[#888] bg-[#231d18]'}`}> <span className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold capitalize ${STATUS_COLORS[displayStatus] || 'text-[#888] bg-[#FFFFFF]'}`}>
{displayStatus.replace('_', ' ')} {displayStatus.replace('_', ' ')}
</span> </span>
</td> </td>
@@ -564,7 +564,7 @@ export default function AdminUsersPage() {
value={inviteEmail} value={inviteEmail}
onChange={(e) => setInviteEmail(e.target.value)} onChange={(e) => setInviteEmail(e.target.value)}
placeholder="contributor@example.com" placeholder="contributor@example.com"
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -574,7 +574,7 @@ export default function AdminUsersPage() {
<select <select
value={inviteRole} value={inviteRole}
onChange={(e) => setInviteRole(e.target.value)} onChange={(e) => setInviteRole(e.target.value)}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value="contributor">Contributor can submit articles for review</option> <option value="contributor">Contributor can submit articles for review</option>
<option value="editor">Editor can publish and manage articles</option> <option value="editor">Editor can publish and manage articles</option>
@@ -590,7 +590,7 @@ export default function AdminUsersPage() {
onChange={(e) => setInviteMessage(e.target.value)} onChange={(e) => setInviteMessage(e.target.value)}
placeholder="Add a personal note to the invitation email..." placeholder="Add a personal note to the invitation email..."
rows={3} rows={3}
className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#0d1117] border border-[#1f2937] text-white text-sm rounded-lg px-3 py-2.5 placeholder-[#444] focus:outline-none focus:border-[#1D4ED8] resize-none"
/> />
</div> </div>
<div className="bg-[#0d1117] border border-[#1f2937] rounded-lg p-3"> <div className="bg-[#0d1117] border border-[#1f2937] rounded-lg p-3">
@@ -609,7 +609,7 @@ export default function AdminUsersPage() {
<button <button
type="submit" type="submit"
disabled={inviteSending || !inviteEmail.trim()} disabled={inviteSending || !inviteEmail.trim()}
className="flex-1 bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2" className="flex-1 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2"
> >
{inviteSending ? ( {inviteSending ? (
<> <>
@@ -644,7 +644,7 @@ export default function AdminUsersPage() {
</div> </div>
<div className="px-6 py-5 space-y-4"> <div className="px-6 py-5 space-y-4">
<div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3"> <div className="flex items-center gap-3 bg-[#0d1117] rounded-lg p-3">
<div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#F0A623] font-bold text-sm flex-shrink-0 overflow-hidden"> <div className="w-9 h-9 rounded-full bg-[#1f2937] flex items-center justify-center text-[#1D4ED8] font-bold text-sm flex-shrink-0 overflow-hidden">
{editingUser.avatar_url ? ( {editingUser.avatar_url ? (
<img src={editingUser.avatar_url} alt={editingUser.full_name || editingUser.email} className="w-full h-full object-cover" /> <img src={editingUser.avatar_url} alt={editingUser.full_name || editingUser.email} className="w-full h-full object-cover" />
) : ( ) : (
@@ -666,7 +666,7 @@ export default function AdminUsersPage() {
key={r.value} key={r.value}
className={`flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${ className={`flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${
editRole === r.value editRole === r.value
? 'border-[#F0A623] bg-[#F0A623]/10' ? 'border-[#1D4ED8] bg-[#1D4ED8]/10'
: 'border-[#1f2937] hover:border-[#2a3a50]' : 'border-[#1f2937] hover:border-[#2a3a50]'
}`} }`}
> >
@@ -679,9 +679,9 @@ export default function AdminUsersPage() {
className="sr-only" className="sr-only"
/> />
<span className={`w-3.5 h-3.5 rounded-full border-2 flex items-center justify-center flex-shrink-0 ${ <span className={`w-3.5 h-3.5 rounded-full border-2 flex items-center justify-center flex-shrink-0 ${
editRole === r.value ? 'border-[#F0A623]' : 'border-[#444]' editRole === r.value ? 'border-[#1D4ED8]' : 'border-[#444]'
}`}> }`}>
{editRole === r.value && <span className="w-1.5 h-1.5 rounded-full bg-[#F0A623]" />} {editRole === r.value && <span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8]" />}
</span> </span>
<span className={`text-xs font-semibold uppercase tracking-wide px-2 py-0.5 rounded ${r.color}`}> <span className={`text-xs font-semibold uppercase tracking-wide px-2 py-0.5 rounded ${r.color}`}>
{r.label} {r.label}
@@ -700,7 +700,7 @@ export default function AdminUsersPage() {
<button <button
onClick={handleEditSave} onClick={handleEditSave}
disabled={editSaving || editRole === editingUser.role} disabled={editSaving || editRole === editingUser.role}
className="flex-1 bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2" className="flex-1 bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 disabled:cursor-not-allowed text-white text-sm font-semibold py-2.5 rounded-lg transition-colors flex items-center justify-center gap-2"
> >
{editSaving ? ( {editSaving ? (
<> <>

View File

@@ -223,7 +223,7 @@ export default function WPUsersAdminPage() {
</thead> </thead>
<tbody> <tbody>
{filteredUsers.map(u => ( {filteredUsers.map(u => (
<tr key={u.id} className="border-b border-[#231d18] hover:bg-[#151515] transition-colors"> <tr key={u.id} className="border-b border-[#FFFFFF] hover:bg-[#151515] transition-colors">
<td className="px-4 py-3 text-[#555] font-mono text-xs">{u.wp_id}</td> <td className="px-4 py-3 text-[#555] font-mono text-xs">{u.wp_id}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className="text-white font-medium">{u.wp_username}</span> <span className="text-white font-medium">{u.wp_username}</span>

View File

@@ -44,7 +44,7 @@ export default function AdvertisePage() {
{/* Hero */} {/* Hero */}
{/* DO NOT OVERRIDE — Advertise page hero section */} {/* DO NOT OVERRIDE — Advertise page hero section */}
<div className="bg-[#0d1520] border-b border-[#412402] py-12"> <div className="bg-[#0d1520] border-b border-[#DCE6F2] py-12">
<div className="max-w-container mx-auto px-4 text-center"> <div className="max-w-container mx-auto px-4 text-center">
<span className="section-label mb-3 inline-block">Advertise</span> <span className="section-label mb-3 inline-block">Advertise</span>
<h1 className="font-heading text-white text-4xl font-bold mb-4">Reach Pro AV &amp; Live Production Professionals</h1> <h1 className="font-heading text-white text-4xl font-bold mb-4">Reach Pro AV &amp; Live Production Professionals</h1>
@@ -64,12 +64,12 @@ export default function AdvertisePage() {
<section className="mb-12"> <section className="mb-12">
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<span className="section-label">Our Audience</span> <span className="section-label">Our Audience</span>
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{stats.map((stat) => ( {stats.map((stat) => (
<div key={stat.label} className="bg-[#111] border border-[#222] p-5 text-center"> <div key={stat.label} className="bg-[#111] border border-[#222] p-5 text-center">
<div className="font-heading text-[#F0A623] text-2xl font-bold mb-1">{stat.value}</div> <div className="font-heading text-[#1D4ED8] text-2xl font-bold mb-1">{stat.value}</div>
<div className="font-body text-[#777] text-xs uppercase tracking-wide">{stat.label}</div> <div className="font-body text-[#777] text-xs uppercase tracking-wide">{stat.label}</div>
</div> </div>
))} ))}
@@ -80,25 +80,25 @@ export default function AdvertisePage() {
<section className="mb-12"> <section className="mb-12">
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<span className="section-label">Ad Placements</span> <span className="section-label">Ad Placements</span>
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-sm font-body"> <table className="w-full text-sm font-body">
<thead> <thead>
<tr className="border-b border-[#222]"> <tr className="border-b border-[#222]">
<th className="text-left py-3 px-4 text-[#F0A623] font-bold text-xs uppercase tracking-wider">Placement</th> <th className="text-left py-3 px-4 text-[#1D4ED8] font-bold text-xs uppercase tracking-wider">Placement</th>
<th className="text-left py-3 px-4 text-[#F0A623] font-bold text-xs uppercase tracking-wider">Dimensions</th> <th className="text-left py-3 px-4 text-[#1D4ED8] font-bold text-xs uppercase tracking-wider">Dimensions</th>
<th className="text-left py-3 px-4 text-[#F0A623] font-bold text-xs uppercase tracking-wider">Location</th> <th className="text-left py-3 px-4 text-[#1D4ED8] font-bold text-xs uppercase tracking-wider">Location</th>
<th className="text-left py-3 px-4 text-[#F0A623] font-bold text-xs uppercase tracking-wider">Rate</th> <th className="text-left py-3 px-4 text-[#1D4ED8] font-bold text-xs uppercase tracking-wider">Rate</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{adZones.map((zone, i) => ( {adZones.map((zone, i) => (
<tr key={zone.name} className={`border-b border-[#231d18] ${i % 2 === 0 ? "bg-[#1c1815]" : "bg-[#111]"}`}> <tr key={zone.name} className={`border-b border-[#FFFFFF] ${i % 2 === 0 ? "bg-[#F8FAFC]" : "bg-[#111]"}`}>
<td className="py-3 px-4 text-[#e0e0e0] font-medium">{zone.name}</td> <td className="py-3 px-4 text-[#e0e0e0] font-medium">{zone.name}</td>
<td className="py-3 px-4 text-[#888] font-mono text-xs">{zone.dimensions}</td> <td className="py-3 px-4 text-[#888] font-mono text-xs">{zone.dimensions}</td>
<td className="py-3 px-4 text-[#777]">{zone.placement}</td> <td className="py-3 px-4 text-[#777]">{zone.placement}</td>
<td className="py-3 px-4 text-[#F0A623] font-medium">{zone.cpm}</td> <td className="py-3 px-4 text-[#1D4ED8] font-medium">{zone.cpm}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@@ -114,7 +114,7 @@ export default function AdvertisePage() {
<section id="contact" className="mb-12"> <section id="contact" className="mb-12">
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<span className="section-label">Contact Sales</span> <span className="section-label">Contact Sales</span>
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Contact info */} {/* Contact info */}
@@ -123,23 +123,23 @@ export default function AdvertisePage() {
<h3 className="font-heading text-[#e0e0e0] text-lg font-bold mb-4">Sales Contact</h3> <h3 className="font-heading text-[#e0e0e0] text-lg font-bold mb-4">Sales Contact</h3>
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="font-body text-[#F0A623] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p> <p className="font-body text-[#1D4ED8] text-xs font-bold uppercase tracking-wider mb-1">Advertising Sales</p>
<p className="font-body text-[#e0e0e0] font-medium">Jeff Victor</p> <p className="font-body text-[#e0e0e0] font-medium">Jeff Victor</p>
<a href="mailto:jeff@avbeat.com" className="font-body text-[#F0A623] text-sm hover:underline"> <a href="mailto:jeff@avbeat.com" className="font-body text-[#1D4ED8] text-sm hover:underline">
jeff@avbeat.com jeff@avbeat.com
</a> </a>
</div> </div>
<div className="border-t border-[#222] pt-4"> <div className="border-t border-[#222] pt-4">
<p className="font-body text-[#F0A623] text-xs font-bold uppercase tracking-wider mb-1">Publication</p> <p className="font-body text-[#1D4ED8] text-xs font-bold uppercase tracking-wider mb-1">Publication</p>
<p className="font-body text-[#777] text-sm">AV Beat</p> <p className="font-body text-[#777] text-sm">AV Beat</p>
<p className="font-body text-[#777] text-sm">Relevant Media Properties</p> <p className="font-body text-[#777] text-sm">Relevant Media Properties</p>
</div> </div>
<div className="border-t border-[#222] pt-4"> <div className="border-t border-[#222] pt-4">
<p className="font-body text-[#F0A623] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p> <p className="font-body text-[#1D4ED8] text-xs font-bold uppercase tracking-wider mb-2">What We Offer</p>
<ul className="space-y-1.5"> <ul className="space-y-1.5">
{["Display advertising (web)", "Newsletter sponsorships", "Sponsored content", "Event coverage partnerships", "Podcast sponsorships"].map((item) => ( {["Display advertising (web)", "Newsletter sponsorships", "Sponsored content", "Event coverage partnerships", "Podcast sponsorships"].map((item) => (
<li key={item} className="flex items-start gap-2 text-[#777] text-sm font-body"> <li key={item} className="flex items-start gap-2 text-[#777] text-sm font-body">
<span className="text-[#F0A623] mt-0.5"></span> <span className="text-[#1D4ED8] mt-0.5"></span>
{item} {item}
</li> </li>
))} ))}
@@ -152,16 +152,16 @@ export default function AdvertisePage() {
{/* Form */} {/* Form */}
<div className="lg:col-span-8"> <div className="lg:col-span-8">
{submitted ? ( {submitted ? (
<div className="bg-[#111] border border-[#F0A623] p-8 text-center"> <div className="bg-[#111] border border-[#1D4ED8] p-8 text-center">
<div className="w-12 h-12 rounded-full bg-[#F0A623]/20 flex items-center justify-center mx-auto mb-4"> <div className="w-12 h-12 rounded-full bg-[#1D4ED8]/20 flex items-center justify-center mx-auto mb-4">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M5 12l4 4 10-10" stroke="#F0A623" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M5 12l4 4 10-10" stroke="#1D4ED8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<h3 className="font-heading text-[#e0e0e0] text-xl font-bold mb-2">Message Sent!</h3> <h3 className="font-heading text-[#e0e0e0] text-xl font-bold mb-2">Message Sent!</h3>
<p className="font-body text-[#777] text-sm"> <p className="font-body text-[#777] text-sm">
Thanks for reaching out. Jeff will be in touch within one business day at{" "} Thanks for reaching out. Jeff will be in touch within one business day at{" "}
<a href="mailto:jeff@avbeat.com" className="text-[#F0A623] hover:underline">jeff@avbeat.com</a>. <a href="mailto:jeff@avbeat.com" className="text-[#1D4ED8] hover:underline">jeff@avbeat.com</a>.
</p> </p>
</div> </div>
) : ( ) : (
@@ -219,7 +219,7 @@ export default function AdvertisePage() {
<select <select
value={formData.budget} value={formData.budget}
onChange={(e) => setFormData({ ...formData, budget: e.target.value })} onChange={(e) => setFormData({ ...formData, budget: e.target.value })}
className="search-input w-full py-2.5 px-3 text-sm bg-[#231d18]"> className="search-input w-full py-2.5 px-3 text-sm bg-[#FFFFFF]">
<option value="">Select a range...</option> <option value="">Select a range...</option>
<option value="under-5k">Under $5,000</option> <option value="under-5k">Under $5,000</option>
<option value="5k-15k">$5,000 $15,000</option> <option value="5k-15k">$5,000 $15,000</option>
@@ -245,7 +245,7 @@ export default function AdvertisePage() {
{loading ? "Sending..." : "Send Inquiry"} {loading ? "Sending..." : "Send Inquiry"}
</button> </button>
<p className="text-[#555] text-xs font-body"> <p className="text-[#555] text-xs font-body">
Or email directly: <a href="mailto:jeff@avbeat.com" className="text-[#F0A623] hover:underline">jeff@avbeat.com</a> Or email directly: <a href="mailto:jeff@avbeat.com" className="text-[#1D4ED8] hover:underline">jeff@avbeat.com</a>
</p> </p>
</form> </form>
)} )}

View File

@@ -19,7 +19,7 @@ export async function POST(request: NextRequest) {
html: ` html: `
<div style="font-family:Arial,sans-serif;max-width:600px;background:#0a0a0a;color:#e5e5e5;padding:32px;border-radius:8px;"> <div style="font-family:Arial,sans-serif;max-width:600px;background:#0a0a0a;color:#e5e5e5;padding:32px;border-radius:8px;">
<div style="margin-bottom:24px;"> <div style="margin-bottom:24px;">
<h1 style="color:#F0A623;font-size:20px;margin:0 0 4px;">Relevant Media Properties</h1> <h1 style="color:#1D4ED8;font-size:20px;margin:0 0 4px;">Relevant Media Properties</h1>
<p style="color:#555;font-size:12px;margin:0;">AV Beat · AV Beat — Company Story Engine</p> <p style="color:#555;font-size:12px;margin:0;">AV Beat · AV Beat — Company Story Engine</p>
</div> </div>
<h2 style="font-size:18px;color:#fff;margin:0 0 12px;">New Company Stories Queued for Review</h2> <h2 style="font-size:18px;color:#fff;margin:0 0 12px;">New Company Stories Queued for Review</h2>
@@ -32,11 +32,11 @@ export async function POST(request: NextRequest) {
${moreText} ${moreText}
</div> </div>
<a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com'}/admin/company-tracker" <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com'}/admin/company-tracker"
style="display:inline-block;background:#F0A623;color:#fff;text-decoration:none;padding:10px 20px;border-radius:6px;font-size:14px;font-weight:bold;"> style="display:inline-block;background:#1D4ED8;color:#fff;text-decoration:none;padding:10px 20px;border-radius:6px;font-size:14px;font-weight:bold;">
Review Story Queue → Review Story Queue →
</a> </a>
<p style="color:#444;font-size:12px;margin-top:24px;"> <p style="color:#444;font-size:12px;margin-top:24px;">
Manage settings at <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com'}/admin/company-tracker" style="color:#F0A623;">Company Tracker</a> Manage settings at <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://avbeat.com'}/admin/company-tracker" style="color:#1D4ED8;">Company Tracker</a>
</p> </p>
</div> </div>
`, `,

View File

@@ -25,7 +25,7 @@ export async function POST(req: NextRequest) {
}); });
const noteHtml = note const noteHtml = note
? `<div style="background:#1a1a1a;border-left:3px solid #F0A623;padding:12px 16px;margin-bottom:20px;border-radius:2px;"> ? `<div style="background:#1a1a1a;border-left:3px solid #1D4ED8;padding:12px 16px;margin-bottom:20px;border-radius:2px;">
<p style="margin:0;color:#aaa;font-size:14px;font-style:italic;">"${note}"</p> <p style="margin:0;color:#aaa;font-size:14px;font-style:italic;">"${note}"</p>
</div>` </div>`
: ""; : "";
@@ -45,7 +45,7 @@ export async function POST(req: NextRequest) {
<!-- Header --> <!-- Header -->
<tr> <tr>
<td style="background:#0a0a0a;border-bottom:1px solid #1a1a1a;padding:20px 28px;"> <td style="background:#0a0a0a;border-bottom:1px solid #1a1a1a;padding:20px 28px;">
<p style="margin:0;color:#F0A623;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;">AV Beat</p> <p style="margin:0;color:#1D4ED8;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;">AV Beat</p>
</td> </td>
</tr> </tr>
<!-- Body --> <!-- Body -->
@@ -53,10 +53,10 @@ export async function POST(req: NextRequest) {
<td style="padding:28px;"> <td style="padding:28px;">
<p style="margin:0 0 8px;color:#555;font-size:11px;text-transform:uppercase;letter-spacing:2px;">Someone shared an article with you</p> <p style="margin:0 0 8px;color:#555;font-size:11px;text-transform:uppercase;letter-spacing:2px;">Someone shared an article with you</p>
${noteHtml} ${noteHtml}
<p style="margin:0 0 6px;color:#F0A623;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:2px;">NEWS</p> <p style="margin:0 0 6px;color:#1D4ED8;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:2px;">NEWS</p>
<h1 style="margin:0 0 12px;color:#f0f0f0;font-size:22px;font-weight:700;line-height:1.3;">${articleTitle}</h1> <h1 style="margin:0 0 12px;color:#f0f0f0;font-size:22px;font-weight:700;line-height:1.3;">${articleTitle}</h1>
<p style="margin:0 0 24px;color:#999;font-size:14px;line-height:1.6;">${articleExcerpt}</p> <p style="margin:0 0 24px;color:#999;font-size:14px;line-height:1.6;">${articleExcerpt}</p>
<a href="${articleUrl}" style="display:inline-block;background:#F0A623;color:#fff;text-decoration:none;font-size:13px;font-weight:700;padding:12px 24px;border-radius:2px;letter-spacing:1px;text-transform:uppercase;">Read Article</a> <a href="${articleUrl}" style="display:inline-block;background:#1D4ED8;color:#fff;text-decoration:none;font-size:13px;font-weight:700;padding:12px 24px;border-radius:2px;letter-spacing:1px;text-transform:uppercase;">Read Article</a>
</td> </td>
</tr> </tr>
<!-- Footer --> <!-- Footer -->

View File

@@ -86,7 +86,7 @@ async function sendWelcome(email: string, topics?: string[]) {
const topicsBlock = const topicsBlock =
topics && topics.length > 0 topics && topics.length > 0
? `<p style="color:#aaa;font-size:14px;line-height:1.5;margin:14px 0 0;">You'll receive coverage on: <strong style="color:#F0A623;">${topics.join(', ')}</strong></p>` ? `<p style="color:#aaa;font-size:14px;line-height:1.5;margin:14px 0 0;">You'll receive coverage on: <strong style="color:#1D4ED8;">${topics.join(', ')}</strong></p>`
: ''; : '';
await transporter.sendMail({ await transporter.sendMail({
@@ -96,24 +96,24 @@ async function sendWelcome(email: string, topics?: string[]) {
html: ` html: `
<!DOCTYPE html> <!DOCTYPE html>
<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>Welcome to AV Beat</title></head> <html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" /><title>Welcome to AV Beat</title></head>
<body style="margin:0;padding:0;background:#1c1815;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;"> <body style="margin:0;padding:0;background:#F8FAFC;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#1c1815;padding:40px 16px;"> <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#F8FAFC;padding:40px 16px;">
<tr><td align="center"> <tr><td align="center">
<table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;background:#231d18;border:1px solid #3a322b;border-radius:10px;overflow:hidden;"> <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="max-width:600px;background:#FFFFFF;border:1px solid #DCE6F2;border-radius:10px;overflow:hidden;">
<tr><td style="padding:32px 36px 8px;"> <tr><td style="padding:32px 36px 8px;">
<div style="font-family:'Courier New',monospace;font-size:10px;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:#F0A623;">AV Beat Newsletter</div> <div style="font-family:'Courier New',monospace;font-size:10px;font-weight:700;letter-spacing:0.22em;text-transform:uppercase;color:#1D4ED8;">AV Beat Newsletter</div>
<h1 style="margin:14px 0 8px;font-size:24px;line-height:1.2;color:#ffffff;font-weight:800;">You're in</h1> <h1 style="margin:14px 0 8px;font-size:24px;line-height:1.2;color:#ffffff;font-weight:800;">You're in</h1>
<p style="margin:0;color:#FBEFE0;font-size:15px;line-height:1.55;">Thanks for subscribing to <strong style="color:#ffffff;">AV Beat</strong> — the digital platform for pro AV, live production, and display tech professionals.</p> <p style="margin:0;color:#0F172A;font-size:15px;line-height:1.55;">Thanks for subscribing to <strong style="color:#ffffff;">AV Beat</strong> — the digital platform for pro AV, live production, and display tech professionals.</p>
${topicsBlock} ${topicsBlock}
</td></tr> </td></tr>
<tr><td style="padding:18px 36px 8px;"> <tr><td style="padding:18px 36px 8px;">
<p style="margin:0;color:#C9BBA8;font-size:14px;line-height:1.6;">You'll get one editorial dispatch a week — industry news, product launches, InfoComm/ISE coverage, and deep-dives. No spam, no sales calls.</p> <p style="margin:0;color:#475569;font-size:14px;line-height:1.6;">You'll get one editorial dispatch a week — industry news, product launches, InfoComm/ISE coverage, and deep-dives. No spam, no sales calls.</p>
</td></tr> </td></tr>
<tr><td style="padding:18px 36px 26px;" align="center"> <tr><td style="padding:18px 36px 26px;" align="center">
<a href="${siteUrl}/news" style="display:inline-block;background:#F0A623;color:#ffffff;text-decoration:none;padding:11px 22px;border-radius:6px;font-weight:700;font-size:13px;letter-spacing:0.05em;text-transform:uppercase;">Browse AV Industry News</a> <a href="${siteUrl}/news" style="display:inline-block;background:#1D4ED8;color:#ffffff;text-decoration:none;padding:11px 22px;border-radius:6px;font-weight:700;font-size:13px;letter-spacing:0.05em;text-transform:uppercase;">Browse AV Industry News</a>
</td></tr> </td></tr>
<tr><td style="padding:18px 36px 28px;border-top:1px solid #3a322b;"> <tr><td style="padding:18px 36px 28px;border-top:1px solid #DCE6F2;">
<p style="margin:0;color:#8a7e6e;font-size:11px;line-height:1.5;">You're receiving this because you signed up at <a href="${siteUrl}" style="color:#F0A623;text-decoration:none;">avbeat.com</a>. <a href="${siteUrl}/unsubscribe?email=${encodeURIComponent(email)}" style="color:#C9BBA8;">Unsubscribe anytime</a>.</p> <p style="margin:0;color:#8a7e6e;font-size:11px;line-height:1.5;">You're receiving this because you signed up at <a href="${siteUrl}" style="color:#1D4ED8;text-decoration:none;">avbeat.com</a>. <a href="${siteUrl}/unsubscribe?email=${encodeURIComponent(email)}" style="color:#475569;">Unsubscribe anytime</a>.</p>
</td></tr> </td></tr>
</table> </table>
</td></tr> </td></tr>

View File

@@ -124,37 +124,37 @@ export async function POST(req: NextRequest) {
const articlesHtml = articles const articlesHtml = articles
.map( .map(
(a: { title: string; excerpt?: string; url?: string; image?: string; category?: string }) => ` (a: { title: string; excerpt?: string; url?: string; image?: string; category?: string }) => `
<div style="border-bottom:1px solid #3a322b;padding:20px 0;"> <div style="border-bottom:1px solid #DCE6F2;padding:20px 0;">
${a.category ? `<span style="color:#F0A623;font-size:11px;font-weight:bold;text-transform:uppercase;letter-spacing:1px;">${a.category}</span>` : ''} ${a.category ? `<span style="color:#1D4ED8;font-size:11px;font-weight:bold;text-transform:uppercase;letter-spacing:1px;">${a.category}</span>` : ''}
<h2 style="color:#e0e0e0;font-size:18px;margin:8px 0 6px;"> <h2 style="color:#e0e0e0;font-size:18px;margin:8px 0 6px;">
${a.url ? `<a href="${siteUrl}${a.url}" style="color:#e0e0e0;text-decoration:none;">${a.title}</a>` : a.title} ${a.url ? `<a href="${siteUrl}${a.url}" style="color:#e0e0e0;text-decoration:none;">${a.title}</a>` : a.title}
</h2> </h2>
${a.excerpt ? `<p style="color:#888;font-size:14px;margin:0 0 10px;">${a.excerpt}</p>` : ''} ${a.excerpt ? `<p style="color:#888;font-size:14px;margin:0 0 10px;">${a.excerpt}</p>` : ''}
${a.url ? `<a href="${siteUrl}${a.url}" style="color:#F0A623;font-size:13px;text-decoration:none;">Read more →</a>` : ''} ${a.url ? `<a href="${siteUrl}${a.url}" style="color:#1D4ED8;font-size:13px;text-decoration:none;">Read more →</a>` : ''}
</div> </div>
` `
) )
.join(''); .join('');
const emailHtml = ` const emailHtml = `
<div style="background:#1c1815;padding:0;font-family:sans-serif;max-width:600px;margin:0 auto;"> <div style="background:#F8FAFC;padding:0;font-family:sans-serif;max-width:600px;margin:0 auto;">
<!-- Header --> <!-- Header -->
<div style="background:#111;border-bottom:2px solid #F0A623;padding:24px 32px;"> <div style="background:#111;border-bottom:2px solid #1D4ED8;padding:24px 32px;">
<h1 style="color:#F0A623;font-size:22px;margin:0;font-weight:bold;">AV Beat</h1> <h1 style="color:#1D4ED8;font-size:22px;margin:0;font-weight:bold;">AV Beat</h1>
<p style="color:#555;font-size:12px;margin:4px 0 0;">News & Intelligence for Pro AV, Live Production & Display Tech</p> <p style="color:#555;font-size:12px;margin:4px 0 0;">News & Intelligence for Pro AV, Live Production & Display Tech</p>
</div> </div>
<!-- Body --> <!-- Body -->
<div style="padding:32px;"> <div style="padding:32px;">
${previewText ? `<p style="color:#aaa;font-size:15px;margin:0 0 24px;">${previewText}</p>` : ''} ${previewText ? `<p style="color:#aaa;font-size:15px;margin:0 0 24px;">${previewText}</p>` : ''}
${customMessage ? `<div style="background:#231d18;border-left:3px solid #F0A623;padding:16px;margin-bottom:24px;"><p style="color:#e0e0e0;font-size:14px;margin:0;">${customMessage}</p></div>` : ''} ${customMessage ? `<div style="background:#FFFFFF;border-left:3px solid #1D4ED8;padding:16px;margin-bottom:24px;"><p style="color:#e0e0e0;font-size:14px;margin:0;">${customMessage}</p></div>` : ''}
${articlesHtml} ${articlesHtml}
</div> </div>
<!-- Footer --> <!-- Footer -->
<div style="background:#1c1815;padding:24px 32px;border-top:1px solid #3a322b;"> <div style="background:#F8FAFC;padding:24px 32px;border-top:1px solid #DCE6F2;">
<p style="color:#555;font-size:12px;margin:0;">You're receiving this because you subscribed at avbeat.com.</p> <p style="color:#555;font-size:12px;margin:0;">You're receiving this because you subscribed at avbeat.com.</p>
<p style="color:#555;font-size:12px;margin:8px 0 0;"> <p style="color:#555;font-size:12px;margin:8px 0 0;">
<a href="${siteUrl}" style="color:#F0A623;">Visit AV Beat</a> · <a href="${siteUrl}" style="color:#1D4ED8;">Visit AV Beat</a> ·
<a href="${siteUrl}/unsubscribe?email={{email}}" style="color:#F0A623;">Unsubscribe</a> <a href="${siteUrl}/unsubscribe?email={{email}}" style="color:#1D4ED8;">Unsubscribe</a>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -38,7 +38,7 @@ export async function POST(req: NextRequest) {
subject_prefix: subject_prefix || '', subject_prefix: subject_prefix || '',
header_text: header_text || '', header_text: header_text || '',
footer_text: footer_text || '', footer_text: footer_text || '',
accent_color: accent_color || '#F0A623', accent_color: accent_color || '#1D4ED8',
article_blocks: article_blocks || [], article_blocks: article_blocks || [],
is_preset: false, is_preset: false,
}) })

View File

@@ -227,11 +227,11 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{/* Breadcrumb navigation */} {/* Breadcrumb navigation */}
{/* DO NOT OVERRIDE — article breadcrumb and back navigation */} {/* DO NOT OVERRIDE — article breadcrumb and back navigation */}
<div className="bg-[#1c1815] border-b border-[#231d18]"> <div className="bg-[#F8FAFC] border-b border-[#FFFFFF]">
<div className="max-w-container mx-auto px-4 py-2.5 flex items-center gap-2 text-xs font-body text-[#555]"> <div className="max-w-container mx-auto px-4 py-2.5 flex items-center gap-2 text-xs font-body text-[#555]">
<Link href="/home-page" className="hover:text-[#F0A623] transition-colors">Home</Link> <Link href="/home-page" className="hover:text-[#1D4ED8] transition-colors">Home</Link>
<span>/</span> <span>/</span>
<Link href={sectionHref} className="hover:text-[#F0A623] transition-colors">{sectionLabel}</Link> <Link href={sectionHref} className="hover:text-[#1D4ED8] transition-colors">{sectionLabel}</Link>
<span>/</span> <span>/</span>
<span className="text-[#888] truncate max-w-[300px]">{article.title}</span> <span className="text-[#888] truncate max-w-[300px]">{article.title}</span>
</div> </div>
@@ -245,7 +245,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{/* Article Header */} {/* Article Header */}
<div className="mb-8"> <div className="mb-8">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<Link href={sectionHref} className="text-xs font-bold uppercase tracking-widest text-[#F0A623] hover:underline"> <Link href={sectionHref} className="text-xs font-bold uppercase tracking-widest text-[#1D4ED8] hover:underline">
{article.category} {article.category}
</Link> </Link>
<span className="text-xs text-[#777]">{article.date}</span> <span className="text-xs text-[#777]">{article.date}</span>
@@ -271,7 +271,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<div> <div>
<Link <Link
href={`/authors/${article.authorSlug}`} href={`/authors/${article.authorSlug}`}
className="font-heading font-bold text-sm hover:text-[#F0A623] transition-colors text-[#e0e0e0]"> className="font-heading font-bold text-sm hover:text-[#1D4ED8] transition-colors text-[#e0e0e0]">
{article.author} {article.author}
</Link> </Link>
<p className="font-body text-xs text-[#777]">{article.authorTitle}</p> <p className="font-body text-xs text-[#777]">{article.authorTitle}</p>
@@ -286,7 +286,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<> <>
<a href={`mailto:?subject=${encodeURIComponent(article.title)}&body=${encodeURIComponent(articleUrl)}`} <a href={`mailto:?subject=${encodeURIComponent(article.title)}&body=${encodeURIComponent(articleUrl)}`}
aria-label="Share via email" title="Email" aria-label="Share via email" title="Email"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#F0A623] hover:bg-[#231d18] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#F0A623]"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#1D4ED8] hover:bg-[#FFFFFF] transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#1D4ED8]">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<rect x="2" y="4" width="20" height="16" rx="2" /> <rect x="2" y="4" width="20" height="16" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /> <path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
@@ -294,39 +294,39 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
</a> </a>
<a href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(articleUrl)}`} <a href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(articleUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on LinkedIn" title="LinkedIn" target="_blank" rel="noopener noreferrer" aria-label="Share on LinkedIn" title="LinkedIn"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#0a66c2] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#0a66c2] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 0h-14C2.24 0 0 2.24 0 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM8 19H5V8h3v11zM6.5 6.73a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5zM20 19h-3v-5.6c0-3.37-4-3.12-4 0V19h-3V8h3v1.77c1.4-2.59 7-2.78 7 2.48V19z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 0h-14C2.24 0 0 2.24 0 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM8 19H5V8h3v11zM6.5 6.73a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5zM20 19h-3v-5.6c0-3.37-4-3.12-4 0V19h-3V8h3v1.77c1.4-2.59 7-2.78 7 2.48V19z"/></svg>
</a> </a>
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=BroadcastBeat`} <a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(articleUrl)}&text=${encodeURIComponent(article.title)}&via=BroadcastBeat`}
target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)" target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-white hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-white hover:bg-[#FFFFFF] transition-colors">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a> </a>
<a href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(articleUrl)}`} <a href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(articleUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook" title="Facebook" target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook" title="Facebook"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#1877f2] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#1877f2] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12S0 5.446 0 12.073C0 18.062 4.388 23.027 10.125 23.927v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12S0 5.446 0 12.073C0 18.062 4.388 23.027 10.125 23.927v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a> </a>
<a href={`https://www.reddit.com/submit?url=${encodeURIComponent(articleUrl)}&title=${encodeURIComponent(article.title)}`} <a href={`https://www.reddit.com/submit?url=${encodeURIComponent(articleUrl)}&title=${encodeURIComponent(article.title)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on Reddit" title="Reddit" target="_blank" rel="noopener noreferrer" aria-label="Share on Reddit" title="Reddit"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#ff4500] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#ff4500] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/></svg>
</a> </a>
<a href={`https://api.whatsapp.com/send?text=${encodeURIComponent(article.title + " " + articleUrl)}`} <a href={`https://api.whatsapp.com/send?text=${encodeURIComponent(article.title + " " + articleUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on WhatsApp" title="WhatsApp" target="_blank" rel="noopener noreferrer" aria-label="Share on WhatsApp" title="WhatsApp"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#25d366] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#25d366] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.945C.16 5.335 5.495 0 12.05 0a11.817 11.817 0 0 1 8.413 3.488 11.824 11.824 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.711.306 1.265.489 1.697.626.713.226 1.362.194 1.875.118.572-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.945C.16 5.335 5.495 0 12.05 0a11.817 11.817 0 0 1 8.413 3.488 11.824 11.824 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.711.306 1.265.489 1.697.626.713.226 1.362.194 1.875.118.572-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413z"/></svg>
</a> </a>
<button type="button" <button type="button"
onClick={async () => { try { await navigator.clipboard.writeText(articleUrl); } catch {} }} onClick={async () => { try { await navigator.clipboard.writeText(articleUrl); } catch {} }}
aria-label="Copy article link" title="Copy link" aria-label="Copy article link" title="Copy link"
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#F0A623] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#888] hover:text-[#1D4ED8] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/> <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg> </svg>
</button> </button>
<span className="w-px h-5 bg-[#3a322b] mx-1" aria-hidden="true" /> <span className="w-px h-5 bg-[#DCE6F2] mx-1" aria-hidden="true" />
</> </>
); );
})()} })()}
@@ -334,7 +334,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
onClick={handleSaveToReadingList} onClick={handleSaveToReadingList}
disabled={savingState !== "idle"} disabled={savingState !== "idle"}
aria-label={isSaved ? "Remove from reading list" : "Save to reading list"} aria-label={isSaved ? "Remove from reading list" : "Save to reading list"}
className="text-xs font-bold uppercase tracking-widest text-[#F0A623] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#F0A623]"> className="text-xs font-bold uppercase tracking-widest text-[#1D4ED8] hover:text-blue-300 disabled:opacity-50 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#1D4ED8]">
{savingState === "saving" ? "Saving..." : savingState === "removing" ? "Removing..." : isSaved ? "✓ Saved" : "Save"} {savingState === "saving" ? "Saving..." : savingState === "removing" ? "Removing..." : isSaved ? "✓ Saved" : "Save"}
</button> </button>
</div> </div>
@@ -368,7 +368,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{article.tags.map((tag) => ( {article.tags.map((tag) => (
<span <span
key={tag} key={tag}
className="px-3 py-1 text-xs font-body bg-[#231d18] border border-[#3a322b] text-[#888] hover:border-[#F0A623] hover:text-[#F0A623] transition-colors rounded-sm cursor-default"> className="px-3 py-1 text-xs font-body bg-[#FFFFFF] border border-[#DCE6F2] text-[#888] hover:border-[#1D4ED8] hover:text-[#1D4ED8] transition-colors rounded-sm cursor-default">
{tag} {tag}
</span> </span>
))} ))}
@@ -380,7 +380,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<div className="pt-6 border-t border-[#222]"> <div className="pt-6 border-t border-[#222]">
<Link <Link
href={sectionHref} href={sectionHref}
className="inline-flex items-center gap-2 text-[#F0A623] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#F0A623]"> className="inline-flex items-center gap-2 text-[#1D4ED8] font-body text-sm hover:underline focus:outline-none focus-visible:ring-1 focus-visible:ring-[#1D4ED8]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<polyline points="15 18 9 12 15 6" /> <polyline points="15 18 9 12 15 6" />
</svg> </svg>
@@ -398,7 +398,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
{/* Related Articles */} {/* Related Articles */}
{/* DO NOT OVERRIDE — related articles sidebar */} {/* DO NOT OVERRIDE — related articles sidebar */}
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#F0A623] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#1D4ED8] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Related Articles Related Articles
</h3> </h3>
<div className="space-y-4"> <div className="space-y-4">
@@ -417,10 +417,10 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
/> />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-[#F0A623] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5"> <p className="text-[#1D4ED8] font-body text-[9px] font-bold uppercase tracking-wider mb-0.5">
{related.category} {related.category}
</p> </p>
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#F0A623] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#1D4ED8] transition-colors line-clamp-2 leading-snug">
{related.title} {related.title}
</p> </p>
</div> </div>
@@ -435,19 +435,19 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
recently-active threads so the box is never empty. */} recently-active threads so the box is never empty. */}
{relatedForumThreads.length > 0 && ( {relatedForumThreads.length > 0 && (
<div className="bg-[#111] border border-[#222] p-5"> <div className="bg-[#111] border border-[#222] p-5">
<h3 className="font-body font-bold text-xs text-[#F0A623] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]"> <h3 className="font-body font-bold text-xs text-[#1D4ED8] uppercase tracking-widest mb-4 pb-2 border-b border-[#222]">
Related Forum Posts Related Forum Posts
</h3> </h3>
<ul className="space-y-3.5"> <ul className="space-y-3.5">
{relatedForumThreads.map((t) => ( {relatedForumThreads.map((t) => (
<li key={t.id}> <li key={t.id}>
<Link href={`/forum/thread/${t.id}`} className="group block"> <Link href={`/forum/thread/${t.id}`} className="group block">
<p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#F0A623] transition-colors line-clamp-2 leading-snug"> <p className="font-heading text-[#e0e0e0] text-xs font-bold group-hover:text-[#1D4ED8] transition-colors line-clamp-2 leading-snug">
{t.title} {t.title}
</p> </p>
<p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]"> <p className="flex items-center gap-2 mt-1 text-[10px] text-[#666]">
{t.category_name && ( {t.category_name && (
<span className="text-[#F0A623] font-body font-bold uppercase tracking-wider"> <span className="text-[#1D4ED8] font-body font-bold uppercase tracking-wider">
{t.category_name} {t.category_name}
</span> </span>
)} )}
@@ -461,7 +461,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
</ul> </ul>
<Link <Link
href="/forum" href="/forum"
className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#F0A623] hover:underline font-body font-semibold uppercase tracking-wider" className="block mt-4 pt-3 border-t border-[#222] text-[11px] text-[#1D4ED8] hover:underline font-body font-semibold uppercase tracking-wider"
> >
Browse all forum threads Browse all forum threads
</Link> </Link>
@@ -479,7 +479,7 @@ export default function ArticleDetailClient({ article, relatedArticles, relatedF
<div <div
role="status" role="status"
aria-live="polite" aria-live="polite"
className="fixed bottom-4 right-4 bg-[#231d18] border border-[#F0A623] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50"> className="fixed bottom-4 right-4 bg-[#FFFFFF] border border-[#1D4ED8] text-white px-4 py-3 rounded-sm text-sm font-body shadow-lg z-50">
{saveToast} {saveToast}
</div> </div>
)} )}

View File

@@ -118,7 +118,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
}; };
return ( return (
<section className="mt-10 pt-8 border-t border-[#3a322b]"> <section className="mt-10 pt-8 border-t border-[#DCE6F2]">
{/* Section Header */} {/* Section Header */}
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<span className="section-label">Comments</span> <span className="section-label">Comments</span>
@@ -127,15 +127,15 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
{comments.length} {comments.length === 1 ? "comment" : "comments"} {comments.length} {comments.length === 1 ? "comment" : "comments"}
</span> </span>
)} )}
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
{/* Comment Form */} {/* Comment Form */}
{user ? ( {user ? (
<form onSubmit={handleSubmit} className="mb-8"> <form onSubmit={handleSubmit} className="mb-8">
<div className="flex gap-3"> <div className="flex gap-3">
<div className="w-8 h-8 rounded-full bg-[#412402] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5"> <div className="w-8 h-8 rounded-full bg-[#DCE6F2] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="font-body text-[10px] font-bold text-[#F0A623]"> <span className="font-body text-[10px] font-bold text-[#1D4ED8]">
{getInitials(user.user_metadata?.full_name || user.email || "U")} {getInitials(user.user_metadata?.full_name || user.email || "U")}
</span> </span>
</div> </div>
@@ -146,10 +146,10 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
placeholder="Share your thoughts on this article..." placeholder="Share your thoughts on this article..."
rows={3} rows={3}
maxLength={1000} maxLength={1000}
className="w-full bg-[#231d18] border border-[#3a322b] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623] transition-colors placeholder-[#444] resize-none" className="w-full bg-[#FFFFFF] border border-[#DCE6F2] text-[#e0e0e0] font-body text-sm px-3 py-2.5 rounded-sm focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8] transition-colors placeholder-[#444] resize-none"
/> />
{error && ( {error && (
<p className="font-body text-xs text-[#D85A30] mt-1.5">{error}</p> <p className="font-body text-xs text-[#1D4ED8] mt-1.5">{error}</p>
)} )}
<div className="flex items-center justify-between mt-2"> <div className="flex items-center justify-between mt-2">
<span className="font-body text-[11px] text-[#555]"> <span className="font-body text-[11px] text-[#555]">
@@ -158,7 +158,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
<button <button
type="submit" type="submit"
disabled={submitting || !newComment.trim()} disabled={submitting || !newComment.trim()}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"> className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]">
{submitting ? "Posting..." : "Post Comment"} {submitting ? "Posting..." : "Post Comment"}
</button> </button>
</div> </div>
@@ -166,14 +166,14 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
</div> </div>
</form> </form>
) : ( ) : (
<div className="mb-8 bg-[#0d1520] border border-[#412402] rounded-sm p-5 flex items-center justify-between gap-4"> <div className="mb-8 bg-[#0d1520] border border-[#DCE6F2] rounded-sm p-5 flex items-center justify-between gap-4">
<div> <div>
<p className="font-body text-sm font-bold text-[#e0e0e0] mb-0.5">Join the conversation</p> <p className="font-body text-sm font-bold text-[#e0e0e0] mb-0.5">Join the conversation</p>
<p className="font-body text-xs text-[#666]">Sign in to post a comment on this article.</p> <p className="font-body text-xs text-[#666]">Sign in to post a comment on this article.</p>
</div> </div>
<Link <Link
href="/login" href="/login"
className="flex-shrink-0 bg-[#F0A623] hover:bg-[#BA7517] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors"> className="flex-shrink-0 bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body text-xs font-bold uppercase tracking-wide px-5 py-2 rounded-sm transition-colors">
Sign In Sign In
</Link> </Link>
</div> </div>
@@ -208,7 +208,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
const isOwn = user?.id === comment.user_id; const isOwn = user?.id === comment.user_id;
return ( return (
<div key={comment.id} className="flex gap-3 group"> <div key={comment.id} className="flex gap-3 group">
<div className="w-8 h-8 rounded-full bg-[#231d18] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5 overflow-hidden"> <div className="w-8 h-8 rounded-full bg-[#FFFFFF] border border-[#2a3a50] flex items-center justify-center flex-shrink-0 mt-0.5 overflow-hidden">
{comment.user_profiles?.avatar_url ? ( {comment.user_profiles?.avatar_url ? (
<img <img
src={comment.user_profiles.avatar_url} src={comment.user_profiles.avatar_url}
@@ -216,7 +216,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
) : ( ) : (
<span className="font-body text-[10px] font-bold text-[#F0A623]"> <span className="font-body text-[10px] font-bold text-[#1D4ED8]">
{getInitials(name)} {getInitials(name)}
</span> </span>
)} )}
@@ -230,7 +230,7 @@ export default function CommentsSection({ articleSlug }: CommentsSectionProps) {
onClick={() => handleDelete(comment.id)} onClick={() => handleDelete(comment.id)}
disabled={deletingId === comment.id} disabled={deletingId === comment.id}
aria-label="Delete comment" aria-label="Delete comment"
className="ml-auto opacity-0 group-hover:opacity-100 font-body text-[11px] text-[#555] hover:text-[#D85A30] transition-all disabled:opacity-30"> className="ml-auto opacity-0 group-hover:opacity-100 font-body text-[11px] text-[#555] hover:text-[#1D4ED8] transition-all disabled:opacity-30">
{deletingId === comment.id ? "Deleting..." : "Delete"} {deletingId === comment.id ? "Deleting..." : "Delete"}
</button> </button>
)} )}

View File

@@ -55,7 +55,7 @@ export default function ClientReadout() {
}, []); }, []);
return ( return (
<pre style={{ padding: 20, fontFamily: "monospace", whiteSpace: "pre-wrap", background: "#231d18", color: "#bbb", borderTop: "1px solid #333" }}> <pre style={{ padding: 20, fontFamily: "monospace", whiteSpace: "pre-wrap", background: "#FFFFFF", color: "#bbb", borderTop: "1px solid #333" }}>
{JSON.stringify(out, null, 2)} {JSON.stringify(out, null, 2)}
</pre> </pre>
); );

View File

@@ -113,7 +113,7 @@ export default function AuthorProfilePage() {
return ( return (
<> <>
<Header /> <Header />
<main className="bg-[#1c1815] min-h-screen"> <main className="bg-[#F8FAFC] min-h-screen">
<aside className="float-right ml-6 mb-6 hidden lg:block max-w-[300px]"><SidebarAdStack /></aside> <aside className="float-right ml-6 mb-6 hidden lg:block max-w-[300px]"><SidebarAdStack /></aside>
{/* Cover Image */} {/* Cover Image */}
<div className="relative h-40 md:h-52 overflow-hidden"> <div className="relative h-40 md:h-52 overflow-hidden">
@@ -126,14 +126,14 @@ export default function AuthorProfilePage() {
className="w-full h-full object-cover opacity-40" className="w-full h-full object-cover opacity-40"
/> />
)} )}
<div className="absolute inset-0 bg-gradient-to-t from-[#1c1815] via-[#1c1815]/60 to-transparent" /> <div className="absolute inset-0 bg-gradient-to-t from-[#F8FAFC] via-[#F8FAFC]/60 to-transparent" />
</div> </div>
<div className="max-w-container mx-auto px-4"> <div className="max-w-container mx-auto px-4">
{/* Author Header */} {/* Author Header */}
<div className="relative -mt-16 md:-mt-20 mb-6"> <div className="relative -mt-16 md:-mt-20 mb-6">
<div className="flex flex-col sm:flex-row sm:items-end gap-4"> <div className="flex flex-col sm:flex-row sm:items-end gap-4">
<div className="w-24 h-24 md:w-28 md:h-28 rounded-full overflow-hidden border-4 border-[#1c1815] flex-shrink-0 bg-[#231d18]"> <div className="w-24 h-24 md:w-28 md:h-28 rounded-full overflow-hidden border-4 border-[#F8FAFC] flex-shrink-0 bg-[#FFFFFF]">
{profile && ( {profile && (
<AppImage <AppImage
src={profile.avatar_url} src={profile.avatar_url}
@@ -145,10 +145,10 @@ export default function AuthorProfilePage() {
)} )}
</div> </div>
<div className="pb-1 flex-1"> <div className="pb-1 flex-1">
<h1 className="font-heading text-2xl md:text-3xl font-bold text-[#FBEFE0] leading-tight"> <h1 className="font-heading text-2xl md:text-3xl font-bold text-[#0F172A] leading-tight">
{loading ? "Loading…" : profile?.display_name || "Author"} {loading ? "Loading…" : profile?.display_name || "Author"}
</h1> </h1>
<p className="font-body text-sm text-[#F0A623] mt-0.5"> <p className="font-body text-sm text-[#1D4ED8] mt-0.5">
{profile?.role || "Contributor"} {profile?.role || "Contributor"}
</p> </p>
<div className="flex flex-wrap items-center gap-3 mt-2"> <div className="flex flex-wrap items-center gap-3 mt-2">
@@ -180,7 +180,7 @@ export default function AuthorProfilePage() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label={`${profile.display_name} on Twitter`} aria-label={`${profile.display_name} on Twitter`}
className="w-8 h-8 flex items-center justify-center bg-[#231d18] border border-[#3a322b] hover:border-[#F0A623] hover:text-[#F0A623] text-[#888] transition-colors rounded-sm" className="w-8 h-8 flex items-center justify-center bg-[#FFFFFF] border border-[#DCE6F2] hover:border-[#1D4ED8] hover:text-[#1D4ED8] text-[#888] transition-colors rounded-sm"
> >
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
@@ -193,7 +193,7 @@ export default function AuthorProfilePage() {
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label={`${profile.display_name} on LinkedIn`} aria-label={`${profile.display_name} on LinkedIn`}
className="w-8 h-8 flex items-center justify-center bg-[#231d18] border border-[#3a322b] hover:border-[#F0A623] hover:text-[#F0A623] text-[#888] transition-colors rounded-sm" className="w-8 h-8 flex items-center justify-center bg-[#FFFFFF] border border-[#DCE6F2] hover:border-[#1D4ED8] hover:text-[#1D4ED8] text-[#888] transition-colors rounded-sm"
> >
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"> <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" /> <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
@@ -204,7 +204,7 @@ export default function AuthorProfilePage() {
<a <a
href={`mailto:${profile.email}`} href={`mailto:${profile.email}`}
aria-label={`Email ${profile.display_name}`} aria-label={`Email ${profile.display_name}`}
className="w-8 h-8 flex items-center justify-center bg-[#231d18] border border-[#3a322b] hover:border-[#F0A623] hover:text-[#F0A623] text-[#888] transition-colors rounded-sm" className="w-8 h-8 flex items-center justify-center bg-[#FFFFFF] border border-[#DCE6F2] hover:border-[#1D4ED8] hover:text-[#1D4ED8] text-[#888] transition-colors rounded-sm"
> >
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /> <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
@@ -223,7 +223,7 @@ export default function AuthorProfilePage() {
{profile.specialties.map((spec) => ( {profile.specialties.map((spec) => (
<span <span
key={spec} key={spec}
className="font-body text-xs text-[#F0A623] bg-[#412402]/30 border border-[#412402] px-2.5 py-1 rounded-sm" className="font-body text-xs text-[#1D4ED8] bg-[#DCE6F2]/30 border border-[#DCE6F2] px-2.5 py-1 rounded-sm"
> >
{spec} {spec}
</span> </span>
@@ -232,12 +232,12 @@ export default function AuthorProfilePage() {
)} )}
{/* Tabs */} {/* Tabs */}
<div className="flex gap-0 border-b border-[#3a322b] mb-6"> <div className="flex gap-0 border-b border-[#DCE6F2] mb-6">
<button <button
onClick={() => setActiveTab("articles")} onClick={() => setActiveTab("articles")}
className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${ className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${
activeTab === "articles" activeTab === "articles"
? "border-[#F0A623] text-[#F0A623]" ? "border-[#1D4ED8] text-[#1D4ED8]"
: "border-transparent text-[#666] hover:text-[#aaa]" : "border-transparent text-[#666] hover:text-[#aaa]"
}`} }`}
> >
@@ -247,7 +247,7 @@ export default function AuthorProfilePage() {
onClick={() => setActiveTab("about")} onClick={() => setActiveTab("about")}
className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${ className={`font-body text-xs font-bold uppercase tracking-wider px-4 py-3 border-b-2 transition-colors ${
activeTab === "about" activeTab === "about"
? "border-[#F0A623] text-[#F0A623]" ? "border-[#1D4ED8] text-[#1D4ED8]"
: "border-transparent text-[#666] hover:text-[#aaa]" : "border-transparent text-[#666] hover:text-[#aaa]"
}`} }`}
> >
@@ -271,7 +271,7 @@ export default function AuthorProfilePage() {
<Link <Link
key={article.slug} key={article.slug}
href={`/articles/${article.slug}`} href={`/articles/${article.slug}`}
className="group bg-[#231d18] border border-[#3a322b] hover:border-[#F0A623] transition-colors rounded-sm overflow-hidden article-card" className="group bg-[#FFFFFF] border border-[#DCE6F2] hover:border-[#1D4ED8] transition-colors rounded-sm overflow-hidden article-card"
> >
<div className="img-zoom h-44 overflow-hidden"> <div className="img-zoom h-44 overflow-hidden">
<AppImage <AppImage
@@ -284,13 +284,13 @@ export default function AuthorProfilePage() {
</div> </div>
<div className="p-4"> <div className="p-4">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#F0A623]"> <span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#1D4ED8]">
{article.category} {article.category}
</span> </span>
<span className="font-body text-[10px] text-[#555]">·</span> <span className="font-body text-[10px] text-[#555]">·</span>
<span className="font-body text-[10px] text-[#555]">{article.readTime}</span> <span className="font-body text-[10px] text-[#555]">{article.readTime}</span>
</div> </div>
<h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#F0A623] transition-colors leading-snug mb-2 line-clamp-2"> <h3 className="font-heading text-sm font-bold text-[#e0e0e0] group-hover:text-[#1D4ED8] transition-colors leading-snug mb-2 line-clamp-2">
{article.title} {article.title}
</h3> </h3>
<p className="font-body text-xs text-[#777] leading-relaxed line-clamp-2 mb-3"> <p className="font-body text-xs text-[#777] leading-relaxed line-clamp-2 mb-3">
@@ -308,7 +308,7 @@ export default function AuthorProfilePage() {
{/* About tab */} {/* About tab */}
{activeTab === "about" && profile && ( {activeTab === "about" && profile && (
<div className="max-w-3xl pb-10 space-y-5"> <div className="max-w-3xl pb-10 space-y-5">
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<p className="section-label mb-4">Biography</p> <p className="section-label mb-4">Biography</p>
{profile.bio ? ( {profile.bio ? (
<p className="font-body text-sm text-[#aaaaaa] leading-relaxed whitespace-pre-line"> <p className="font-body text-sm text-[#aaaaaa] leading-relaxed whitespace-pre-line">
@@ -328,7 +328,7 @@ export default function AuthorProfilePage() {
profile.address_line1 || profile.address_line1 ||
profile.city || profile.city ||
profile.country) && ( profile.country) && (
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<p className="section-label mb-4">Contact</p> <p className="section-label mb-4">Contact</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5"> <div className="grid grid-cols-1 md:grid-cols-2 gap-5">
{addressLines.length > 0 && ( {addressLines.length > 0 && (
@@ -353,7 +353,7 @@ export default function AuthorProfilePage() {
</p> </p>
<a <a
href={`tel:${profile.phone.replace(/\s+/g, "")}`} href={`tel:${profile.phone.replace(/\s+/g, "")}`}
className="font-body text-sm text-[#F0A623] hover:underline" className="font-body text-sm text-[#1D4ED8] hover:underline"
> >
{profile.phone} {profile.phone}
</a> </a>
@@ -366,7 +366,7 @@ export default function AuthorProfilePage() {
</p> </p>
<a <a
href={`mailto:${profile.email}`} href={`mailto:${profile.email}`}
className="font-body text-sm text-[#F0A623] hover:underline break-all" className="font-body text-sm text-[#1D4ED8] hover:underline break-all"
> >
{profile.email} {profile.email}
</a> </a>
@@ -381,7 +381,7 @@ export default function AuthorProfilePage() {
href={profile.website} href={profile.website}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-body text-sm text-[#F0A623] hover:underline break-all" className="font-body text-sm text-[#1D4ED8] hover:underline break-all"
> >
{profile.website.replace(/^https?:\/\//, "").replace(/\/$/, "")} {profile.website.replace(/^https?:\/\//, "").replace(/\/$/, "")}
</a> </a>
@@ -392,7 +392,7 @@ export default function AuthorProfilePage() {
)} )}
{/* Profile facts */} {/* Profile facts */}
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6">
<p className="section-label mb-4">Profile</p> <p className="section-label mb-4">Profile</p>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
{locationLine && ( {locationLine && (

View File

@@ -283,7 +283,7 @@ export default function ContributorDashboard() {
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors text-left ${ className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition-colors text-left ${
view === item.id view === item.id
? 'bg-[#c9a84c]/10 text-[#c9a84c] border border-[#c9a84c]/20' ? 'bg-[#c9a84c]/10 text-[#c9a84c] border border-[#c9a84c]/20'
: 'text-[#888] hover:text-white hover:bg-[#231d18]' : 'text-[#888] hover:text-white hover:bg-[#FFFFFF]'
}`} }`}
> >
<span className="text-base w-5 text-center">{item.icon}</span> <span className="text-base w-5 text-center">{item.icon}</span>
@@ -339,7 +339,7 @@ export default function ContributorDashboard() {
<button onClick={() => setView('new-post')} className="px-5 py-2.5 bg-[#c9a84c] text-black font-semibold rounded-lg text-sm hover:bg-[#b8973b] transition-colors"> <button onClick={() => setView('new-post')} className="px-5 py-2.5 bg-[#c9a84c] text-black font-semibold rounded-lg text-sm hover:bg-[#b8973b] transition-colors">
+ New Post + New Post
</button> </button>
<button onClick={() => setView('my-posts')} className="px-5 py-2.5 bg-[#231d18] border border-[#333] text-white rounded-lg text-sm hover:bg-[#222] transition-colors"> <button onClick={() => setView('my-posts')} className="px-5 py-2.5 bg-[#FFFFFF] border border-[#333] text-white rounded-lg text-sm hover:bg-[#222] transition-colors">
View My Posts View My Posts
</button> </button>
</div> </div>
@@ -453,7 +453,7 @@ export default function ContributorDashboard() {
{submittingPost ? 'Submitting...' : profile?.role === 'contributor' ? 'Submit for Review' : 'Publish Post'} {submittingPost ? 'Submitting...' : profile?.role === 'contributor' ? 'Submit for Review' : 'Publish Post'}
</button> </button>
<button type="button" onClick={() => setView('overview')} <button type="button" onClick={() => setView('overview')}
className="px-5 py-2.5 bg-[#231d18] border border-[#333] text-[#888] rounded-lg text-sm hover:text-white hover:bg-[#222] transition-colors"> className="px-5 py-2.5 bg-[#FFFFFF] border border-[#333] text-[#888] rounded-lg text-sm hover:text-white hover:bg-[#222] transition-colors">
Cancel Cancel
</button> </button>
</div> </div>

View File

@@ -214,7 +214,7 @@ function renderMarkdown(text: string): string {
.replace(/^# (.+)$/gm, '<h1 class="text-xl font-bold text-white mt-5 mb-2">$1</h1>') .replace(/^# (.+)$/gm, '<h1 class="text-xl font-bold text-white mt-5 mb-2">$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>') .replace(/\*\*(.+?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>') .replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`([^`]+)`/g, '<code class="bg-[#231d18] text-[#F0A623] px-1 py-0.5 rounded text-sm font-mono">$1</code>') .replace(/`([^`]+)`/g, '<code class="bg-[#FFFFFF] text-[#1D4ED8] px-1 py-0.5 rounded text-sm font-mono">$1</code>')
.replace(/^- (.+)$/gm, '<li class="ml-4 list-disc text-[#aaa]">$1</li>') .replace(/^- (.+)$/gm, '<li class="ml-4 list-disc text-[#aaa]">$1</li>')
.replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal text-[#aaa]">$1</li>') .replace(/^\d+\. (.+)$/gm, '<li class="ml-4 list-decimal text-[#aaa]">$1</li>')
.replace(/\n\n/g, '</p><p class="mb-3 text-[#ccc] leading-relaxed">') .replace(/\n\n/g, '</p><p class="mb-3 text-[#ccc] leading-relaxed">')
@@ -294,7 +294,7 @@ function DraftPreviewModal({ content, title, penName, siteId, onClose, onSave }:
</button> </button>
<button <button
onClick={() => onSave(true)} onClick={() => onSave(true)}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
> >
Open in Editor Open in Editor
</button> </button>
@@ -353,7 +353,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
onClick={() => { setSelectedVariant(v.prompt); setCustomPrompt(''); }} onClick={() => { setSelectedVariant(v.prompt); setCustomPrompt(''); }}
className={`text-left text-xs px-3 py-2 rounded border transition-colors ${ className={`text-left text-xs px-3 py-2 rounded border transition-colors ${
selectedVariant === v.prompt selectedVariant === v.prompt
? 'border-[#F0A623] bg-[#412402] text-white' ? 'border-[#1D4ED8] bg-[#DCE6F2] text-white'
: 'border-[#1e2a3a] text-[#aaa] hover:border-[#2a3a50] hover:text-white hover:bg-[#161f2e]' : 'border-[#1e2a3a] text-[#aaa] hover:border-[#2a3a50] hover:text-white hover:bg-[#161f2e]'
}`} }`}
> >
@@ -370,7 +370,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
onChange={e => { setCustomPrompt(e.target.value); setSelectedVariant(null); }} onChange={e => { setCustomPrompt(e.target.value); setSelectedVariant(null); }}
placeholder="e.g. Rewrite this focusing on the European market impact..." placeholder="e.g. Rewrite this focusing on the European market impact..."
rows={3} rows={3}
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#333] resize-none" className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#333] resize-none"
/> />
</div> </div>
@@ -386,7 +386,7 @@ function RegenerateModal({ onClose, onRegenerate }: RegenerateModalProps) {
<button onClick={onClose} className="text-[#555] hover:text-[#aaa] text-sm transition-colors">Cancel</button> <button onClick={onClose} className="text-[#555] hover:text-[#aaa] text-sm transition-colors">Cancel</button>
<button <button
onClick={handleSubmit} onClick={handleSubmit}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
> >
Regenerate Regenerate
</button> </button>
@@ -418,7 +418,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
const found = PEN_NAME_PROFILES.find(p => p.name === e.target.value); const found = PEN_NAME_PROFILES.find(p => p.name === e.target.value);
if (found) side === 'left' ? setLeftPen(found) : setRightPen(found); if (found) side === 'left' ? setLeftPen(found) : setRightPen(found);
}} }}
className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded px-2 py-1.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#1e2a3a] text-white text-sm rounded px-2 py-1.5 focus:outline-none focus:border-[#1D4ED8]"
> >
{PEN_NAME_PROFILES.map(p => ( {PEN_NAME_PROFILES.map(p => (
<option key={p.name} value={p.name}>{p.name} ({p.site})</option> <option key={p.name} value={p.name}>{p.name} ({p.site})</option>
@@ -434,7 +434,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
</span> </span>
<span className="text-white font-semibold text-sm">{profile.name}</span> <span className="text-white font-semibold text-sm">{profile.name}</span>
</div> </div>
<p className="text-[#F0A623] text-xs">{profile.title}</p> <p className="text-[#1D4ED8] text-xs">{profile.title}</p>
</div> </div>
<div> <div>
@@ -458,7 +458,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
<div> <div>
<p className="text-[#555] text-[10px] uppercase tracking-wider font-bold mb-1">Example Lead</p> <p className="text-[#555] text-[10px] uppercase tracking-wider font-bold mb-1">Example Lead</p>
<p className="text-[#aaa] text-xs italic leading-relaxed border-l-2 border-[#F0A623]/40 pl-3">"{profile.exampleLead}"</p> <p className="text-[#aaa] text-xs italic leading-relaxed border-l-2 border-[#1D4ED8]/40 pl-3">"{profile.exampleLead}"</p>
</div> </div>
<button <button
@@ -485,7 +485,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
<button <button
key={f} key={f}
onClick={() => setFilterSite(f)} onClick={() => setFilterSite(f)}
className={`text-xs px-2.5 py-1 rounded transition-colors ${filterSite === f ? 'bg-[#F0A623] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`} className={`text-xs px-2.5 py-1 rounded transition-colors ${filterSite === f ? 'bg-[#1D4ED8] text-white' : 'bg-[#1e2a3a] text-[#aaa] hover:text-white'}`}
> >
{f === 'ALL' ? 'All Writers' : f === 'BB' ? 'AV Beat' : 'AV Beat'} {f === 'ALL' ? 'All Writers' : f === 'BB' ? 'AV Beat' : 'AV Beat'}
</button> </button>
@@ -514,7 +514,7 @@ function StyleGuidesPanel({ onClose, onSelectPenName }: StyleGuidesPanelProps) {
> >
<div className="flex items-center gap-2 mb-0.5"> <div className="flex items-center gap-2 mb-0.5">
<span className={`text-[9px] font-bold px-1 rounded ${p.site === 'AV' ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}>{p.site}</span> <span className={`text-[9px] font-bold px-1 rounded ${p.site === 'AV' ? 'bg-orange-900/40 text-orange-400' : 'bg-blue-900/40 text-blue-400'}`}>{p.site}</span>
<span className="text-white text-xs font-medium group-hover:text-[#F0A623] transition-colors">{p.name}</span> <span className="text-white text-xs font-medium group-hover:text-[#1D4ED8] transition-colors">{p.name}</span>
</div> </div>
<p className="text-[#555] text-[10px]">{p.title}</p> <p className="text-[#555] text-[10px]">{p.title}</p>
</button> </button>
@@ -560,14 +560,14 @@ function ResponseHistorySidebar({ messages, onJumpTo, onClose }: ResponseHistory
className="w-full text-left p-3 rounded border border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e] transition-colors group" className="w-full text-left p-3 rounded border border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e] transition-colors group"
> >
<div className="flex items-center justify-between mb-1"> <div className="flex items-center justify-between mb-1">
<span className="text-[10px] text-[#F0A623] font-bold">Response #{idx + 1}</span> <span className="text-[10px] text-[#1D4ED8] font-bold">Response #{idx + 1}</span>
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${msg.provider === 'ollama' ? 'bg-green-500' : 'bg-yellow-500'}`} /> <span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${msg.provider === 'ollama' ? 'bg-green-500' : 'bg-yellow-500'}`} />
</div> </div>
<p className="text-[#aaa] text-xs line-clamp-3 leading-relaxed">{preview}</p> <p className="text-[#aaa] text-xs line-clamp-3 leading-relaxed">{preview}</p>
<div className="flex items-center gap-3 mt-2"> <div className="flex items-center gap-3 mt-2">
<span className="text-[10px] text-[#444]">{msg.timestamp.toLocaleTimeString()}</span> <span className="text-[10px] text-[#444]">{msg.timestamp.toLocaleTimeString()}</span>
<span className="text-[10px] text-[#444]">{wordCount} words</span> <span className="text-[10px] text-[#444]">{wordCount} words</span>
<span className="text-[10px] text-[#F0A623] opacity-0 group-hover:opacity-100 transition-opacity">Jump </span> <span className="text-[10px] text-[#1D4ED8] opacity-0 group-hover:opacity-100 transition-opacity">Jump </span>
</div> </div>
</button> </button>
); );
@@ -927,7 +927,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
{/* Header */} {/* Header */}
<div className="bg-[#111827] border-b border-[#1e2a3a] px-4 py-3 flex items-center justify-between"> <div className="bg-[#111827] border-b border-[#1e2a3a] px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/admin" className="text-[#555] hover:text-[#F0A623] text-sm transition-colors"> Dashboard</Link> <Link href="/admin" className="text-[#555] hover:text-[#1D4ED8] text-sm transition-colors"> Dashboard</Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
<h1 className="text-white font-bold text-sm tracking-wide">AI Console</h1> <h1 className="text-white font-bold text-sm tracking-wide">AI Console</h1>
</div> </div>
@@ -943,18 +943,18 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onClick={() => setShowHistorySidebar(v => !v)} onClick={() => setShowHistorySidebar(v => !v)}
className={`text-xs px-2.5 py-1.5 rounded border transition-colors ${ className={`text-xs px-2.5 py-1.5 rounded border transition-colors ${
showHistorySidebar showHistorySidebar
? 'bg-[#412402] border-[#F0A623]/40 text-[#F0A623]' ? 'bg-[#DCE6F2] border-[#1D4ED8]/40 text-[#1D4ED8]'
: 'bg-[#1e2a3a] border-[#2a3a50] text-[#555] hover:text-[#aaa]' : 'bg-[#1e2a3a] border-[#2a3a50] text-[#555] hover:text-[#aaa]'
}`} }`}
title="Toggle response history sidebar" title="Toggle response history sidebar"
> >
History History
{assistantMessageCount > 0 && ( {assistantMessageCount > 0 && (
<span className="ml-1.5 bg-[#F0A623] text-white text-[9px] font-bold px-1 py-0.5 rounded-full">{assistantMessageCount}</span> <span className="ml-1.5 bg-[#1D4ED8] text-white text-[9px] font-bold px-1 py-0.5 rounded-full">{assistantMessageCount}</span>
)} )}
</button> </button>
<Link href="/dashboard/ai-console/health" className="text-[#555] hover:text-[#F0A623] text-xs transition-colors">Health</Link> <Link href="/dashboard/ai-console/health" className="text-[#555] hover:text-[#1D4ED8] text-xs transition-colors">Health</Link>
<Link href="/dashboard/ai-console/settings" className="text-[#555] hover:text-[#F0A623] text-xs transition-colors">Settings</Link> <Link href="/dashboard/ai-console/settings" className="text-[#555] hover:text-[#1D4ED8] text-xs transition-colors">Settings</Link>
<button onClick={handleNewSession} className="text-xs bg-[#1e2a3a] hover:bg-[#253347] text-[#aaa] px-3 py-1.5 rounded transition-colors"> <button onClick={handleNewSession} className="text-xs bg-[#1e2a3a] hover:bg-[#253347] text-[#aaa] px-3 py-1.5 rounded transition-colors">
+ New Session + New Session
</button> </button>
@@ -1002,7 +1002,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
key={session.id} key={session.id}
className={`group p-2.5 rounded border cursor-pointer transition-colors ${ className={`group p-2.5 rounded border cursor-pointer transition-colors ${
currentSessionId === session.id currentSessionId === session.id
? 'border-[#F0A623]/40 bg-[#1e2a3a]' ? 'border-[#1D4ED8]/40 bg-[#1e2a3a]'
: 'border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e]' : 'border-[#1e2a3a] hover:border-[#2a3a50] hover:bg-[#161f2e]'
}`} }`}
> >
@@ -1046,11 +1046,11 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onClick={() => { onClick={() => {
fetch('/api/ai/console?action=ping').then(r => r.json()).then(setAiStatus).catch(() => {}); fetch('/api/ai/console?action=ping').then(r => r.json()).then(setAiStatus).catch(() => {});
}} }}
className="text-[10px] text-[#555] hover:text-[#F0A623] transition-colors" className="text-[10px] text-[#555] hover:text-[#1D4ED8] transition-colors"
> >
Refresh Refresh
</button> </button>
<Link href="/dashboard/ai-console/health" className="text-[10px] text-[#555] hover:text-[#F0A623] transition-colors"> <Link href="/dashboard/ai-console/health" className="text-[10px] text-[#555] hover:text-[#1D4ED8] transition-colors">
Health Dashboard Health Dashboard
</Link> </Link>
</div> </div>
@@ -1066,7 +1066,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<select <select
value={selectedSite} value={selectedSite}
onChange={e => setSelectedSite(Number(e.target.value) as 1 | 2)} onChange={e => setSelectedSite(Number(e.target.value) as 1 | 2)}
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#F0A623]" className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value={1}>AV Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
@@ -1077,7 +1077,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<select <select
value={selectedPenName} value={selectedPenName}
onChange={e => setSelectedPenName(e.target.value)} onChange={e => setSelectedPenName(e.target.value)}
className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#F0A623]" className="bg-[#1e2a3a] border border-[#2a3a50] text-white text-xs rounded px-2 py-1 focus:outline-none focus:border-[#1D4ED8]"
> >
{penNames.map(name => ( {penNames.map(name => (
<option key={name} value={name}>{name}</option> <option key={name} value={name}>{name}</option>
@@ -1086,7 +1086,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
</div> </div>
<button <button
onClick={() => setShowStyleGuides(true)} onClick={() => setShowStyleGuides(true)}
className="text-[10px] text-[#F0A623] hover:text-[#F0A623] transition-colors ml-auto" className="text-[10px] text-[#1D4ED8] hover:text-[#1D4ED8] transition-colors ml-auto"
> >
View style guide View style guide
</button> </button>
@@ -1123,7 +1123,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
</div> </div>
)} )}
<div className={`rounded-lg px-4 py-3 ${ <div className={`rounded-lg px-4 py-3 ${
msg.role === 'user' ?'bg-[#412402] text-white text-sm' :'bg-[#111827] border border-[#1e2a3a] text-[#ccc] text-sm' msg.role === 'user' ?'bg-[#DCE6F2] text-white text-sm' :'bg-[#111827] border border-[#1e2a3a] text-[#ccc] text-sm'
}`}> }`}>
{msg.role === 'user' ? ( {msg.role === 'user' ? (
<p className="whitespace-pre-wrap">{msg.content}</p> <p className="whitespace-pre-wrap">{msg.content}</p>
@@ -1151,7 +1151,7 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
</button> </button>
<button <button
onClick={() => setShowDraftPreview(true)} onClick={() => setShowDraftPreview(true)}
className="text-[10px] text-[#F0A623] hover:text-[#F0A623] bg-[#111827] border border-[#F0A623]/30 px-2 py-1 rounded transition-colors font-medium" className="text-[10px] text-[#1D4ED8] hover:text-[#1D4ED8] bg-[#111827] border border-[#1D4ED8]/30 px-2 py-1 rounded transition-colors font-medium"
> >
Preview Draft Preview Draft
</button> </button>
@@ -1195,9 +1195,9 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<div className="flex justify-start"> <div className="flex justify-start">
<div className="bg-[#111827] border border-[#1e2a3a] rounded-lg px-4 py-3"> <div className="bg-[#111827] border border-[#1e2a3a] rounded-lg px-4 py-3">
<div className="flex gap-1"> <div className="flex gap-1">
<div className="w-1.5 h-1.5 bg-[#F0A623] rounded-full animate-bounce" style={{ animationDelay: '0ms' }} /> <div className="w-1.5 h-1.5 bg-[#1D4ED8] rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<div className="w-1.5 h-1.5 bg-[#F0A623] rounded-full animate-bounce" style={{ animationDelay: '150ms' }} /> <div className="w-1.5 h-1.5 bg-[#1D4ED8] rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-1.5 h-1.5 bg-[#F0A623] rounded-full animate-bounce" style={{ animationDelay: '300ms' }} /> <div className="w-1.5 h-1.5 bg-[#1D4ED8] rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div> </div>
</div> </div>
</div> </div>
@@ -1225,13 +1225,13 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
onChange={e => setUrlInput(e.target.value)} onChange={e => setUrlInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleFetchUrl()} onKeyDown={e => e.key === 'Enter' && handleFetchUrl()}
placeholder="https://example.com/press-release" placeholder="https://example.com/press-release"
className="flex-1 bg-[#1e2a3a] border border-[#2a3a50] text-white text-sm rounded px-3 py-2 focus:outline-none focus:border-[#F0A623] placeholder-[#444]" className="flex-1 bg-[#1e2a3a] border border-[#2a3a50] text-white text-sm rounded px-3 py-2 focus:outline-none focus:border-[#1D4ED8] placeholder-[#444]"
autoFocus autoFocus
/> />
<button <button
onClick={handleFetchUrl} onClick={handleFetchUrl}
disabled={fetchingUrl} disabled={fetchingUrl}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white text-xs px-3 py-2 rounded transition-colors disabled:opacity-50" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white text-xs px-3 py-2 rounded transition-colors disabled:opacity-50"
> >
{fetchingUrl ? 'Fetching...' : 'Add'} {fetchingUrl ? 'Fetching...' : 'Add'}
</button> </button>
@@ -1244,19 +1244,19 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
<div className="flex gap-2 mb-2"> <div className="flex gap-2 mb-2">
<button <button
onClick={() => setShowUrlInput(!showUrlInput)} onClick={() => setShowUrlInput(!showUrlInput)}
className="text-xs text-[#555] hover:text-[#F0A623] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors" className="text-xs text-[#555] hover:text-[#1D4ED8] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
> >
+ Add URL + Add URL
</button> </button>
<button <button
onClick={() => setInput(prev => prev + '\n\n[Paste story content here for reference]')} onClick={() => setInput(prev => prev + '\n\n[Paste story content here for reference]')}
className="text-xs text-[#555] hover:text-[#F0A623] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors" className="text-xs text-[#555] hover:text-[#1D4ED8] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
> >
+ Add Story + Add Story
</button> </button>
<button <button
onClick={() => setInput(prev => prev + `\n\nWrite in the style of ${selectedPenName}.`)} onClick={() => setInput(prev => prev + `\n\nWrite in the style of ${selectedPenName}.`)}
className="text-xs text-[#555] hover:text-[#F0A623] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors" className="text-xs text-[#555] hover:text-[#1D4ED8] bg-[#111827] border border-[#1e2a3a] px-2 py-1 rounded transition-colors"
> >
+ Add Style + Add Style
</button> </button>
@@ -1274,13 +1274,13 @@ Respond conversationally and helpfully. If asked to write a story, write the ful
}} }}
placeholder="Ask the AI to write a story, generate headlines, translate content..." placeholder="Ask the AI to write a story, generate headlines, translate content..."
rows={3} rows={3}
className="flex-1 bg-[#111827] border border-[#1e2a3a] text-white text-sm rounded-lg px-4 py-3 focus:outline-none focus:border-[#F0A623] placeholder-[#333] resize-none" className="flex-1 bg-[#111827] border border-[#1e2a3a] text-white text-sm rounded-lg px-4 py-3 focus:outline-none focus:border-[#1D4ED8] placeholder-[#333] resize-none"
/> />
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<button <button
onClick={() => handleSend()} onClick={() => handleSend()}
disabled={isStreaming || !input.trim()} disabled={isStreaming || !input.trim()}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium flex-1" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium flex-1"
> >
{isStreaming ? ( {isStreaming ? (
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">

View File

@@ -117,7 +117,7 @@ export default function AIConsoleSettingsPage() {
if (loading || loadingSettings) { if (loading || loadingSettings) {
return ( return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center"> <div className="min-h-screen bg-[#0d1117] flex items-center justify-center">
<div className="w-6 h-6 border-2 border-[#F0A623]/30 border-t-[#F0A623] rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8]/30 border-t-[#1D4ED8] rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -127,14 +127,14 @@ export default function AIConsoleSettingsPage() {
{/* Header */} {/* Header */}
<div className="bg-[#111827] border-b border-[#1e2a3a] px-6 py-4 flex items-center justify-between"> <div className="bg-[#111827] border-b border-[#1e2a3a] px-6 py-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/dashboard/ai-console" className="text-[#555] hover:text-[#F0A623] text-sm transition-colors"> AI Console</Link> <Link href="/dashboard/ai-console" className="text-[#555] hover:text-[#1D4ED8] text-sm transition-colors"> AI Console</Link>
<span className="text-[#333]">/</span> <span className="text-[#333]">/</span>
<h1 className="text-white font-bold text-sm">AI Console Settings</h1> <h1 className="text-white font-bold text-sm">AI Console Settings</h1>
</div> </div>
<button <button
onClick={handleSave} onClick={handleSave}
disabled={saving} disabled={saving}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white text-sm px-4 py-2 rounded-lg transition-colors font-medium"
> >
{saving ? 'Saving...' : 'Save Settings'} {saving ? 'Saving...' : 'Save Settings'}
</button> </button>
@@ -177,7 +177,7 @@ export default function AIConsoleSettingsPage() {
value={settings.ollama_endpoint} value={settings.ollama_endpoint}
onChange={e => update('ollama_endpoint', e.target.value)} onChange={e => update('ollama_endpoint', e.target.value)}
placeholder="http://192.168.1.x:11434 or https://your-tunnel.ngrok.io" placeholder="http://192.168.1.x:11434 or https://your-tunnel.ngrok.io"
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623] placeholder-[#333]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8] placeholder-[#333]"
/> />
<p className="text-[#444] text-[10px] mt-1"> <p className="text-[#444] text-[10px] mt-1">
Use direct IP if on the same network, or a tunnel URL (ngrok, Cloudflare Tunnel, Tailscale) for remote access. Use direct IP if on the same network, or a tunnel URL (ngrok, Cloudflare Tunnel, Tailscale) for remote access.
@@ -212,7 +212,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.ollama_model_default} value={settings.ollama_model_default}
onChange={e => update('ollama_model_default', e.target.value)} onChange={e => update('ollama_model_default', e.target.value)}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value="">Auto (largest available)</option> <option value="">Auto (largest available)</option>
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)} {connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
@@ -223,7 +223,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.ollama_model_translation} value={settings.ollama_model_translation}
onChange={e => update('ollama_model_translation', e.target.value)} onChange={e => update('ollama_model_translation', e.target.value)}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value="">Same as default</option> <option value="">Same as default</option>
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)} {connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
@@ -234,7 +234,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.ollama_model_background} value={settings.ollama_model_background}
onChange={e => update('ollama_model_background', e.target.value)} onChange={e => update('ollama_model_background', e.target.value)}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value="">Same as default</option> <option value="">Same as default</option>
{connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)} {connectionResult.models.map(m => <option key={m} value={m}>{m}</option>)}
@@ -250,7 +250,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('auto_failover', !settings.auto_failover)} onClick={() => update('auto_failover', !settings.auto_failover)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.auto_failover ? 'bg-[#F0A623]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.auto_failover ? 'bg-[#1D4ED8]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.auto_failover ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.auto_failover ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -263,7 +263,7 @@ export default function AIConsoleSettingsPage() {
value={settings.failover_timeout_seconds} value={settings.failover_timeout_seconds}
onChange={e => update('failover_timeout_seconds', Number(e.target.value))} onChange={e => update('failover_timeout_seconds', Number(e.target.value))}
min={3} max={30} min={3} max={30}
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#F0A623]" className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
</div> </div>
@@ -301,7 +301,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.default_site_id} value={settings.default_site_id}
onChange={e => update('default_site_id', Number(e.target.value))} onChange={e => update('default_site_id', Number(e.target.value))}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value={1}>AV Beat</option> <option value={1}>AV Beat</option>
<option value={2}>AV Beat</option> <option value={2}>AV Beat</option>
@@ -312,7 +312,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.default_pen_name_bb} value={settings.default_pen_name_bb}
onChange={e => update('default_pen_name_bb', e.target.value)} onChange={e => update('default_pen_name_bb', e.target.value)}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
{BB_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)} {BB_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)}
</select> </select>
@@ -322,7 +322,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.default_pen_name_av} value={settings.default_pen_name_av}
onChange={e => update('default_pen_name_av', e.target.value)} onChange={e => update('default_pen_name_av', e.target.value)}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
{AV_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)} {AV_PEN_NAMES.map(n => <option key={n} value={n}>{n}</option>)}
</select> </select>
@@ -335,7 +335,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('save_session_history', !settings.save_session_history)} onClick={() => update('save_session_history', !settings.save_session_history)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.save_session_history ? 'bg-[#F0A623]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.save_session_history ? 'bg-[#1D4ED8]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.save_session_history ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.save_session_history ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -346,7 +346,7 @@ export default function AIConsoleSettingsPage() {
<select <select
value={settings.history_retention_days} value={settings.history_retention_days}
onChange={e => update('history_retention_days', Number(e.target.value))} onChange={e => update('history_retention_days', Number(e.target.value))}
className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2.5 focus:outline-none focus:border-[#1D4ED8]"
> >
<option value={30}>30 days</option> <option value={30}>30 days</option>
<option value={60}>60 days</option> <option value={60}>60 days</option>
@@ -361,7 +361,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('streaming_enabled', !settings.streaming_enabled)} onClick={() => update('streaming_enabled', !settings.streaming_enabled)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.streaming_enabled ? 'bg-[#F0A623]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.streaming_enabled ? 'bg-[#1D4ED8]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.streaming_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.streaming_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -380,7 +380,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('background_use_local', !settings.background_use_local)} onClick={() => update('background_use_local', !settings.background_use_local)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.background_use_local ? 'bg-[#F0A623]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.background_use_local ? 'bg-[#1D4ED8]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.background_use_local ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.background_use_local ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -393,7 +393,7 @@ export default function AIConsoleSettingsPage() {
value={settings.background_timeout_seconds} value={settings.background_timeout_seconds}
onChange={e => update('background_timeout_seconds', Number(e.target.value))} onChange={e => update('background_timeout_seconds', Number(e.target.value))}
min={30} max={300} min={30} max={300}
className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#F0A623]" className="w-32 bg-[#0d1117] border border-[#2a3a50] text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
@@ -404,7 +404,7 @@ export default function AIConsoleSettingsPage() {
</div> </div>
<button <button
onClick={() => update('priority_queue_enabled', !settings.priority_queue_enabled)} onClick={() => update('priority_queue_enabled', !settings.priority_queue_enabled)}
className={`w-10 h-5 rounded-full transition-colors relative ${settings.priority_queue_enabled ? 'bg-[#F0A623]' : 'bg-[#2a3a50]'}`} className={`w-10 h-5 rounded-full transition-colors relative ${settings.priority_queue_enabled ? 'bg-[#1D4ED8]' : 'bg-[#2a3a50]'}`}
> >
<div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.priority_queue_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} /> <div className={`w-4 h-4 bg-white rounded-full absolute top-0.5 transition-transform ${settings.priority_queue_enabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
</button> </button>
@@ -422,7 +422,7 @@ export default function AIConsoleSettingsPage() {
'Classification and extraction tasks', 'Classification and extraction tasks',
].map((item, i) => ( ].map((item, i) => (
<li key={i} className="flex items-center gap-2 text-xs text-[#555]"> <li key={i} className="flex items-center gap-2 text-xs text-[#555]">
<span className="w-4 h-4 rounded-full bg-[#1e2a3a] text-[#F0A623] text-[10px] flex items-center justify-center font-bold flex-shrink-0">{i + 1}</span> <span className="w-4 h-4 rounded-full bg-[#1e2a3a] text-[#1D4ED8] text-[10px] flex items-center justify-center font-bold flex-shrink-0">{i + 1}</span>
{item} {item}
</li> </li>
))} ))}

View File

@@ -130,9 +130,9 @@ export default function OverviewTab({ companyId, selectedCompany }: Props) {
<Line <Line
type="monotone" type="monotone"
dataKey="revenue" dataKey="revenue"
stroke={selectedCompany === 'rmp' ? '#F0A623' : '#a855f7'} stroke={selectedCompany === 'rmp' ? '#1D4ED8' : '#a855f7'}
strokeWidth={2} strokeWidth={2}
dot={{ fill: selectedCompany === 'rmp' ? '#F0A623' : '#a855f7', r: 3 }} dot={{ fill: selectedCompany === 'rmp' ? '#1D4ED8' : '#a855f7', r: 3 }}
/> />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@@ -269,13 +269,13 @@ export default function EditorialDashboard() {
function SortIcon({ col }: { col: typeof sortBy }) { function SortIcon({ col }: { col: typeof sortBy }) {
if (sortBy !== col) return <span className="text-[#444] ml-1"></span>; if (sortBy !== col) return <span className="text-[#444] ml-1"></span>;
return <span className="text-[#F0A623] ml-1">{sortDir === 'desc' ? '↓' : '↑'}</span>; return <span className="text-[#1D4ED8] ml-1">{sortDir === 'desc' ? '↓' : '↑'}</span>;
} }
if (loading || !authChecked) { if (loading || !authChecked) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -285,7 +285,7 @@ export default function EditorialDashboard() {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0]"> <div className="min-h-screen bg-[#0a0a0a] text-[#e0e0e0]">
{/* Header */} {/* Header */}
<div className="border-b border-[#1e1e1e] bg-[#1c1815]"> <div className="border-b border-[#1e1e1e] bg-[#F8FAFC]">
<div className="max-w-[1400px] mx-auto px-6 py-5 flex items-center justify-between gap-4"> <div className="max-w-[1400px] mx-auto px-6 py-5 flex items-center justify-between gap-4">
<div> <div>
<h1 className="text-xl font-bold text-white tracking-tight">Editorial Dashboard</h1> <h1 className="text-xl font-bold text-white tracking-tight">Editorial Dashboard</h1>
@@ -294,13 +294,13 @@ export default function EditorialDashboard() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link <Link
href="/admin/articles" href="/admin/articles"
className="px-3 py-1.5 text-xs font-semibold bg-[#F0A623] hover:bg-blue-500 text-white rounded transition-colors" className="px-3 py-1.5 text-xs font-semibold bg-[#1D4ED8] hover:bg-blue-500 text-white rounded transition-colors"
> >
+ New Article + New Article
</Link> </Link>
<Link <Link
href="/dashboard/adops" href="/dashboard/adops"
className="px-3 py-1.5 text-xs font-semibold bg-[#231d18] hover:bg-[#3a322b] text-[#aaa] border border-[#3a322b] rounded transition-colors" className="px-3 py-1.5 text-xs font-semibold bg-[#FFFFFF] hover:bg-[#DCE6F2] text-[#aaa] border border-[#DCE6F2] rounded transition-colors"
> >
Ad Ops Ad Ops
</Link> </Link>
@@ -325,7 +325,7 @@ export default function EditorialDashboard() {
<button <button
key={card.label} key={card.label}
onClick={card.onClick} onClick={card.onClick}
className="bg-[#111] border border-[#1e1e1e] rounded-lg p-4 text-left hover:border-[#3a322b] transition-colors cursor-pointer" className="bg-[#111] border border-[#1e1e1e] rounded-lg p-4 text-left hover:border-[#DCE6F2] transition-colors cursor-pointer"
> >
<div className={`text-xl font-bold ${card.color}`}>{card.value}</div> <div className={`text-xl font-bold ${card.color}`}>{card.value}</div>
<div className="text-[11px] text-[#666] mt-1 uppercase tracking-wider">{card.label}</div> <div className="text-[11px] text-[#666] mt-1 uppercase tracking-wider">{card.label}</div>
@@ -344,7 +344,7 @@ export default function EditorialDashboard() {
onClick={() => setStatusFilter(s)} onClick={() => setStatusFilter(s)}
className={`px-3 py-1.5 text-xs font-semibold capitalize transition-colors ${ className={`px-3 py-1.5 text-xs font-semibold capitalize transition-colors ${
statusFilter === s statusFilter === s
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#666] hover:text-[#aaa]' : 'text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -360,7 +360,7 @@ export default function EditorialDashboard() {
placeholder="Search articles, clients, campaigns..." placeholder="Search articles, clients, campaigns..."
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={e => setSearchQuery(e.target.value)}
className="w-full bg-[#111] border border-[#1e1e1e] rounded-lg px-3 py-1.5 text-xs text-[#ccc] placeholder-[#444] focus:outline-none focus:border-[#F0A623] transition-colors" className="w-full bg-[#111] border border-[#1e1e1e] rounded-lg px-3 py-1.5 text-xs text-[#ccc] placeholder-[#444] focus:outline-none focus:border-[#1D4ED8] transition-colors"
/> />
{searchQuery && ( {searchQuery && (
<button <button
@@ -376,10 +376,10 @@ export default function EditorialDashboard() {
</div> </div>
{/* Pipeline Table */} {/* Pipeline Table */}
<div className="bg-[#1c1815] border border-[#1e1e1e] rounded-xl overflow-hidden"> <div className="bg-[#F8FAFC] border border-[#1e1e1e] rounded-xl overflow-hidden">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<div className="w-6 h-6 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-6 h-6 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : filtered.length === 0 ? ( ) : filtered.length === 0 ? (
<div className="text-center py-20 text-[#444] text-sm">No articles found</div> <div className="text-center py-20 text-[#444] text-sm">No articles found</div>
@@ -387,7 +387,7 @@ export default function EditorialDashboard() {
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full text-xs"> <table className="w-full text-xs">
<thead> <thead>
<tr className="border-b border-[#231d18] bg-[#0a0a0a]"> <tr className="border-b border-[#FFFFFF] bg-[#0a0a0a]">
<th className="text-left px-4 py-3 text-[#555] font-semibold uppercase tracking-wider w-[30%]">Article</th> <th className="text-left px-4 py-3 text-[#555] font-semibold uppercase tracking-wider w-[30%]">Article</th>
<th className="text-left px-3 py-3 text-[#555] font-semibold uppercase tracking-wider">Status</th> <th className="text-left px-3 py-3 text-[#555] font-semibold uppercase tracking-wider">Status</th>
<th <th
@@ -437,7 +437,7 @@ export default function EditorialDashboard() {
<span className="text-[#555]">{article.author_name}</span> <span className="text-[#555]">{article.author_name}</span>
)} )}
{article.category && ( {article.category && (
<span className="text-[#F0A623]/70 bg-[#F0A623]/10 px-1.5 py-0.5 rounded text-[10px]"> <span className="text-[#1D4ED8]/70 bg-[#1D4ED8]/10 px-1.5 py-0.5 rounded text-[10px]">
{article.category} {article.category}
</span> </span>
)} )}

View File

@@ -256,7 +256,7 @@ export default function ShowCalendarPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
@@ -281,7 +281,7 @@ export default function ShowCalendarPage() {
)} )}
{/* Header */} {/* Header */}
<div className="border-b border-[#231d18] bg-[#0f0f0f]"> <div className="border-b border-[#FFFFFF] bg-[#0f0f0f]">
<div className="max-w-7xl mx-auto px-4 py-4"> <div className="max-w-7xl mx-auto px-4 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
@@ -294,13 +294,13 @@ export default function ShowCalendarPage() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
onClick={() => { setEditingEvent({ site_id: 1, year: new Date().getFullYear(), seo_tier: 3, event_type: 'trade_show' }); setShowEventModal(true); }} onClick={() => { setEditingEvent({ site_id: 1, year: new Date().getFullYear(), seo_tier: 3, event_type: 'trade_show' }); setShowEventModal(true); }}
className="px-3 py-1.5 bg-[#F0A623] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors" className="px-3 py-1.5 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors"
> >
+ Add Event + Add Event
</button> </button>
<button <button
onClick={() => setShowGenerateModal(true)} onClick={() => setShowGenerateModal(true)}
className="px-3 py-1.5 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-white text-sm rounded-lg transition-colors" className="px-3 py-1.5 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-white text-sm rounded-lg transition-colors"
> >
Generate Story Generate Story
</button> </button>
@@ -316,8 +316,8 @@ export default function ShowCalendarPage() {
onClick={() => setSiteFilter(s)} onClick={() => setSiteFilter(s)}
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${ className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
siteFilter === s siteFilter === s
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'bg-[#231d18] text-[#888] hover:text-white border border-[#333]' : 'bg-[#FFFFFF] text-[#888] hover:text-white border border-[#333]'
}`} }`}
> >
{s === '' ? 'All Sites' : s === '1' ? '🔵 AV Beat' : '🟠 AV Beat'} {s === '' ? 'All Sites' : s === '1' ? '🔵 AV Beat' : '🟠 AV Beat'}
@@ -326,14 +326,14 @@ export default function ShowCalendarPage() {
</div> </div>
{/* Tabs */} {/* Tabs */}
<div className="flex gap-1 mt-4 border-b border-[#231d18]"> <div className="flex gap-1 mt-4 border-b border-[#FFFFFF]">
{tabs.map(tab => ( {tabs.map(tab => (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${ className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
activeTab === tab.id activeTab === tab.id
? 'border-[#F0A623] text-white' ? 'border-[#1D4ED8] text-white'
: 'border-transparent text-[#666] hover:text-white' : 'border-transparent text-[#666] hover:text-white'
}`} }`}
> >
@@ -347,7 +347,7 @@ export default function ShowCalendarPage() {
<div className="max-w-7xl mx-auto px-4 py-6"> <div className="max-w-7xl mx-auto px-4 py-6">
{loadingData ? ( {loadingData ? (
<div className="flex items-center justify-center py-20"> <div className="flex items-center justify-center py-20">
<div className="w-8 h-8 border-2 border-[#F0A623] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-2 border-[#1D4ED8] border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
<> <>
@@ -383,7 +383,7 @@ export default function ShowCalendarPage() {
<span>Engine: {formatDate(event.story_engine_start_date)} {formatDate(event.story_engine_end_date)}</span> <span>Engine: {formatDate(event.story_engine_start_date)} {formatDate(event.story_engine_end_date)}</span>
{event.official_site && ( {event.official_site && (
<a href={`https://${event.official_site}`} target="_blank" rel="noopener noreferrer" <a href={`https://${event.official_site}`} target="_blank" rel="noopener noreferrer"
className="text-[#F0A623] hover:underline">{event.official_site}</a> className="text-[#1D4ED8] hover:underline">{event.official_site}</a>
)} )}
</div> </div>
<div className="flex items-center gap-3 mt-2 text-xs text-[#555]"> <div className="flex items-center gap-3 mt-2 text-xs text-[#555]">
@@ -400,13 +400,13 @@ export default function ShowCalendarPage() {
<button <button
onClick={() => handleExtractStyle(event.event_name)} onClick={() => handleExtractStyle(event.event_name)}
disabled={actionLoading === 'style-' + event.event_name} disabled={actionLoading === 'style-' + event.event_name}
className="px-2 py-1 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors disabled:opacity-50" className="px-2 py-1 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors disabled:opacity-50"
> >
{actionLoading === 'style-' + event.event_name ? 'Extracting...' : 'Extract Style'} {actionLoading === 'style-' + event.event_name ? 'Extracting...' : 'Extract Style'}
</button> </button>
<button <button
onClick={() => { setEditingEvent(event); setShowEventModal(true); }} onClick={() => { setEditingEvent(event); setShowEventModal(true); }}
className="px-2 py-1 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors" className="px-2 py-1 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors"
> >
Edit Dates Edit Dates
</button> </button>
@@ -440,8 +440,8 @@ export default function ShowCalendarPage() {
onClick={() => setQueueStatus(s)} onClick={() => setQueueStatus(s)}
className={`px-3 py-1 rounded text-xs font-medium transition-colors ${ className={`px-3 py-1 rounded text-xs font-medium transition-colors ${
queueStatus === s queueStatus === s
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'bg-[#231d18] text-[#888] hover:text-white border border-[#333]' : 'bg-[#FFFFFF] text-[#888] hover:text-white border border-[#333]'
}`} }`}
> >
{s.replace('_', ' ')} {s.replace('_', ' ')}
@@ -460,7 +460,7 @@ export default function ShowCalendarPage() {
<div className="flex items-center gap-2 mb-1 flex-wrap"> <div className="flex items-center gap-2 mb-1 flex-wrap">
<SiteBadge siteId={item.site_id} /> <SiteBadge siteId={item.site_id} />
{item.source_show && ( {item.source_show && (
<span className="text-xs text-[#555] bg-[#231d18] px-1.5 py-0.5 rounded">{item.source_show}</span> <span className="text-xs text-[#555] bg-[#FFFFFF] px-1.5 py-0.5 rounded">{item.source_show}</span>
)} )}
{item.show_phase && ( {item.show_phase && (
<span className={`text-xs ${PHASE_LABELS[item.show_phase]?.color || 'text-gray-500'}`}> <span className={`text-xs ${PHASE_LABELS[item.show_phase]?.color || 'text-gray-500'}`}>
@@ -470,7 +470,7 @@ export default function ShowCalendarPage() {
<span className={`text-xs px-1.5 py-0.5 rounded ${ <span className={`text-xs px-1.5 py-0.5 rounded ${
item.status === 'published' ? 'bg-green-900/20 text-green-400' : item.status === 'published' ? 'bg-green-900/20 text-green-400' :
item.status === 'rejected' ? 'bg-red-900/20 text-red-400' : item.status === 'rejected' ? 'bg-red-900/20 text-red-400' :
item.status === 'generated'? 'bg-blue-900/20 text-blue-400' : 'bg-[#231d18] text-[#888]' item.status === 'generated'? 'bg-blue-900/20 text-blue-400' : 'bg-[#FFFFFF] text-[#888]'
}`}>{item.status}</span> }`}>{item.status}</span>
{item.quality_confidence !== null && ( {item.quality_confidence !== null && (
<span className={`text-xs ${item.quality_confidence >= 0.85 ? 'text-green-400' : item.quality_confidence >= 0.7 ? 'text-yellow-400' : 'text-red-400'}`}> <span className={`text-xs ${item.quality_confidence >= 0.85 ? 'text-green-400' : item.quality_confidence >= 0.7 ? 'text-yellow-400' : 'text-red-400'}`}>
@@ -490,7 +490,7 @@ export default function ShowCalendarPage() {
{/* Expanded content */} {/* Expanded content */}
{expandedItem === item.id && item.generated_content && ( {expandedItem === item.id && item.generated_content && (
<div className="mt-3 p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="mt-3 p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<div <div
className="text-[#aaa] text-xs prose prose-invert max-w-none" className="text-[#aaa] text-xs prose prose-invert max-w-none"
dangerouslySetInnerHTML={{ __html: item.generated_content.slice(0, 1500) + (item.generated_content.length > 1500 ? '...' : '') }} dangerouslySetInnerHTML={{ __html: item.generated_content.slice(0, 1500) + (item.generated_content.length > 1500 ? '...' : '') }}
@@ -501,7 +501,7 @@ export default function ShowCalendarPage() {
<div className="flex items-center gap-2 flex-shrink-0"> <div className="flex items-center gap-2 flex-shrink-0">
<button <button
onClick={() => setExpandedItem(expandedItem === item.id ? null : item.id)} onClick={() => setExpandedItem(expandedItem === item.id ? null : item.id)}
className="px-2 py-1 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors" className="px-2 py-1 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors"
> >
{expandedItem === item.id ? 'Hide' : 'Preview'} {expandedItem === item.id ? 'Hide' : 'Preview'}
</button> </button>
@@ -541,7 +541,7 @@ export default function ShowCalendarPage() {
<select <select
value={showFilter} value={showFilter}
onChange={e => setShowFilter(e.target.value)} onChange={e => setShowFilter(e.target.value)}
className="bg-[#231d18] border border-[#333] text-white text-xs rounded px-2 py-1" className="bg-[#FFFFFF] border border-[#333] text-white text-xs rounded px-2 py-1"
> >
<option value="">All Shows</option> <option value="">All Shows</option>
{events.map(e => <option key={e.id} value={e.id}>{e.event_name}</option>)} {events.map(e => <option key={e.id} value={e.id}>{e.event_name}</option>)}
@@ -564,13 +564,13 @@ export default function ShowCalendarPage() {
<tr><td colSpan={5} className="text-center py-8 text-[#555]">No exhibitors found.</td></tr> <tr><td colSpan={5} className="text-center py-8 text-[#555]">No exhibitors found.</td></tr>
)} )}
{exhibitors.map(ex => ( {exhibitors.map(ex => (
<tr key={ex.id} className="border-b border-[#231d18] hover:bg-[#0f0f0f]"> <tr key={ex.id} className="border-b border-[#FFFFFF] hover:bg-[#0f0f0f]">
<td className="px-4 py-3 text-white font-medium">{ex.company_name}</td> <td className="px-4 py-3 text-white font-medium">{ex.company_name}</td>
<td className="px-4 py-3 text-[#888]">{ex.booth_number || '—'}</td> <td className="px-4 py-3 text-[#888]">{ex.booth_number || '—'}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className={`text-xs px-1.5 py-0.5 rounded ${ <span className={`text-xs px-1.5 py-0.5 rounded ${
ex.preview_story_status === 'published' ? 'bg-green-900/20 text-green-400' : ex.preview_story_status === 'published' ? 'bg-green-900/20 text-green-400' :
ex.preview_story_status === 'generated'? 'bg-blue-900/20 text-blue-400' : 'bg-[#231d18] text-[#888]' ex.preview_story_status === 'generated'? 'bg-blue-900/20 text-blue-400' : 'bg-[#FFFFFF] text-[#888]'
}`}>{ex.preview_story_status}</span> }`}>{ex.preview_story_status}</span>
</td> </td>
<td className="px-4 py-3"> <td className="px-4 py-3">
@@ -613,14 +613,14 @@ export default function ShowCalendarPage() {
Updated: {formatDate(profile.last_updated)} Updated: {formatDate(profile.last_updated)}
</p> </p>
{expandedItem === profile.id && profile.style_guide_text && ( {expandedItem === profile.id && profile.style_guide_text && (
<div className="mt-3 p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="mt-3 p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<p className="text-[#aaa] text-xs whitespace-pre-wrap">{profile.style_guide_text}</p> <p className="text-[#aaa] text-xs whitespace-pre-wrap">{profile.style_guide_text}</p>
</div> </div>
)} )}
</div> </div>
<button <button
onClick={() => setExpandedItem(expandedItem === profile.id ? null : profile.id)} onClick={() => setExpandedItem(expandedItem === profile.id ? null : profile.id)}
className="px-2 py-1 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors flex-shrink-0" className="px-2 py-1 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-[#888] hover:text-white text-xs rounded transition-colors flex-shrink-0"
> >
{expandedItem === profile.id ? 'Hide' : 'View Guide'} {expandedItem === profile.id ? 'Hide' : 'View Guide'}
</button> </button>
@@ -637,24 +637,24 @@ export default function ShowCalendarPage() {
<div className="bg-[#111] border border-[#1e1e1e] rounded-xl p-6"> <div className="bg-[#111] border border-[#1e1e1e] rounded-xl p-6">
<h2 className="text-white font-semibold mb-4">Show Engine Settings</h2> <h2 className="text-white font-semibold mb-4">Show Engine Settings</h2>
<div className="space-y-4 text-sm text-[#888]"> <div className="space-y-4 text-sm text-[#888]">
<div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<p className="text-white font-medium mb-1">Annual Date Discovery</p> <p className="text-white font-medium mb-1">Annual Date Discovery</p>
<p>Runs automatically on January 1 each year. Searches official show websites and Google for confirmed dates. Sends admin notification when dates are set or when manual review is needed.</p> <p>Runs automatically on January 1 each year. Searches official show websites and Google for confirmed dates. Sends admin notification when dates are set or when manual review is needed.</p>
</div> </div>
<div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<p className="text-white font-medium mb-1">Pen Name Rosters</p> <p className="text-white font-medium mb-1">Pen Name Rosters</p>
<p className="mb-2"><strong className="text-[#aaa]">AV Beat (12 writers):</strong> Michael Strand, David Harlow, Karen Fielding, James Mercer (primary NAB), Peter Calloway, Sandra Voss, Brian Kowalski, Laura Pennington, Thomas Reeves, Christine Vale, Marcus Webb, Ellen Forsythe</p> <p className="mb-2"><strong className="text-[#aaa]">AV Beat (12 writers):</strong> Michael Strand, David Harlow, Karen Fielding, James Mercer (primary NAB), Peter Calloway, Sandra Voss, Brian Kowalski, Laura Pennington, Thomas Reeves, Christine Vale, Marcus Webb, Ellen Forsythe</p>
<p><strong className="text-[#aaa]">AV Beat (7 writers):</strong> Rex Chandler, Dana Flux, Derek Wainwright, Sloane Rigging, Chip Crosspoint, Blair Presenter, Jordan Lumen</p> <p><strong className="text-[#aaa]">AV Beat (7 writers):</strong> Rex Chandler, Dana Flux, Derek Wainwright, Sloane Rigging, Chip Crosspoint, Blair Presenter, Jordan Lumen</p>
</div> </div>
<div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<p className="text-white font-medium mb-1">SEO Tier Priority</p> <p className="text-white font-medium mb-1">SEO Tier Priority</p>
<p>Tier 1 (Max): NAB Las Vegas, IBC, InfoComm, ISE · Tier 2 (High): NAB NY, BroadcastAsia, CABSAT, AES, InfoComm EDGE/China/LatAm · Tier 3 (Standard): MPTS, SET Expo, NATPE, InfoComm India, FORTÉ LIVE · Tier 4 (Light): NewsTECHForum, TAB, SVVS, Hamburg Open, AVIXA Regional</p> <p>Tier 1 (Max): NAB Las Vegas, IBC, InfoComm, ISE · Tier 2 (High): NAB NY, BroadcastAsia, CABSAT, AES, InfoComm EDGE/China/LatAm · Tier 3 (Standard): MPTS, SET Expo, NATPE, InfoComm India, FORTÉ LIVE · Tier 4 (Light): NewsTECHForum, TAB, SVVS, Hamburg Open, AVIXA Regional</p>
</div> </div>
<div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<p className="text-white font-medium mb-1">NAB Cross-Post to AV Beat</p> <p className="text-white font-medium mb-1">NAB Cross-Post to AV Beat</p>
<p>NAB exhibitors with AV-eligible products (projectors, displays, conferencing, control systems, digital signage, installed audio, AV-over-IP) automatically generate stories on AV Beat using AV Beat pen names.</p> <p>NAB exhibitors with AV-eligible products (projectors, displays, conferencing, control systems, digital signage, installed audio, AV-over-IP) automatically generate stories on AV Beat using AV Beat pen names.</p>
</div> </div>
<div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#231d18]"> <div className="p-3 bg-[#0a0a0a] rounded-lg border border-[#FFFFFF]">
<p className="text-white font-medium mb-1">Blocked Competitor Sources</p> <p className="text-white font-medium mb-1">Blocked Competitor Sources</p>
<p>BB: tvtechnology.com, broadcastingcable.com, sportsvideo.org, digitaltveurope.com, nexttv.com · AV: ravepubs.com, avnetwork.com, commercialintegrator.com, systemscontractor.com, avinteractive.com · Note: infocommshow.org, iseurope.org, nabshow.com, avixa.org are PRIMARY sources not blocked.</p> <p>BB: tvtechnology.com, broadcastingcable.com, sportsvideo.org, digitaltveurope.com, nexttv.com · AV: ravepubs.com, avnetwork.com, commercialintegrator.com, systemscontractor.com, avinteractive.com · Note: infocommshow.org, iseurope.org, nabshow.com, avixa.org are PRIMARY sources not blocked.</p>
</div> </div>
@@ -779,14 +779,14 @@ export default function ShowCalendarPage() {
<button <button
type="submit" type="submit"
disabled={actionLoading === 'save-event'} disabled={actionLoading === 'save-event'}
className="flex-1 py-2 bg-[#F0A623] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
> >
{actionLoading === 'save-event' ? 'Saving...' : 'Save Event'} {actionLoading === 'save-event' ? 'Saving...' : 'Save Event'}
</button> </button>
<button <button
type="button" type="button"
onClick={() => { setShowEventModal(false); setEditingEvent(null); }} onClick={() => { setShowEventModal(false); setEditingEvent(null); }}
className="px-4 py-2 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-white text-sm rounded-lg transition-colors" className="px-4 py-2 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-white text-sm rounded-lg transition-colors"
> >
Cancel Cancel
</button> </button>
@@ -857,14 +857,14 @@ export default function ShowCalendarPage() {
<button <button
type="submit" type="submit"
disabled={actionLoading === 'generate'} disabled={actionLoading === 'generate'}
className="flex-1 py-2 bg-[#F0A623] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50" className="flex-1 py-2 bg-[#1D4ED8] hover:bg-blue-500 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
> >
{actionLoading === 'generate' ? 'Generating...' : 'Generate Story'} {actionLoading === 'generate' ? 'Generating...' : 'Generate Story'}
</button> </button>
<button <button
type="button" type="button"
onClick={() => setShowGenerateModal(false)} onClick={() => setShowGenerateModal(false)}
className="px-4 py-2 bg-[#231d18] hover:bg-[#3a322b] border border-[#333] text-white text-sm rounded-lg transition-colors" className="px-4 py-2 bg-[#FFFFFF] hover:bg-[#DCE6F2] border border-[#333] text-white text-sm rounded-lg transition-colors"
> >
Cancel Cancel
</button> </button>

View File

@@ -13,7 +13,7 @@ export default function Error({
console.error("[route error]", error); console.error("[route error]", error);
}, [error]); }, [error]);
return ( return (
<div className="min-h-screen flex items-center justify-center bg-[#1c1815] text-[#e0e0e0] p-6"> <div className="min-h-screen flex items-center justify-center bg-[#F8FAFC] text-[#e0e0e0] p-6">
<div className="max-w-md text-center"> <div className="max-w-md text-center">
<h2 className="font-heading text-2xl mb-3">Something went wrong</h2> <h2 className="font-heading text-2xl mb-3">Something went wrong</h2>
<p className="text-[#888] text-sm mb-4 font-body"> <p className="text-[#888] text-sm mb-4 font-body">
@@ -25,13 +25,13 @@ export default function Error({
<div className="flex gap-3 justify-center"> <div className="flex gap-3 justify-center">
<button <button
onClick={() => reset()} onClick={() => reset()}
className="px-4 py-2 bg-[#F0A623] text-white text-sm rounded-sm hover:bg-[#BA7517] transition-colors" className="px-4 py-2 bg-[#1D4ED8] text-white text-sm rounded-sm hover:bg-[#1E3A8A] transition-colors"
> >
Try again Try again
</button> </button>
<Link <Link
href="/home-page" href="/home-page"
className="px-4 py-2 border border-[#333] text-sm rounded-sm hover:border-[#F0A623] transition-colors" className="px-4 py-2 border border-[#333] text-sm rounded-sm hover:border-[#1D4ED8] transition-colors"
> >
Home Home
</Link> </Link>

View File

@@ -74,27 +74,27 @@ function TopThreadsWidget({ categoryId }: { categoryId: string }) {
const getMetricValue = (thread: ForumThread) => { const getMetricValue = (thread: ForumThread) => {
if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' }; if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' };
if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#F0A623]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#1D4ED8]' };
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' }; return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
}; };
return ( return (
<div className="bg-[#231d18] border border-[#3a322b] rounded-xl overflow-hidden mb-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-xl overflow-hidden mb-6">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-5 py-4 border-b border-[#3a322b]"> <div className="flex items-center justify-between px-5 py-4 border-b border-[#DCE6F2]">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-lg">🏆</span> <span className="text-lg">🏆</span>
<h2 className="text-white font-heading font-bold text-base">Top Performing Threads</h2> <h2 className="text-white font-heading font-bold text-base">Top Performing Threads</h2>
</div> </div>
{/* Metric Tabs */} {/* Metric Tabs */}
<div className="flex gap-1 bg-[#111] border border-[#3a322b] rounded-lg p-1"> <div className="flex gap-1 bg-[#111] border border-[#DCE6F2] rounded-lg p-1">
{metrics.map(m => ( {metrics.map(m => (
<button <button
key={m.value} key={m.value}
onClick={() => setActiveMetric(m.value)} onClick={() => setActiveMetric(m.value)}
className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${ className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
activeMetric === m.value activeMetric === m.value
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#666] hover:text-[#aaa]' : 'text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -111,12 +111,12 @@ function TopThreadsWidget({ categoryId }: { categoryId: string }) {
<> <>
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center gap-3 px-5 py-3"> <div key={i} className="flex items-center gap-3 px-5 py-3">
<div className="w-6 h-6 bg-[#3a322b] rounded animate-pulse flex-shrink-0" /> <div className="w-6 h-6 bg-[#DCE6F2] rounded animate-pulse flex-shrink-0" />
<div className="flex-1 space-y-1.5"> <div className="flex-1 space-y-1.5">
<div className="h-3.5 bg-[#3a322b] rounded animate-pulse w-3/4" /> <div className="h-3.5 bg-[#DCE6F2] rounded animate-pulse w-3/4" />
<div className="h-2.5 bg-[#1e1e1e] rounded animate-pulse w-1/3" /> <div className="h-2.5 bg-[#1e1e1e] rounded animate-pulse w-1/3" />
</div> </div>
<div className="w-10 h-8 bg-[#3a322b] rounded animate-pulse flex-shrink-0" /> <div className="w-10 h-8 bg-[#DCE6F2] rounded animate-pulse flex-shrink-0" />
</div> </div>
))} ))}
</> </>
@@ -142,11 +142,11 @@ function TopThreadsWidget({ categoryId }: { categoryId: string }) {
</span> </span>
{/* Thread info */} {/* Thread info */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-white font-body text-sm font-semibold group-hover:text-[#F0A623] transition-colors truncate leading-snug"> <p className="text-white font-body text-sm font-semibold group-hover:text-[#1D4ED8] transition-colors truncate leading-snug">
{thread.title} {thread.title}
</p> </p>
<p className="text-[#555] font-body text-xs mt-0.5"> <p className="text-[#555] font-body text-xs mt-0.5">
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#F0A623] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#1D4ED8] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
</div> </div>
@@ -277,12 +277,12 @@ export default function ForumCategoryPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
{/* Breadcrumb + Header */} {/* Breadcrumb + Header */}
<div className="bg-[#231d18] border-b border-[#2a3a50]"> <div className="bg-[#FFFFFF] border-b border-[#2a3a50]">
<div className="max-w-container mx-auto px-4 py-8"> <div className="max-w-container mx-auto px-4 py-8">
<nav className="text-sm font-body text-[#666] mb-3"> <nav className="text-sm font-body text-[#666] mb-3">
<Link href="/forum" className="hover:text-[#F0A623] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#1D4ED8] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
<span className="text-[#aab4c4]">{category?.name || slug}</span> <span className="text-[#aab4c4]">{category?.name || slug}</span>
</nav> </nav>
@@ -299,14 +299,14 @@ export default function ForumCategoryPage() {
{isAuthenticated ? ( {isAuthenticated ? (
<button <button
onClick={() => setShowNewThread(!showNewThread)} onClick={() => setShowNewThread(!showNewThread)}
className="flex-shrink-0 bg-[#F0A623] hover:bg-[#BA7517] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors" className="flex-shrink-0 bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors"
> >
+ New Thread + New Thread
</button> </button>
) : ( ) : (
<Link <Link
href={`/forum/login?next=${typeof window === 'undefined' ? '/forum' : encodeURIComponent(window.location.pathname)}`} href={`/forum/login?next=${typeof window === 'undefined' ? '/forum' : encodeURIComponent(window.location.pathname)}`}
className="flex-shrink-0 bg-[#F0A623] hover:bg-[#BA7517] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors" className="flex-shrink-0 bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors"
> >
Sign in to post Sign in to post
</Link> </Link>
@@ -318,7 +318,7 @@ export default function ForumCategoryPage() {
<div className="max-w-container mx-auto px-4 py-6"> <div className="max-w-container mx-auto px-4 py-6">
{/* New Thread Form — auth-gated */} {/* New Thread Form — auth-gated */}
{showNewThread && isAuthenticated && ( {showNewThread && isAuthenticated && (
<form onSubmit={handleNewThread} className="bg-[#231d18] border border-[#2a3a50] rounded-lg p-5 mb-6"> <form onSubmit={handleNewThread} className="bg-[#FFFFFF] border border-[#2a3a50] rounded-lg p-5 mb-6">
<h3 className="text-white font-heading font-semibold mb-4">Start a New Thread</h3> <h3 className="text-white font-heading font-semibold mb-4">Start a New Thread</h3>
<div className="space-y-3"> <div className="space-y-3">
<input <input
@@ -327,7 +327,7 @@ export default function ForumCategoryPage() {
value={newTitle} value={newTitle}
onChange={e => setNewTitle(e.target.value)} onChange={e => setNewTitle(e.target.value)}
required required
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#1D4ED8]"
/> />
<textarea <textarea
placeholder="What would you like to discuss? *" placeholder="What would you like to discuss? *"
@@ -335,14 +335,14 @@ export default function ForumCategoryPage() {
onChange={e => setNewBody(e.target.value)} onChange={e => setNewBody(e.target.value)}
required required
rows={5} rows={5}
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#1D4ED8] resize-none"
/> />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<ImageAttachButton onInsert={(md) => setNewBody((b) => (b || "") + md)} /> <ImageAttachButton onInsert={(md) => setNewBody((b) => (b || "") + md)} />
<span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span> <span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span>
</div> </div>
{newError && ( {newError && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{newError} {newError}
</div> </div>
)} )}
@@ -350,14 +350,14 @@ export default function ForumCategoryPage() {
<button <button
type="submit" type="submit"
disabled={submitting} disabled={submitting}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
{submitting ? 'Posting...' : 'Post Thread'} {submitting ? 'Posting...' : 'Post Thread'}
</button> </button>
<button <button
type="button" type="button"
onClick={() => setShowNewThread(false)} onClick={() => setShowNewThread(false)}
className="bg-[#3a322b] hover:bg-[#333] text-[#aaa] font-body text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#DCE6F2] hover:bg-[#333] text-[#aaa] font-body text-sm px-5 py-2 rounded-lg transition-colors"
> >
Cancel Cancel
</button> </button>
@@ -378,7 +378,7 @@ export default function ForumCategoryPage() {
placeholder="Search threads in this category..." placeholder="Search threads in this category..."
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}
className="w-full bg-[#231d18] border border-[#3a322b] focus:border-[#F0A623] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors" className="w-full bg-[#FFFFFF] border border-[#DCE6F2] focus:border-[#1D4ED8] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors"
/> />
{search && ( {search && (
<button <button
@@ -394,14 +394,14 @@ export default function ForumCategoryPage() {
</div> </div>
{/* Sort Options */} {/* Sort Options */}
<div className="flex gap-1 bg-[#231d18] border border-[#3a322b] rounded-lg p-1 flex-shrink-0"> <div className="flex gap-1 bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-1 flex-shrink-0">
{sortOptions.map(opt => ( {sortOptions.map(opt => (
<button <button
key={opt.value} key={opt.value}
onClick={() => setSort(opt.value)} onClick={() => setSort(opt.value)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${ className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
sort === opt.value sort === opt.value
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -414,7 +414,7 @@ export default function ForumCategoryPage() {
{(loading || threadsLoading) && ( {(loading || threadsLoading) && (
<div className="space-y-2"> <div className="space-y-2">
{[...Array(10)].map((_, i) => ( {[...Array(10)].map((_, i) => (
<div key={i} className="bg-[#231d18] rounded-lg h-16 animate-pulse border border-[#3a322b]" /> <div key={i} className="bg-[#FFFFFF] rounded-lg h-16 animate-pulse border border-[#DCE6F2]" />
))} ))}
</div> </div>
)} )}
@@ -445,12 +445,12 @@ export default function ForumCategoryPage() {
<Link <Link
key={thread.id} key={thread.id}
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="flex items-center gap-4 bg-[#231d18] hover:bg-[#1e2a3a] border border-[#3a322b] hover:border-[#F0A623] rounded-lg px-4 py-3 transition-all group" className="flex items-center gap-4 bg-[#FFFFFF] hover:bg-[#1e2a3a] border border-[#DCE6F2] hover:border-[#1D4ED8] rounded-lg px-4 py-3 transition-all group"
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
{thread.status === 'pinned' && ( {thread.status === 'pinned' && (
<span className="text-xs bg-[#F0A623]/20 text-[#F0A623] px-2 py-0.5 rounded font-body font-semibold">PINNED</span> <span className="text-xs bg-[#1D4ED8]/20 text-[#1D4ED8] px-2 py-0.5 rounded font-body font-semibold">PINNED</span>
)} )}
{thread.status === 'closed' && ( {thread.status === 'closed' && (
<span className="text-xs bg-[#555]/20 text-[#888] px-2 py-0.5 rounded font-body">CLOSED</span> <span className="text-xs bg-[#555]/20 text-[#888] px-2 py-0.5 rounded font-body">CLOSED</span>
@@ -458,12 +458,12 @@ export default function ForumCategoryPage() {
{thread.reply_count === 0 && ( {thread.reply_count === 0 && (
<span className="text-xs bg-[#f59e0b]/10 text-[#f59e0b] px-2 py-0.5 rounded font-body">UNANSWERED</span> <span className="text-xs bg-[#f59e0b]/10 text-[#f59e0b] px-2 py-0.5 rounded font-body">UNANSWERED</span>
)} )}
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#F0A623] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#1D4ED8] transition-colors truncate">
{thread.title} {thread.title}
</h3> </h3>
</div> </div>
<p className="text-[#666] font-body text-xs mt-0.5"> <p className="text-[#666] font-body text-xs mt-0.5">
by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#F0A623] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#1D4ED8] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
</div> </div>

View File

@@ -30,10 +30,10 @@ function ForgotInner() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-gradient-to-b from-[#1c1815] via-[#1c1815] to-[#1c1815]"> <main className="min-h-screen bg-gradient-to-b from-[#F8FAFC] via-[#F8FAFC] to-[#F8FAFC]">
<div className="max-w-md mx-auto px-4 py-16"> <div className="max-w-md mx-auto px-4 py-16">
<div className="text-center mb-6"> <div className="text-center mb-6">
<div className="text-[10px] uppercase tracking-widest text-[#F0A623] font-mono mb-2">Account recovery</div> <div className="text-[10px] uppercase tracking-widest text-[#1D4ED8] font-mono mb-2">Account recovery</div>
<h1 className="text-3xl font-heading font-bold text-white">Reset your password</h1> <h1 className="text-3xl font-heading font-bold text-white">Reset your password</h1>
<p className="text-[#888] font-body text-sm mt-2"> <p className="text-[#888] font-body text-sm mt-2">
Enter your email we&rsquo;ll send a link to set a new password. Enter your email we&rsquo;ll send a link to set a new password.
@@ -41,10 +41,10 @@ function ForgotInner() {
</div> </div>
<form <form
onSubmit={submit} onSubmit={submit}
className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 space-y-4 shadow-2xl" className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 space-y-4 shadow-2xl"
> >
{err && ( {err && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{err} {err}
</div> </div>
)} )}
@@ -63,19 +63,19 @@ function ForgotInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={busy || !email.trim()} disabled={busy || !email.trim()}
className="w-full bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors" className="w-full bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors"
> >
{busy ? "Sending…" : "Send reset link"} {busy ? "Sending…" : "Send reset link"}
</button> </button>
<div className="text-center text-xs font-body text-[#888] pt-2 border-t border-[#3a322b]"> <div className="text-center text-xs font-body text-[#888] pt-2 border-t border-[#DCE6F2]">
Remembered it?{" "} Remembered it?{" "}
<Link href="/forum/login" className="text-[#F0A623] hover:underline font-semibold"> <Link href="/forum/login" className="text-[#1D4ED8] hover:underline font-semibold">
Back to sign in Back to sign in
</Link> </Link>
</div> </div>

View File

@@ -51,10 +51,10 @@ function ForumLoginInner() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-gradient-to-b from-[#1c1815] via-[#1c1815] to-[#1c1815]"> <main className="min-h-screen bg-gradient-to-b from-[#F8FAFC] via-[#F8FAFC] to-[#F8FAFC]">
<div className="max-w-md mx-auto px-4 py-16"> <div className="max-w-md mx-auto px-4 py-16">
<div className="text-center mb-6"> <div className="text-center mb-6">
<div className="text-[10px] uppercase tracking-widest text-[#F0A623] font-mono mb-2">Welcome back</div> <div className="text-[10px] uppercase tracking-widest text-[#1D4ED8] font-mono mb-2">Welcome back</div>
<h1 className="text-3xl font-heading font-bold text-white">Sign in to AV Beat</h1> <h1 className="text-3xl font-heading font-bold text-white">Sign in to AV Beat</h1>
<p className="text-[#888] font-body text-sm mt-2"> <p className="text-[#888] font-body text-sm mt-2">
Your account covers the forum, newsletter preferences, and saved articles. Your account covers the forum, newsletter preferences, and saved articles.
@@ -62,10 +62,10 @@ function ForumLoginInner() {
</div> </div>
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 space-y-4 shadow-2xl" className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 space-y-4 shadow-2xl"
> >
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -79,7 +79,7 @@ function ForumLoginInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -92,20 +92,20 @@ function ForumLoginInner() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password" autoComplete="current-password"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors" className="w-full bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors"
> >
{loading ? 'Signing in…' : 'Sign In'} {loading ? 'Signing in…' : 'Sign In'}
</button> </button>
<div className="text-right text-xs font-body"> <div className="text-right text-xs font-body">
<Link <Link
href={`/forum/forgot-password?email=${encodeURIComponent(email)}`} href={`/forum/forgot-password?email=${encodeURIComponent(email)}`}
className="text-[#888] hover:text-[#F0A623] hover:underline" className="text-[#888] hover:text-[#1D4ED8] hover:underline"
> >
Forgot password? Forgot password?
</Link> </Link>
@@ -115,7 +115,7 @@ function ForumLoginInner() {
No account yet?{' '} No account yet?{' '}
<Link <Link
href={`/forum/register?next=${encodeURIComponent(next)}`} href={`/forum/register?next=${encodeURIComponent(next)}`}
className="text-[#F0A623] hover:underline" className="text-[#1D4ED8] hover:underline"
> >
Create one Create one
</Link> </Link>

View File

@@ -103,27 +103,27 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
const getMetricValue = (thread: ForumThread) => { const getMetricValue = (thread: ForumThread) => {
if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' }; if (activeMetric === 'upvotes') return { value: thread.upvotes ?? 0, label: 'votes', color: 'text-[#22c55e]' };
if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#F0A623]' }; if (activeMetric === 'replies') return { value: thread.reply_count, label: 'replies', color: 'text-[#1D4ED8]' };
return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' }; return { value: thread.view_count, label: 'views', color: 'text-[#f59e0b]' };
}; };
return ( return (
<div className="bg-[#231d18] border border-[#3a322b] rounded-xl overflow-hidden mb-8"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-xl overflow-hidden mb-8">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between px-5 py-4 border-b border-[#3a322b]"> <div className="flex items-center justify-between px-5 py-4 border-b border-[#DCE6F2]">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-lg">🏆</span> <span className="text-lg">🏆</span>
<h2 className="text-white font-heading font-bold text-base">Top Performing Threads</h2> <h2 className="text-white font-heading font-bold text-base">Top Performing Threads</h2>
</div> </div>
{/* Metric Tabs */} {/* Metric Tabs */}
<div className="flex gap-1 bg-[#111] border border-[#3a322b] rounded-lg p-1"> <div className="flex gap-1 bg-[#111] border border-[#DCE6F2] rounded-lg p-1">
{metrics.map(m => ( {metrics.map(m => (
<button <button
key={m.value} key={m.value}
onClick={() => setActiveMetric(m.value)} onClick={() => setActiveMetric(m.value)}
className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${ className={`px-3 py-1 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
activeMetric === m.value activeMetric === m.value
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#666] hover:text-[#aaa]' : 'text-[#666] hover:text-[#aaa]'
}`} }`}
> >
@@ -140,12 +140,12 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
<> <>
{[...Array(5)].map((_, i) => ( {[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center gap-3 px-5 py-3"> <div key={i} className="flex items-center gap-3 px-5 py-3">
<div className="w-6 h-6 bg-[#3a322b] rounded animate-pulse flex-shrink-0" /> <div className="w-6 h-6 bg-[#DCE6F2] rounded animate-pulse flex-shrink-0" />
<div className="flex-1 space-y-1.5"> <div className="flex-1 space-y-1.5">
<div className="h-3.5 bg-[#3a322b] rounded animate-pulse w-3/4" /> <div className="h-3.5 bg-[#DCE6F2] rounded animate-pulse w-3/4" />
<div className="h-2.5 bg-[#1e1e1e] rounded animate-pulse w-1/3" /> <div className="h-2.5 bg-[#1e1e1e] rounded animate-pulse w-1/3" />
</div> </div>
<div className="w-10 h-8 bg-[#3a322b] rounded animate-pulse flex-shrink-0" /> <div className="w-10 h-8 bg-[#DCE6F2] rounded animate-pulse flex-shrink-0" />
</div> </div>
))} ))}
</> </>
@@ -171,13 +171,13 @@ function TopThreadsWidget({ categoryId }: { categoryId?: string }) {
</span> </span>
{/* Thread info */} {/* Thread info */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-white font-body text-sm font-semibold group-hover:text-[#F0A623] transition-colors truncate leading-snug"> <p className="text-white font-body text-sm font-semibold group-hover:text-[#1D4ED8] transition-colors truncate leading-snug">
{thread.title} {thread.title}
</p> </p>
<p className="text-[#555] font-body text-xs mt-0.5"> <p className="text-[#555] font-body text-xs mt-0.5">
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#F0A623] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#666] hover:text-[#1D4ED8] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{thread.forum_categories && ( {thread.forum_categories && (
<> · <span className="text-[#F0A623]">{thread.forum_categories.name}</span></> <> · <span className="text-[#1D4ED8]">{thread.forum_categories.name}</span></>
)} )}
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
@@ -283,10 +283,10 @@ export default function ForumIndexPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
{/* Hero — width-aligned with content below + featured-box pattern */} {/* Hero — width-aligned with content below + featured-box pattern */}
<div className="max-w-container mx-auto px-4"> <div className="max-w-container mx-auto px-4">
<div className="bg-[#231d18] border-b border-[#2a3a50] py-5 px-4"> <div className="bg-[#FFFFFF] border-b border-[#2a3a50] py-5 px-4">
<div className="flex items-start justify-between gap-4 flex-wrap"> <div className="flex items-start justify-between gap-4 flex-wrap">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-3 mb-1">
@@ -301,7 +301,7 @@ export default function ForumIndexPage() {
{currentUserId ? ( {currentUserId ? (
<Link <Link
href="/forum/new" href="/forum/new"
className="inline-flex items-center gap-2 bg-[#F0A623] hover:bg-[#BA7517] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors" className="inline-flex items-center gap-2 bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<path d="M12 5v14M5 12h14" /> <path d="M12 5v14M5 12h14" />
@@ -311,7 +311,7 @@ export default function ForumIndexPage() {
) : ( ) : (
<Link <Link
href="/forum/login?next=/forum" href="/forum/login?next=/forum"
className="inline-flex items-center gap-2 bg-[#F0A623] hover:bg-[#BA7517] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors" className="inline-flex items-center gap-2 bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body font-semibold text-sm rounded-lg px-5 py-2.5 transition-colors"
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M15 12H3" /> <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M15 12H3" />
@@ -329,11 +329,11 @@ export default function ForumIndexPage() {
<div> <div>
{/* Feed Mode Tabs */} {/* Feed Mode Tabs */}
<div className="flex items-center gap-1 bg-[#231d18] border border-[#3a322b] rounded-lg p-1 mb-6 w-fit"> <div className="flex items-center gap-1 bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-1 mb-6 w-fit">
<button <button
onClick={() => setFeedMode('discover')} onClick={() => setFeedMode('discover')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${ className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${
feedMode === 'discover' ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white' feedMode === 'discover' ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
@@ -344,7 +344,7 @@ export default function ForumIndexPage() {
<button <button
onClick={() => setFeedMode('following')} onClick={() => setFeedMode('following')}
className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${ className={`px-4 py-2 rounded-md text-sm font-body font-semibold transition-colors flex items-center gap-2 ${
feedMode === 'following' ? 'bg-[#F0A623] text-white' : 'text-[#888] hover:text-white' feedMode === 'following' ? 'bg-[#1D4ED8] text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
@@ -359,11 +359,11 @@ export default function ForumIndexPage() {
{feedMode === 'following' && ( {feedMode === 'following' && (
<div className="mb-8"> <div className="mb-8">
{!currentUserId && ( {!currentUserId && (
<div className="bg-[#231d18] border border-[#2a3a50] rounded-lg p-8 text-center"> <div className="bg-[#FFFFFF] border border-[#2a3a50] rounded-lg p-8 text-center">
<p className="text-[#aab4c4] font-body text-sm mb-3">Sign in to see threads from people you follow.</p> <p className="text-[#aab4c4] font-body text-sm mb-3">Sign in to see threads from people you follow.</p>
<Link <Link
href="/login" href="/login"
className="inline-block bg-[#F0A623] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#BA7517] transition-colors" className="inline-block bg-[#1D4ED8] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#1E3A8A] transition-colors"
> >
Sign In Sign In
</Link> </Link>
@@ -372,19 +372,19 @@ export default function ForumIndexPage() {
{currentUserId && followingLoading && ( {currentUserId && followingLoading && (
<div className="space-y-2"> <div className="space-y-2">
{[...Array(6)].map((_, i) => ( {[...Array(6)].map((_, i) => (
<div key={i} className="bg-[#231d18] rounded-lg h-16 animate-pulse border border-[#3a322b]" /> <div key={i} className="bg-[#FFFFFF] rounded-lg h-16 animate-pulse border border-[#DCE6F2]" />
))} ))}
</div> </div>
)} )}
{currentUserId && !followingLoading && followingThreads.length === 0 && ( {currentUserId && !followingLoading && followingThreads.length === 0 && (
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg p-10 text-center"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-10 text-center">
<div className="text-3xl mb-3">👥</div> <div className="text-3xl mb-3">👥</div>
<p className="text-[#aaa] font-body text-sm font-semibold mb-1">Your following feed is empty</p> <p className="text-[#aaa] font-body text-sm font-semibold mb-1">Your following feed is empty</p>
<p className="text-[#555] font-body text-sm mb-4">Follow other members to see their threads here.</p> <p className="text-[#555] font-body text-sm mb-4">Follow other members to see their threads here.</p>
<Link <Link
href="/forum" href="/forum"
onClick={() => setFeedMode('discover')} onClick={() => setFeedMode('discover')}
className="inline-block bg-[#F0A623] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#BA7517] transition-colors" className="inline-block bg-[#1D4ED8] text-white font-body text-sm font-bold px-5 py-2 rounded-lg hover:bg-[#1E3A8A] transition-colors"
> >
Discover Members Discover Members
</Link> </Link>
@@ -398,10 +398,10 @@ export default function ForumIndexPage() {
<Link <Link
key={thread.id} key={thread.id}
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="flex items-center gap-4 bg-[#231d18] hover:bg-[#1e2a3a] border border-[#3a322b] hover:border-[#F0A623] rounded-lg px-4 py-3 transition-all group" className="flex items-center gap-4 bg-[#FFFFFF] hover:bg-[#1e2a3a] border border-[#DCE6F2] hover:border-[#1D4ED8] rounded-lg px-4 py-3 transition-all group"
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#F0A623] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#1D4ED8] transition-colors truncate">
{thread.title} {thread.title}
</h3> </h3>
<p className="text-[#666] font-body text-xs mt-0.5"> <p className="text-[#666] font-body text-xs mt-0.5">
@@ -409,7 +409,7 @@ export default function ForumIndexPage() {
<Link <Link
href={`/profile/${thread.author_id}`} href={`/profile/${thread.author_id}`}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
className="text-[#F0A623] hover:underline" className="text-[#1D4ED8] hover:underline"
> >
{thread.author_name} {thread.author_name}
</Link> </Link>
@@ -450,7 +450,7 @@ export default function ForumIndexPage() {
placeholder="Search threads..." placeholder="Search threads..."
value={search} value={search}
onChange={e => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}
className="w-full bg-[#231d18] border border-[#3a322b] focus:border-[#F0A623] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors" className="w-full bg-[#FFFFFF] border border-[#DCE6F2] focus:border-[#1D4ED8] rounded-lg pl-9 pr-4 py-2.5 text-white font-body text-sm placeholder-[#555] focus:outline-none transition-colors"
/> />
{search && ( {search && (
<button <button
@@ -470,7 +470,7 @@ export default function ForumIndexPage() {
<select <select
value={selectedCategory} value={selectedCategory}
onChange={e => setSelectedCategory(e.target.value)} onChange={e => setSelectedCategory(e.target.value)}
className="appearance-none bg-[#231d18] border border-[#3a322b] focus:border-[#F0A623] rounded-lg pl-3 pr-8 py-2.5 text-sm font-body text-white focus:outline-none transition-colors cursor-pointer min-w-[160px]" className="appearance-none bg-[#FFFFFF] border border-[#DCE6F2] focus:border-[#1D4ED8] rounded-lg pl-3 pr-8 py-2.5 text-sm font-body text-white focus:outline-none transition-colors cursor-pointer min-w-[160px]"
> >
<option value="">All Categories</option> <option value="">All Categories</option>
{categories.map(cat => ( {categories.map(cat => (
@@ -483,14 +483,14 @@ export default function ForumIndexPage() {
</div> </div>
{/* Sort Options */} {/* Sort Options */}
<div className="flex gap-1 bg-[#231d18] border border-[#3a322b] rounded-lg p-1"> <div className="flex gap-1 bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-1">
{sortOptions.map(opt => ( {sortOptions.map(opt => (
<button <button
key={opt.value} key={opt.value}
onClick={() => setSort(opt.value)} onClick={() => setSort(opt.value)}
className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${ className={`px-3 py-1.5 rounded-md text-xs font-body font-semibold transition-colors whitespace-nowrap ${
sort === opt.value sort === opt.value
? 'bg-[#F0A623] text-white' ? 'bg-[#1D4ED8] text-white'
: 'text-[#888] hover:text-white' : 'text-[#888] hover:text-white'
}`} }`}
> >
@@ -506,7 +506,7 @@ export default function ForumIndexPage() {
{threadsLoading && ( {threadsLoading && (
<div className="space-y-2"> <div className="space-y-2">
{[...Array(6)].map((_, i) => ( {[...Array(6)].map((_, i) => (
<div key={i} className="bg-[#231d18] rounded-lg h-14 animate-pulse border border-[#3a322b]" /> <div key={i} className="bg-[#FFFFFF] rounded-lg h-14 animate-pulse border border-[#DCE6F2]" />
))} ))}
</div> </div>
)} )}
@@ -523,16 +523,16 @@ export default function ForumIndexPage() {
<Link <Link
key={thread.id} key={thread.id}
href={`/forum/thread/${thread.id}`} href={`/forum/thread/${thread.id}`}
className="flex items-center gap-4 bg-[#231d18] hover:bg-[#1e2a3a] border border-[#3a322b] hover:border-[#F0A623] rounded-lg px-4 py-3 transition-all group" className="flex items-center gap-4 bg-[#FFFFFF] hover:bg-[#1e2a3a] border border-[#DCE6F2] hover:border-[#1D4ED8] rounded-lg px-4 py-3 transition-all group"
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h3 className="text-white font-body font-semibold text-sm group-hover:text-[#F0A623] transition-colors truncate"> <h3 className="text-white font-body font-semibold text-sm group-hover:text-[#1D4ED8] transition-colors truncate">
{thread.title} {thread.title}
</h3> </h3>
<p className="text-[#666] font-body text-xs mt-0.5"> <p className="text-[#666] font-body text-xs mt-0.5">
by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#F0A623] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link> by <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-[#888] hover:text-[#1D4ED8] hover:underline" onClick={(e) => e.stopPropagation()}>{thread.author_name}</Link>
{thread.forum_categories && ( {thread.forum_categories && (
<> · <span className="text-[#F0A623]">{thread.forum_categories.name}</span></> <> · <span className="text-[#1D4ED8]">{thread.forum_categories.name}</span></>
)} )}
{' · '}{timeAgo(thread.created_at)} {' · '}{timeAgo(thread.created_at)}
</p> </p>
@@ -560,7 +560,7 @@ export default function ForumIndexPage() {
{loading && ( {loading && (
<div className="space-y-3 mb-8"> <div className="space-y-3 mb-8">
{[...Array(8)].map((_, i) => ( {[...Array(8)].map((_, i) => (
<div key={i} className="bg-[#231d18] rounded-lg h-20 animate-pulse border border-[#3a322b]" /> <div key={i} className="bg-[#FFFFFF] rounded-lg h-20 animate-pulse border border-[#DCE6F2]" />
))} ))}
</div> </div>
)} )}
@@ -584,11 +584,11 @@ export default function ForumIndexPage() {
<Link <Link
key={cat.id} key={cat.id}
href={`/forum/${cat.slug}`} href={`/forum/${cat.slug}`}
className="flex items-center gap-4 bg-[#231d18] hover:bg-[#1e2a3a] border border-[#3a322b] hover:border-[#F0A623] rounded-lg p-4 transition-all group" className="flex items-center gap-4 bg-[#FFFFFF] hover:bg-[#1e2a3a] border border-[#DCE6F2] hover:border-[#1D4ED8] rounded-lg p-4 transition-all group"
> >
<span className="text-3xl flex-shrink-0 w-10 text-center">{cat.icon}</span> <span className="text-3xl flex-shrink-0 w-10 text-center">{cat.icon}</span>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#F0A623] transition-colors"> <h2 className="text-white font-heading font-semibold text-lg group-hover:text-[#1D4ED8] transition-colors">
{cat.name} {cat.name}
</h2> </h2>
<p className="text-[#888] font-body text-sm mt-0.5 truncate">{cat.description}</p> <p className="text-[#888] font-body text-sm mt-0.5 truncate">{cat.description}</p>
@@ -610,7 +610,7 @@ export default function ForumIndexPage() {
<div className="text-white font-body font-semibold text-sm">{cat.post_count}</div> <div className="text-white font-body font-semibold text-sm">{cat.post_count}</div>
<div className="text-[#666] font-body text-xs">posts</div> <div className="text-[#666] font-body text-xs">posts</div>
</div> </div>
<svg className="w-4 h-4 text-[#555] group-hover:text-[#F0A623] flex-shrink-0 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-[#555] group-hover:text-[#1D4ED8] flex-shrink-0 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg> </svg>
</Link> </Link>
@@ -624,12 +624,12 @@ export default function ForumIndexPage() {
)} )}
{/* Forum guidelines */} {/* Forum guidelines */}
<div className="mt-8 bg-[#231d18] border border-[#2a3a50] rounded-lg p-5"> <div className="mt-8 bg-[#FFFFFF] border border-[#2a3a50] rounded-lg p-5">
<h3 className="text-white font-heading font-semibold mb-2">Community Guidelines</h3> <h3 className="text-white font-heading font-semibold mb-2">Community Guidelines</h3>
<ul className="text-[#aab4c4] font-body text-sm space-y-1"> <ul className="text-[#aab4c4] font-body text-sm space-y-1">
<li> Be respectful and professional this is a community of peers</li> <li> Be respectful and professional this is a community of peers</li>
<li> Share real-world experience and cite sources when possible</li> <li> Share real-world experience and cite sources when possible</li>
<li> The <span className="text-[#F0A623]">AV Beat AI</span> assistant is available in threads to help answer questions it is clearly labeled</li> <li> The <span className="text-[#1D4ED8]">AV Beat AI</span> assistant is available in threads to help answer questions it is clearly labeled</li>
<li> No spam, self-promotion, or vendor pitches without disclosure</li> <li> No spam, self-promotion, or vendor pitches without disclosure</li>
</ul> </ul>
</div> </div>

View File

@@ -73,7 +73,7 @@ export default function EditForumProfilePage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<div className="max-w-2xl mx-auto px-4 py-12 text-[#666] font-body text-sm">Loading</div> <div className="max-w-2xl mx-auto px-4 py-12 text-[#666] font-body text-sm">Loading</div>
</main> </main>
<Footer /> <Footer />
@@ -85,7 +85,7 @@ export default function EditForumProfilePage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<div className="max-w-2xl mx-auto px-4 py-12 text-[#888] font-body text-sm"> <div className="max-w-2xl mx-auto px-4 py-12 text-[#888] font-body text-sm">
Profile unavailable. Profile unavailable.
</div> </div>
@@ -98,13 +98,13 @@ export default function EditForumProfilePage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<div className="max-w-2xl mx-auto px-4 py-8"> <div className="max-w-2xl mx-auto px-4 py-8">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-heading font-bold text-white">Edit Profile</h1> <h1 className="text-2xl font-heading font-bold text-white">Edit Profile</h1>
<Link <Link
href={`/forum/user/${profile.username}`} href={`/forum/user/${profile.username}`}
className="text-xs font-body text-[#F0A623] hover:underline" className="text-xs font-body text-[#1D4ED8] hover:underline"
> >
View public profile View public profile
</Link> </Link>
@@ -112,10 +112,10 @@ export default function EditForumProfilePage() {
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 space-y-4" className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 space-y-4"
> >
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -132,7 +132,7 @@ export default function EditForumProfilePage() {
onChange={(e) => update('username', e.target.value)} onChange={(e) => update('username', e.target.value)}
pattern="[A-Za-z0-9_]{3,24}" pattern="[A-Za-z0-9_]{3,24}"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
<p className="text-[11px] font-body text-[#666] mt-1"> <p className="text-[11px] font-body text-[#666] mt-1">
3-24 characters. Letters, numbers, underscore. 3-24 characters. Letters, numbers, underscore.
@@ -145,7 +145,7 @@ export default function EditForumProfilePage() {
value={profile.display_name} value={profile.display_name}
onChange={(e) => update('display_name', e.target.value)} onChange={(e) => update('display_name', e.target.value)}
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</Field> </Field>
@@ -154,7 +154,7 @@ export default function EditForumProfilePage() {
value={profile.bio || ''} value={profile.bio || ''}
onChange={(e) => update('bio', e.target.value)} onChange={(e) => update('bio', e.target.value)}
rows={4} rows={4}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8] resize-none"
/> />
</Field> </Field>
@@ -164,7 +164,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.role_title || ''} value={profile.role_title || ''}
onChange={(e) => update('role_title', e.target.value)} onChange={(e) => update('role_title', e.target.value)}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</Field> </Field>
<Field label="Company"> <Field label="Company">
@@ -172,7 +172,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.company || ''} value={profile.company || ''}
onChange={(e) => update('company', e.target.value)} onChange={(e) => update('company', e.target.value)}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</Field> </Field>
</div> </div>
@@ -183,7 +183,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.location_city || ''} value={profile.location_city || ''}
onChange={(e) => update('location_city', e.target.value)} onChange={(e) => update('location_city', e.target.value)}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</Field> </Field>
<Field label="Country"> <Field label="Country">
@@ -191,7 +191,7 @@ export default function EditForumProfilePage() {
type="text" type="text"
value={profile.location_country || ''} value={profile.location_country || ''}
onChange={(e) => update('location_country', e.target.value)} onChange={(e) => update('location_country', e.target.value)}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</Field> </Field>
</div> </div>
@@ -202,14 +202,14 @@ export default function EditForumProfilePage() {
value={profile.avatar_url || ''} value={profile.avatar_url || ''}
onChange={(e) => update('avatar_url', e.target.value)} onChange={(e) => update('avatar_url', e.target.value)}
placeholder="https://…" placeholder="https://…"
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</Field> </Field>
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors"
> >
{saving ? 'Saving…' : 'Save Profile'} {saving ? 'Saving…' : 'Save Profile'}
</button> </button>

View File

@@ -101,11 +101,11 @@ function ForumRegisterInner() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-gradient-to-b from-[#1c1815] via-[#1c1815] to-[#1c1815]"> <main className="min-h-screen bg-gradient-to-b from-[#F8FAFC] via-[#F8FAFC] to-[#F8FAFC]">
<div className="max-w-5xl mx-auto px-4 py-12 grid grid-cols-1 lg:grid-cols-[1fr_440px] gap-10 items-start"> <div className="max-w-5xl mx-auto px-4 py-12 grid grid-cols-1 lg:grid-cols-[1fr_440px] gap-10 items-start">
{/* Left rail — what membership gets you */} {/* Left rail — what membership gets you */}
<aside className="hidden lg:block"> <aside className="hidden lg:block">
<div className="text-[10px] uppercase tracking-widest text-[#F0A623] font-mono mb-3">Membership</div> <div className="text-[10px] uppercase tracking-widest text-[#1D4ED8] font-mono mb-3">Membership</div>
<h1 className="font-heading text-white text-4xl xl:text-5xl font-bold leading-tight mb-4"> <h1 className="font-heading text-white text-4xl xl:text-5xl font-bold leading-tight mb-4">
Join AV Beat Join AV Beat
</h1> </h1>
@@ -128,10 +128,10 @@ function ForumRegisterInner() {
</li> </li>
))} ))}
</ul> </ul>
<div className="mt-6 text-[11px] text-[#666] leading-relaxed max-w-md border-t border-[#3a322b] pt-4"> <div className="mt-6 text-[11px] text-[#666] leading-relaxed max-w-md border-t border-[#DCE6F2] pt-4">
<strong className="text-[#888]">PR firms &amp; contributors:</strong> a separate contributor application is required to submit press releases. <strong className="text-[#888]">PR firms &amp; contributors:</strong> a separate contributor application is required to submit press releases.
{" "} {" "}
<Link href="/about/advertise" className="text-[#F0A623] hover:underline">Contact us</Link> if you represent a manufacturer or agency. <Link href="/about/advertise" className="text-[#1D4ED8] hover:underline">Contact us</Link> if you represent a manufacturer or agency.
</div> </div>
</aside> </aside>
@@ -145,14 +145,14 @@ function ForumRegisterInner() {
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 space-y-4 shadow-2xl" className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 space-y-4 shadow-2xl"
> >
<div className="hidden lg:block mb-2"> <div className="hidden lg:block mb-2">
<div className="text-[10px] uppercase tracking-widest text-[#F0A623] font-mono mb-1">Create your account</div> <div className="text-[10px] uppercase tracking-widest text-[#1D4ED8] font-mono mb-1">Create your account</div>
<div className="text-[#888] text-xs">Takes 30 seconds. Free, no card needed.</div> <div className="text-[#888] text-xs">Takes 30 seconds. Free, no card needed.</div>
</div> </div>
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -171,7 +171,7 @@ function ForumRegisterInner() {
onChange={(e) => setFullName(e.target.value)} onChange={(e) => setFullName(e.target.value)}
autoComplete="name" autoComplete="name"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -184,7 +184,7 @@ function ForumRegisterInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -198,7 +198,7 @@ function ForumRegisterInner() {
autoComplete="new-password" autoComplete="new-password"
required required
minLength={6} minLength={6}
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -211,29 +211,29 @@ function ForumRegisterInner() {
onChange={(e) => setConfirm(e.target.value)} onChange={(e) => setConfirm(e.target.value)}
autoComplete="new-password" autoComplete="new-password"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors" className="w-full bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-3 rounded-lg transition-colors"
> >
{loading ? 'Creating account…' : 'Create Free Account'} {loading ? 'Creating account…' : 'Create Free Account'}
</button> </button>
<p className="text-[10px] text-[#666] leading-relaxed text-center px-2"> <p className="text-[10px] text-[#666] leading-relaxed text-center px-2">
By creating an account you agree to our{' '} By creating an account you agree to our{' '}
<Link href="/terms" className="text-[#888] hover:text-[#F0A623] underline">terms</Link> <Link href="/terms" className="text-[#888] hover:text-[#1D4ED8] underline">terms</Link>
{' '}and{' '} {' '}and{' '}
<Link href="/privacy" className="text-[#888] hover:text-[#F0A623] underline">privacy policy</Link>. <Link href="/privacy" className="text-[#888] hover:text-[#1D4ED8] underline">privacy policy</Link>.
We&rsquo;ll never sell your email. We&rsquo;ll never sell your email.
</p> </p>
<SocialAuthButtons next={next} /> <SocialAuthButtons next={next} />
<div className="text-center text-xs font-body text-[#888] pt-2 border-t border-[#3a322b]"> <div className="text-center text-xs font-body text-[#888] pt-2 border-t border-[#DCE6F2]">
Already have an account?{' '} Already have an account?{' '}
<Link <Link
href={`/forum/login?next=${encodeURIComponent(next)}`} href={`/forum/login?next=${encodeURIComponent(next)}`}
className="text-[#F0A623] hover:underline font-semibold" className="text-[#1D4ED8] hover:underline font-semibold"
> >
Sign in Sign in
</Link> </Link>

View File

@@ -40,20 +40,20 @@ function ResetInner() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-gradient-to-b from-[#1c1815] via-[#1c1815] to-[#1c1815]"> <main className="min-h-screen bg-gradient-to-b from-[#F8FAFC] via-[#F8FAFC] to-[#F8FAFC]">
<div className="max-w-md mx-auto px-4 py-16"> <div className="max-w-md mx-auto px-4 py-16">
<div className="text-center mb-6"> <div className="text-center mb-6">
<div className="text-[10px] uppercase tracking-widest text-[#F0A623] font-mono mb-2">Reset password</div> <div className="text-[10px] uppercase tracking-widest text-[#1D4ED8] font-mono mb-2">Reset password</div>
<h1 className="text-3xl font-heading font-bold text-white">Set a new password</h1> <h1 className="text-3xl font-heading font-bold text-white">Set a new password</h1>
</div> </div>
{hasSession === false ? ( {hasSession === false ? (
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 text-center"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 text-center">
<p className="text-[#cbd5e1] mb-4 text-sm"> <p className="text-[#cbd5e1] mb-4 text-sm">
This reset link is expired or invalid. This reset link is expired or invalid.
</p> </p>
<Link <Link
href="/forum/forgot-password" href="/forum/forgot-password"
className="inline-block bg-[#F0A623] hover:bg-[#BA7517] text-white font-semibold text-sm px-5 py-2 rounded" className="inline-block bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-semibold text-sm px-5 py-2 rounded"
> >
Request a new link Request a new link
</Link> </Link>
@@ -61,10 +61,10 @@ function ResetInner() {
) : ( ) : (
<form <form
onSubmit={submit} onSubmit={submit}
className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 space-y-4 shadow-2xl" className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 space-y-4 shadow-2xl"
> >
{err && ( {err && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{err} {err}
</div> </div>
)} )}
@@ -84,7 +84,7 @@ function ResetInner() {
autoComplete="new-password" autoComplete="new-password"
minLength={8} minLength={8}
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -97,13 +97,13 @@ function ResetInner() {
onChange={(e) => setConfirm(e.target.value)} onChange={(e) => setConfirm(e.target.value)}
autoComplete="new-password" autoComplete="new-password"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={busy} disabled={busy}
className="w-full bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-semibold text-sm px-5 py-3 rounded-lg" className="w-full bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-semibold text-sm px-5 py-3 rounded-lg"
> >
{busy ? "Saving…" : "Update password"} {busy ? "Saving…" : "Update password"}
</button> </button>

View File

@@ -67,13 +67,13 @@ function timeAgo(dateStr: string): string {
function Avatar({ name, isAI }: { name: string; isAI: boolean }) { function Avatar({ name, isAI }: { name: string; isAI: boolean }) {
if (isAI) { if (isAI) {
return ( return (
<div className="w-9 h-9 rounded-full bg-[#412402] border border-[#F0A623] flex items-center justify-center flex-shrink-0"> <div className="w-9 h-9 rounded-full bg-[#DCE6F2] border border-[#1D4ED8] flex items-center justify-center flex-shrink-0">
<span className="text-[#F0A623] text-xs font-bold">AI</span> <span className="text-[#1D4ED8] text-xs font-bold">AI</span>
</div> </div>
); );
} }
const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2); const initials = name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
const colors = ['#412402', '#1a3a2a', '#3a1a1a', '#2a1a3a', '#1a2a3a']; const colors = ['#DCE6F2', '#1a3a2a', '#3a1a1a', '#2a1a3a', '#1a2a3a'];
const colorIdx = name.charCodeAt(0) % colors.length; const colorIdx = name.charCodeAt(0) % colors.length;
return ( return (
<div <div
@@ -133,7 +133,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
} }
}; };
const scoreColor = counts.vote_score > 0 ? 'text-[#F0A623]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]'; const scoreColor = counts.vote_score > 0 ? 'text-[#1D4ED8]' : counts.vote_score < 0 ? 'text-red-400' : 'text-[#666]';
return ( return (
<div className="flex items-center gap-1 mt-2"> <div className="flex items-center gap-1 mt-2">
@@ -144,8 +144,8 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
title={!isAuthenticated ? 'Sign in to vote' : isAI ? 'Cannot vote on AI responses' : 'Upvote'} title={!isAuthenticated ? 'Sign in to vote' : isAI ? 'Cannot vote on AI responses' : 'Upvote'}
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-body font-semibold transition-colors border ${ className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-body font-semibold transition-colors border ${
currentVote === 1 currentVote === 1
? 'bg-[#F0A623]/20 border-[#F0A623] text-[#F0A623]' ? 'bg-[#1D4ED8]/20 border-[#1D4ED8] text-[#1D4ED8]'
: 'bg-transparent border-[#3a322b] text-[#666] hover:border-[#F0A623] hover:text-[#F0A623]' : 'bg-transparent border-[#DCE6F2] text-[#666] hover:border-[#1D4ED8] hover:text-[#1D4ED8]'
} disabled:opacity-40 disabled:cursor-not-allowed`} } disabled:opacity-40 disabled:cursor-not-allowed`}
aria-label="Upvote" aria-label="Upvote"
> >
@@ -167,7 +167,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
title={!isAuthenticated ? 'Sign in to vote' : isAI ? 'Cannot vote on AI responses' : 'Downvote'} title={!isAuthenticated ? 'Sign in to vote' : isAI ? 'Cannot vote on AI responses' : 'Downvote'}
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-body font-semibold transition-colors border ${ className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-body font-semibold transition-colors border ${
currentVote === -1 currentVote === -1
? 'bg-red-900/30 border-red-700 text-red-400' :'bg-transparent border-[#3a322b] text-[#666] hover:border-red-700 hover:text-red-400' ? 'bg-red-900/30 border-red-700 text-red-400' :'bg-transparent border-[#DCE6F2] text-[#666] hover:border-red-700 hover:text-red-400'
} disabled:opacity-40 disabled:cursor-not-allowed`} } disabled:opacity-40 disabled:cursor-not-allowed`}
aria-label="Downvote" aria-label="Downvote"
> >
@@ -178,7 +178,7 @@ function VoteButtons({ targetType, targetId, upvotes, downvotes, voteScore, isAI
</button> </button>
{!isAuthenticated && ( {!isAuthenticated && (
<Link href="/login" className="text-xs font-body text-[#555] hover:text-[#F0A623] ml-1 transition-colors"> <Link href="/login" className="text-xs font-body text-[#555] hover:text-[#1D4ED8] ml-1 transition-colors">
Sign in to vote Sign in to vote
</Link> </Link>
)} )}
@@ -300,12 +300,12 @@ export default function ForumThreadPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<aside className="float-right ml-6 mb-6 hidden lg:block max-w-[300px]"><SidebarAdStack /></aside> <aside className="float-right ml-6 mb-6 hidden lg:block max-w-[300px]"><SidebarAdStack /></aside>
<div className="max-w-container mx-auto px-4 py-10 space-y-4"> <div className="max-w-container mx-auto px-4 py-10 space-y-4">
<div className="h-8 bg-[#231d18] rounded animate-pulse w-2/3" /> <div className="h-8 bg-[#FFFFFF] rounded animate-pulse w-2/3" />
<div className="h-32 bg-[#231d18] rounded animate-pulse" /> <div className="h-32 bg-[#FFFFFF] rounded animate-pulse" />
<div className="h-20 bg-[#231d18] rounded animate-pulse" /> <div className="h-20 bg-[#FFFFFF] rounded animate-pulse" />
</div> </div>
</main> </main>
<Footer /> <Footer />
@@ -317,12 +317,12 @@ export default function ForumThreadPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<div className="max-w-container mx-auto px-4 py-10"> <div className="max-w-container mx-auto px-4 py-10">
<div className="bg-red-900/20 border border-red-800 rounded-lg p-4 text-red-400 font-body"> <div className="bg-red-900/20 border border-red-800 rounded-lg p-4 text-red-400 font-body">
{error || 'Thread not found.'} {error || 'Thread not found.'}
</div> </div>
<Link href="/forum" className="mt-4 inline-block text-[#F0A623] hover:underline font-body text-sm"> <Link href="/forum" className="mt-4 inline-block text-[#1D4ED8] hover:underline font-body text-sm">
Back to Forum Back to Forum
</Link> </Link>
</div> </div>
@@ -337,16 +337,16 @@ export default function ForumThreadPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
{/* Breadcrumb */} {/* Breadcrumb */}
<div className="bg-[#231d18] border-b border-[#2a3a50]"> <div className="bg-[#FFFFFF] border-b border-[#2a3a50]">
<div className="max-w-container mx-auto px-4 py-4"> <div className="max-w-container mx-auto px-4 py-4">
<nav className="text-sm font-body text-[#666]"> <nav className="text-sm font-body text-[#666]">
<Link href="/forum" className="hover:text-[#F0A623] transition-colors">Forum</Link> <Link href="/forum" className="hover:text-[#1D4ED8] transition-colors">Forum</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
{category && ( {category && (
<> <>
<Link href={`/forum/${category.slug}`} className="hover:text-[#F0A623] transition-colors">{category.name}</Link> <Link href={`/forum/${category.slug}`} className="hover:text-[#1D4ED8] transition-colors">{category.name}</Link>
<span className="mx-2"></span> <span className="mx-2"></span>
</> </>
)} )}
@@ -361,12 +361,12 @@ export default function ForumThreadPage() {
<h1 className="text-2xl font-heading font-bold text-white mb-6 leading-snug">{thread.title}</h1> <h1 className="text-2xl font-heading font-bold text-white mb-6 leading-snug">{thread.title}</h1>
{/* Original Post */} {/* Original Post */}
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg p-5 mb-4"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-5 mb-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Avatar name={thread.author_name} isAI={false} /> <Avatar name={thread.author_name} isAI={false} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#F0A623] hover:underline">{thread.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(thread.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#1D4ED8] hover:underline">{thread.author_name}</Link>
<span className="text-[#555] font-body text-xs">·</span> <span className="text-[#555] font-body text-xs">·</span>
<span className="text-[#555] font-body text-xs">{timeAgo(thread.created_at)}</span> <span className="text-[#555] font-body text-xs">{timeAgo(thread.created_at)}</span>
<span className="text-[#555] font-body text-xs">·</span> <span className="text-[#555] font-body text-xs">·</span>
@@ -395,17 +395,17 @@ export default function ForumThreadPage() {
<div <div
className={`border rounded-lg p-5 ${ className={`border rounded-lg p-5 ${
reply.is_ai_response reply.is_ai_response
? 'bg-[#0d1f35] border-[#412402]' ? 'bg-[#0d1f35] border-[#DCE6F2]'
: 'bg-[#231d18] border-[#3a322b]' : 'bg-[#FFFFFF] border-[#DCE6F2]'
}`} }`}
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Avatar name={reply.author_name} isAI={reply.is_ai_response} /> <Avatar name={reply.author_name} isAI={reply.is_ai_response} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2 flex-wrap"> <div className="flex items-center gap-2 mb-2 flex-wrap">
<Link href={`/forum/user/${encodeURIComponent(reply.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#F0A623] hover:underline">{reply.author_name}</Link> <Link href={`/forum/user/${encodeURIComponent(reply.author_name)}`} className="text-white font-body font-semibold text-sm hover:text-[#1D4ED8] hover:underline">{reply.author_name}</Link>
{reply.is_ai_response && ( {reply.is_ai_response && (
<span className="text-xs bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/30 px-2 py-0.5 rounded font-body font-semibold"> <span className="text-xs bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/30 px-2 py-0.5 rounded font-body font-semibold">
AI Assistant AI Assistant
</span> </span>
)} )}
@@ -441,14 +441,14 @@ export default function ForumThreadPage() {
)} )}
{/* AI Assistant Panel */} {/* AI Assistant Panel */}
<div className="bg-[#0d1f35] border border-[#412402] rounded-lg p-5 mb-6"> <div className="bg-[#0d1f35] border border-[#DCE6F2] rounded-lg p-5 mb-6">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<div className="w-7 h-7 rounded-full bg-[#412402] border border-[#F0A623] flex items-center justify-center"> <div className="w-7 h-7 rounded-full bg-[#DCE6F2] border border-[#1D4ED8] flex items-center justify-center">
<span className="text-[#F0A623] text-xs font-bold">AI</span> <span className="text-[#1D4ED8] text-xs font-bold">AI</span>
</div> </div>
<div> <div>
<span className="text-white font-body font-semibold text-sm">AV Beat AI Assistant</span> <span className="text-white font-body font-semibold text-sm">AV Beat AI Assistant</span>
<span className="ml-2 text-xs bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/30 px-2 py-0.5 rounded font-body">AI Not a human</span> <span className="ml-2 text-xs bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/30 px-2 py-0.5 rounded font-body">AI Not a human</span>
</div> </div>
</div> </div>
<p className="text-[#7a9ab8] font-body text-xs mb-3"> <p className="text-[#7a9ab8] font-body text-xs mb-3">
@@ -461,12 +461,12 @@ export default function ForumThreadPage() {
value={aiQuestion} value={aiQuestion}
onChange={e => setAiQuestion(e.target.value)} onChange={e => setAiQuestion(e.target.value)}
disabled={aiLoading} disabled={aiLoading}
className="flex-1 bg-[#111] border border-[#2a3a50] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#3a5a7a] focus:outline-none focus:border-[#F0A623] disabled:opacity-50" className="flex-1 bg-[#111] border border-[#2a3a50] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#3a5a7a] focus:outline-none focus:border-[#1D4ED8] disabled:opacity-50"
/> />
<button <button
type="submit" type="submit"
disabled={aiLoading || !aiQuestion.trim()} disabled={aiLoading || !aiQuestion.trim()}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors flex-shrink-0" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-4 py-2 rounded-lg transition-colors flex-shrink-0"
> >
{aiLoading ? ( {aiLoading ? (
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
@@ -486,7 +486,7 @@ export default function ForumThreadPage() {
{/* Reply Form — auth-gated */} {/* Reply Form — auth-gated */}
{thread.status !== 'closed' && isAuthenticated && ( {thread.status !== 'closed' && isAuthenticated && (
<div className="bg-[#231d18] border border-[#3a322b] rounded-lg p-5"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-5">
<h3 className="text-white font-heading font-semibold mb-4">Post a Reply</h3> <h3 className="text-white font-heading font-semibold mb-4">Post a Reply</h3>
<form onSubmit={handleReply} className="space-y-3"> <form onSubmit={handleReply} className="space-y-3">
<textarea <textarea
@@ -495,21 +495,21 @@ export default function ForumThreadPage() {
onChange={e => setReplyBody(e.target.value)} onChange={e => setReplyBody(e.target.value)}
required required
rows={5} rows={5}
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#F0A623] resize-none" className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#1D4ED8] resize-none"
/> />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<ImageAttachButton onInsert={(md) => setReplyBody((b) => (b || "") + md)} /> <ImageAttachButton onInsert={(md) => setReplyBody((b) => (b || "") + md)} />
<span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span> <span className="text-[10px] text-[#666]">Auto-optimized, never &gt; 20 MB</span>
</div> </div>
{replyError && ( {replyError && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{replyError} {replyError}
</div> </div>
)} )}
<button <button
type="submit" type="submit"
disabled={submitting || !replyBody.trim()} disabled={submitting || !replyBody.trim()}
className="bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
{submitting ? 'Posting...' : 'Post Reply'} {submitting ? 'Posting...' : 'Post Reply'}
</button> </button>
@@ -518,7 +518,7 @@ export default function ForumThreadPage() {
)} )}
{thread.status !== 'closed' && !isAuthenticated && ( {thread.status !== 'closed' && !isAuthenticated && (
<div className="bg-[#0d1f35] border border-[#412402] rounded-lg p-5 text-center"> <div className="bg-[#0d1f35] border border-[#DCE6F2] rounded-lg p-5 text-center">
<h3 className="text-white font-heading font-semibold mb-1">Join the discussion</h3> <h3 className="text-white font-heading font-semibold mb-1">Join the discussion</h3>
<p className="text-[#7a9ab8] font-body text-sm mb-4"> <p className="text-[#7a9ab8] font-body text-sm mb-4">
Sign in or create a free account to post replies. Sign in or create a free account to post replies.
@@ -526,13 +526,13 @@ export default function ForumThreadPage() {
<div className="flex items-center justify-center gap-3"> <div className="flex items-center justify-center gap-3">
<Link <Link
href={`/forum/login?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`} href={`/forum/login?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`}
className="bg-[#F0A623] hover:bg-[#BA7517] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
Sign In Sign In
</Link> </Link>
<Link <Link
href={`/forum/register?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`} href={`/forum/register?next=${encodeURIComponent(`/forum/thread/${threadId}`)}`}
className="border border-[#F0A623] text-[#F0A623] hover:bg-[#F0A623]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors" className="border border-[#1D4ED8] text-[#1D4ED8] hover:bg-[#1D4ED8]/10 font-body font-semibold text-sm px-5 py-2 rounded-lg transition-colors"
> >
Register Register
</Link> </Link>
@@ -541,7 +541,7 @@ export default function ForumThreadPage() {
)} )}
{thread.status === 'closed' && ( {thread.status === 'closed' && (
<div className="bg-[#231d18] border border-[#333] rounded-lg p-4 text-center text-[#666] font-body text-sm"> <div className="bg-[#FFFFFF] border border-[#333] rounded-lg p-4 text-center text-[#666] font-body text-sm">
This thread is closed to new replies. This thread is closed to new replies.
</div> </div>
)} )}

View File

@@ -107,7 +107,7 @@ export default function ForumUserPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="bg-[#1c1815] min-h-screen"> <main className="bg-[#F8FAFC] min-h-screen">
<div className="max-w-container mx-auto px-4 py-12"> <div className="max-w-container mx-auto px-4 py-12">
<p className="font-body text-sm text-[#666]">Loading profile</p> <p className="font-body text-sm text-[#666]">Loading profile</p>
</div> </div>
@@ -120,11 +120,11 @@ export default function ForumUserPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="bg-[#1c1815] min-h-screen"> <main className="bg-[#F8FAFC] min-h-screen">
<div className="max-w-container mx-auto px-4 py-12"> <div className="max-w-container mx-auto px-4 py-12">
<h1 className="font-heading text-2xl font-bold text-[#FBEFE0] mb-2">User not found</h1> <h1 className="font-heading text-2xl font-bold text-[#0F172A] mb-2">User not found</h1>
<p className="font-body text-sm text-[#888]">No forum member with username "{username}".</p> <p className="font-body text-sm text-[#888]">No forum member with username "{username}".</p>
<Link href="/forum" className="font-body text-sm text-[#F0A623] hover:underline mt-4 inline-block"> Back to The Crew Lounge</Link> <Link href="/forum" className="font-body text-sm text-[#1D4ED8] hover:underline mt-4 inline-block"> Back to The Crew Lounge</Link>
</div> </div>
</main> </main>
<Footer /> <Footer />
@@ -139,16 +139,16 @@ export default function ForumUserPage() {
return ( return (
<> <>
<Header /> <Header />
<main className="bg-[#1c1815] min-h-screen"> <main className="bg-[#F8FAFC] min-h-screen">
<div className="max-w-container mx-auto px-4 py-8"> <div className="max-w-container mx-auto px-4 py-8">
{/* Header card */} {/* Header card */}
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6 mb-6"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-6 mb-6">
<div className="flex flex-col sm:flex-row sm:items-start gap-5"> <div className="flex flex-col sm:flex-row sm:items-start gap-5">
<div className="w-20 h-20 sm:w-24 sm:h-24 rounded-full overflow-hidden border-2 border-[#3a322b] flex-shrink-0 bg-[#1c1815] flex items-center justify-center"> <div className="w-20 h-20 sm:w-24 sm:h-24 rounded-full overflow-hidden border-2 border-[#DCE6F2] flex-shrink-0 bg-[#F8FAFC] flex items-center justify-center">
{profile.avatar_url ? ( {profile.avatar_url ? (
<AppImage src={profile.avatar_url} alt={profile.display_name} width={96} height={96} className="w-full h-full object-cover" /> <AppImage src={profile.avatar_url} alt={profile.display_name} width={96} height={96} className="w-full h-full object-cover" />
) : ( ) : (
<span className="font-heading text-2xl font-bold text-[#F0A623]"> <span className="font-heading text-2xl font-bold text-[#1D4ED8]">
{profile.display_name.charAt(0).toUpperCase()} {profile.display_name.charAt(0).toUpperCase()}
</span> </span>
)} )}
@@ -156,19 +156,19 @@ export default function ForumUserPage() {
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-3 flex-wrap"> <div className="flex items-center justify-between gap-3 flex-wrap">
<h1 className="font-heading text-2xl md:text-3xl font-bold text-[#FBEFE0] leading-tight"> <h1 className="font-heading text-2xl md:text-3xl font-bold text-[#0F172A] leading-tight">
{profile.display_name} {profile.display_name}
</h1> </h1>
{isOwnProfile && ( {isOwnProfile && (
<Link <Link
href="/forum/profile/edit" href="/forum/profile/edit"
className="text-xs font-body font-bold uppercase tracking-wider bg-[#F0A623] hover:bg-[#BA7517] text-white px-3 py-1.5 rounded-sm transition-colors" className="text-xs font-body font-bold uppercase tracking-wider bg-[#1D4ED8] hover:bg-[#1E3A8A] text-white px-3 py-1.5 rounded-sm transition-colors"
> >
Edit Profile Edit Profile
</Link> </Link>
)} )}
</div> </div>
<p className="font-body text-sm text-[#F0A623] mt-0.5">@{profile.username}</p> <p className="font-body text-sm text-[#1D4ED8] mt-0.5">@{profile.username}</p>
<div className="flex flex-wrap items-center gap-2 mt-3"> <div className="flex flex-wrap items-center gap-2 mt-3">
{archetypeLabel && ( {archetypeLabel && (
@@ -198,7 +198,7 @@ export default function ForumUserPage() {
</div> </div>
{profile.bio && ( {profile.bio && (
<p className="font-body text-sm text-[#bfbfbf] leading-relaxed mt-5 pt-5 border-t border-[#3a322b]"> <p className="font-body text-sm text-[#bfbfbf] leading-relaxed mt-5 pt-5 border-t border-[#DCE6F2]">
{profile.bio} {profile.bio}
</p> </p>
)} )}
@@ -206,7 +206,7 @@ export default function ForumUserPage() {
{profile.expertise_tags && profile.expertise_tags.length > 0 && ( {profile.expertise_tags && profile.expertise_tags.length > 0 && (
<div className="flex flex-wrap gap-2 mt-4"> <div className="flex flex-wrap gap-2 mt-4">
{profile.expertise_tags.map((tag) => ( {profile.expertise_tags.map((tag) => (
<span key={tag} className="font-body text-[11px] text-[#9ca3af] bg-[#222] border border-[#3a322b] px-2 py-0.5 rounded-sm"> <span key={tag} className="font-body text-[11px] text-[#9ca3af] bg-[#222] border border-[#DCE6F2] px-2 py-0.5 rounded-sm">
#{tag} #{tag}
</span> </span>
))} ))}
@@ -217,7 +217,7 @@ export default function ForumUserPage() {
{/* Activity grid */} {/* Activity grid */}
<div className="grid grid-cols-1 lg:grid-cols-[2fr,1fr] gap-6"> <div className="grid grid-cols-1 lg:grid-cols-[2fr,1fr] gap-6">
<div> <div>
<h2 className="font-heading text-lg font-bold text-[#FBEFE0] mb-3"> <h2 className="font-heading text-lg font-bold text-[#0F172A] mb-3">
Threads started ({profile.thread_count}) Threads started ({profile.thread_count})
</h2> </h2>
{threads.length === 0 ? ( {threads.length === 0 ? (
@@ -225,8 +225,8 @@ export default function ForumUserPage() {
) : ( ) : (
<ul className="space-y-2 mb-8"> <ul className="space-y-2 mb-8">
{threads.map((t) => ( {threads.map((t) => (
<li key={t.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-3 hover:border-[#F0A623]/50 transition-colors"> <li key={t.id} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-3 hover:border-[#1D4ED8]/50 transition-colors">
<Link href={`/forum/thread/${t.id}`} className="font-body text-sm font-semibold text-[#e0e0e0] hover:text-[#F0A623] block"> <Link href={`/forum/thread/${t.id}`} className="font-body text-sm font-semibold text-[#e0e0e0] hover:text-[#1D4ED8] block">
{t.title} {t.title}
</Link> </Link>
<div className="flex items-center gap-3 mt-1 text-[11px] text-[#666]"> <div className="flex items-center gap-3 mt-1 text-[11px] text-[#666]">
@@ -243,7 +243,7 @@ export default function ForumUserPage() {
</ul> </ul>
)} )}
<h2 className="font-heading text-lg font-bold text-[#FBEFE0] mb-3"> <h2 className="font-heading text-lg font-bold text-[#0F172A] mb-3">
Recent replies Recent replies
</h2> </h2>
{replies.length === 0 ? ( {replies.length === 0 ? (
@@ -251,8 +251,8 @@ export default function ForumUserPage() {
) : ( ) : (
<ul className="space-y-2"> <ul className="space-y-2">
{replies.map((r) => ( {replies.map((r) => (
<li key={r.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-3"> <li key={r.id} className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-3">
<Link href={`/forum/thread/${r.thread_id}`} className="font-body text-xs font-semibold text-[#F0A623] hover:underline"> <Link href={`/forum/thread/${r.thread_id}`} className="font-body text-xs font-semibold text-[#1D4ED8] hover:underline">
{r.forum_threads?.title || "(thread)"} {r.forum_threads?.title || "(thread)"}
</Link> </Link>
<p className="font-body text-sm text-[#bfbfbf] leading-relaxed mt-1 line-clamp-3">{r.body}</p> <p className="font-body text-sm text-[#bfbfbf] leading-relaxed mt-1 line-clamp-3">{r.body}</p>
@@ -266,7 +266,7 @@ export default function ForumUserPage() {
</div> </div>
<aside> <aside>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 sticky top-4"> <div className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-sm p-4 sticky top-4">
<h3 className="font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-2">Profile facts</h3> <h3 className="font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-2">Profile facts</h3>
<dl className="text-sm space-y-2"> <dl className="text-sm space-y-2">
<div><dt className="text-[#666] text-[11px] uppercase tracking-wider">Role</dt><dd className="text-[#cccccc]">{profile.role_title || "—"}</dd></div> <div><dt className="text-[#666] text-[11px] uppercase tracking-wider">Role</dt><dd className="text-[#cccccc]">{profile.role_title || "—"}</dd></div>

View File

@@ -34,7 +34,7 @@ export default async function GearPage() {
<div className="max-w-container mx-auto px-4"> <div className="max-w-container mx-auto px-4">
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
<span className="section-label">Gear &amp; Reviews</span> <span className="section-label">Gear &amp; Reviews</span>
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
<h1 className="font-heading text-white text-3xl font-bold">Production Gear &amp; Reviews</h1> <h1 className="font-heading text-white text-3xl font-bold">Production Gear &amp; Reviews</h1>
<p className="text-[#777] font-body text-sm mt-2">In-depth reviews and coverage of cameras, audio, infrastructure, and production technology</p> <p className="text-[#777] font-body text-sm mt-2">In-depth reviews and coverage of cameras, audio, infrastructure, and production technology</p>
@@ -49,7 +49,7 @@ export default async function GearPage() {
<Link <Link
key={article.slug} key={article.slug}
href={`/articles/${article.slug}`} href={`/articles/${article.slug}`}
className="bg-[#111] border border-[#222] overflow-hidden group hover:border-[#F0A623] transition-colors"> className="bg-[#111] border border-[#222] overflow-hidden group hover:border-[#1D4ED8] transition-colors">
<div className="relative h-[200px] overflow-hidden"> <div className="relative h-[200px] overflow-hidden">
<AppImage <AppImage
src={article.image} src={article.image}
@@ -59,7 +59,7 @@ export default async function GearPage() {
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
/> />
<div className="absolute top-2 left-2"> <div className="absolute top-2 left-2">
<span className="bg-[#F0A623] text-white font-body text-[9px] font-bold px-2 py-0.5 tracking-wider uppercase"> <span className="bg-[#1D4ED8] text-white font-body text-[9px] font-bold px-2 py-0.5 tracking-wider uppercase">
{article.category} {article.category}
</span> </span>
</div> </div>
@@ -74,13 +74,13 @@ export default async function GearPage() {
)} )}
<span className="text-[#555] font-body text-[11px]">{article.readTime}</span> <span className="text-[#555] font-body text-[11px]">{article.readTime}</span>
</div> </div>
<h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-2 group-hover:text-[#F0A623] transition-colors line-clamp-3"> <h2 className="font-heading text-[#e0e0e0] text-sm font-bold leading-snug mb-2 group-hover:text-[#1D4ED8] transition-colors line-clamp-3">
{article.title} {article.title}
</h2> </h2>
<p className="text-[#666] font-body text-xs leading-relaxed line-clamp-2">{article.excerpt}</p> <p className="text-[#666] font-body text-xs leading-relaxed line-clamp-2">{article.excerpt}</p>
<div className="mt-3 flex flex-wrap gap-1"> <div className="mt-3 flex flex-wrap gap-1">
{article.tags.slice(0, 3).map((tag) => ( {article.tags.slice(0, 3).map((tag) => (
<span key={tag} className="px-1.5 py-0.5 bg-[#231d18] border border-[#3a322b] text-[#666] text-[10px] font-body rounded-sm"> <span key={tag} className="px-1.5 py-0.5 bg-[#FFFFFF] border border-[#DCE6F2] text-[#666] text-[10px] font-body rounded-sm">
{tag} {tag}
</span> </span>
))} ))}

View File

@@ -13,7 +13,7 @@ export default function GlobalError({
}, [error]); }, [error]);
return ( return (
<html> <html>
<body style={{ background: "#1c1815", color: "#e0e0e0", fontFamily: "system-ui", padding: 24, minHeight: "100vh" }}> <body style={{ background: "#F8FAFC", color: "#e0e0e0", fontFamily: "system-ui", padding: 24, minHeight: "100vh" }}>
<div style={{ maxWidth: 480, margin: "10vh auto", textAlign: "center" }}> <div style={{ maxWidth: 480, margin: "10vh auto", textAlign: "center" }}>
<h2 style={{ fontSize: 22, marginBottom: 12 }}>Something went wrong</h2> <h2 style={{ fontSize: 22, marginBottom: 12 }}>Something went wrong</h2>
<p style={{ marginBottom: 12, color: "#888" }}>AV Beat hit an unexpected error.</p> <p style={{ marginBottom: 12, color: "#888" }}>AV Beat hit an unexpected error.</p>
@@ -22,7 +22,7 @@ export default function GlobalError({
)} )}
<button <button
onClick={() => reset()} onClick={() => reset()}
style={{ padding: "8px 16px", background: "#F0A623", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }} style={{ padding: "8px 16px", background: "#1D4ED8", color: "white", border: 0, cursor: "pointer", borderRadius: 2 }}
> >
Try again Try again
</button> </button>

View File

@@ -305,7 +305,7 @@ export default function ArticleFeed() {
{/* Section header */} {/* Section header */}
<div className="flex items-center gap-3 mb-4 md:mb-5 flex-wrap"> <div className="flex items-center gap-3 mb-4 md:mb-5 flex-wrap">
<span className="section-label">Industry News</span> <span className="section-label">Industry News</span>
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
{/* Sort dropdown */} {/* Sort dropdown */}
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="font-body text-[10px] text-[#555] uppercase tracking-wide hidden sm:inline">Sort:</span> <span className="font-body text-[10px] text-[#555] uppercase tracking-wide hidden sm:inline">Sort:</span>
@@ -314,7 +314,7 @@ export default function ArticleFeed() {
value={sortOrder} value={sortOrder}
onChange={(e) => { setSortOrder(e.target.value as SortOrder); setPage(1); setInfiniteCount(ARTICLES_PER_PAGE); }} onChange={(e) => { setSortOrder(e.target.value as SortOrder); setPage(1); setInfiniteCount(ARTICLES_PER_PAGE); }}
aria-label="Sort articles" aria-label="Sort articles"
className="bg-[#111] border border-[#3a322b] text-[#aaa] font-body text-[11px] font-semibold uppercase tracking-wide rounded-sm pl-2.5 pr-7 py-1 appearance-none focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]" className="bg-[#111] border border-[#DCE6F2] text-[#aaa] font-body text-[11px] font-semibold uppercase tracking-wide rounded-sm pl-2.5 pr-7 py-1 appearance-none focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8]/30 transition-colors [color-scheme:dark] cursor-pointer hover:border-[#3a3a3a]"
> >
<option value="recent">Recent</option> <option value="recent">Recent</option>
<option value="trending">Trending</option> <option value="trending">Trending</option>
@@ -328,13 +328,13 @@ export default function ArticleFeed() {
{/* Feed mode toggle */} {/* Feed mode toggle */}
<div className="flex items-center gap-1.5" role="group" aria-label="Feed display mode"> <div className="flex items-center gap-1.5" role="group" aria-label="Feed display mode">
<span className="font-body text-[10px] text-[#555] uppercase tracking-wide hidden sm:inline">View:</span> <span className="font-body text-[10px] text-[#555] uppercase tracking-wide hidden sm:inline">View:</span>
<div className="flex items-center bg-[#111] border border-[#3a322b] rounded-sm p-0.5"> <div className="flex items-center bg-[#111] border border-[#DCE6F2] rounded-sm p-0.5">
<button <button
onClick={() => handleFeedModeChange("pagination")} onClick={() => handleFeedModeChange("pagination")}
aria-pressed={feedMode === "pagination"} aria-pressed={feedMode === "pagination"}
title="Pagination mode" title="Pagination mode"
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${ className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
feedMode === "pagination" ?"bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" feedMode === "pagination" ?"bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}> }`}>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true"> <svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="1" y="1" width="10" height="2.5" rx="0.5" fill="currentColor" opacity="0.9"/> <rect x="1" y="1" width="10" height="2.5" rx="0.5" fill="currentColor" opacity="0.9"/>
@@ -347,8 +347,8 @@ export default function ArticleFeed() {
onClick={() => handleFeedModeChange("infinite")} onClick={() => handleFeedModeChange("infinite")}
aria-pressed={feedMode === "infinite"} aria-pressed={feedMode === "infinite"}
title="Infinite scroll mode" title="Infinite scroll mode"
className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${ className={`flex items-center gap-1 px-2.5 py-1 rounded-sm font-body text-[10px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
feedMode === "infinite" ?"bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" feedMode === "infinite" ?"bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}> }`}>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true"> <svg width="11" height="11" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="1" y="1" width="10" height="2" rx="0.5" fill="currentColor" opacity="0.9"/> <rect x="1" y="1" width="10" height="2" rx="0.5" fill="currentColor" opacity="0.9"/>
@@ -372,7 +372,7 @@ export default function ArticleFeed() {
value={dateFrom} value={dateFrom}
onChange={(e) => { setDateFrom(e.target.value); setPage(1); }} onChange={(e) => { setDateFrom(e.target.value); setPage(1); }}
max={dateTo || undefined} max={dateTo || undefined}
className="bg-[#231d18] border border-[#3a322b] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#F0A623] transition-colors [color-scheme:dark]" className="bg-[#FFFFFF] border border-[#DCE6F2] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#1D4ED8] transition-colors [color-scheme:dark]"
title="From date" title="From date"
aria-label="Filter articles from date" aria-label="Filter articles from date"
/> />
@@ -382,7 +382,7 @@ export default function ArticleFeed() {
value={dateTo} value={dateTo}
onChange={(e) => { setDateTo(e.target.value); setPage(1); }} onChange={(e) => { setDateTo(e.target.value); setPage(1); }}
min={dateFrom || undefined} min={dateFrom || undefined}
className="bg-[#231d18] border border-[#3a322b] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#F0A623] transition-colors [color-scheme:dark]" className="bg-[#FFFFFF] border border-[#DCE6F2] text-[#888] text-xs font-body px-3 py-2 rounded-sm focus:outline-none focus:border-[#1D4ED8] transition-colors [color-scheme:dark]"
title="To date" title="To date"
aria-label="Filter articles to date" aria-label="Filter articles to date"
/> />
@@ -401,17 +401,17 @@ export default function ArticleFeed() {
onClick={() => setShowAdvanced((v) => !v)} onClick={() => setShowAdvanced((v) => !v)}
aria-expanded={showAdvanced} aria-expanded={showAdvanced}
aria-controls={`${feedId}-advanced`} aria-controls={`${feedId}-advanced`}
className={`flex items-center gap-1.5 px-3 py-2 rounded-sm border font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] whitespace-nowrap ${ className={`flex items-center gap-1.5 px-3 py-2 rounded-sm border font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] whitespace-nowrap ${
showAdvanced || hasAdvancedActive showAdvanced || hasAdvancedActive
? "bg-[#F0A623]/15 text-[#F0A623] border-[#F0A623]/40" ? "bg-[#1D4ED8]/15 text-[#1D4ED8] border-[#1D4ED8]/40"
: "bg-[#111] text-[#666] border-[#3a322b] hover:text-[#aaa] hover:border-[#3a3a3a]" : "bg-[#111] text-[#666] border-[#DCE6F2] hover:text-[#aaa] hover:border-[#3a3a3a]"
}`}> }`}>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<path d="M1 3h10M3 6h6M5 9h2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/> <path d="M1 3h10M3 6h6M5 9h2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
</svg> </svg>
More More
{hasAdvancedActive && ( {hasAdvancedActive && (
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] inline-block ml-0.5" aria-label="Active filters" /> <span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] inline-block ml-0.5" aria-label="Active filters" />
)} )}
</button> </button>
</div> </div>
@@ -428,10 +428,10 @@ export default function ArticleFeed() {
type="button" type="button"
onClick={() => { setActiveCategory(cat); setPage(1); }} onClick={() => { setActiveCategory(cat); setPage(1); }}
aria-pressed={isActive} aria-pressed={isActive}
className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${ className={`px-3 py-1.5 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
isActive isActive
? "bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/50" ? "bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/50"
: "bg-[#111] text-[#888] border border-[#3a322b] hover:text-[#ccc] hover:border-[#3a3a3a]" : "bg-[#111] text-[#888] border border-[#DCE6F2] hover:text-[#ccc] hover:border-[#3a3a3a]"
}`}> }`}>
{cat} {cat}
</button> </button>
@@ -444,7 +444,7 @@ export default function ArticleFeed() {
{showAdvanced && ( {showAdvanced && (
<div <div
id={`${feedId}-advanced`} id={`${feedId}-advanced`}
className="bg-[#1c1815] border border-[#3a322b] rounded-sm p-3 space-y-3" className="bg-[#F8FAFC] border border-[#DCE6F2] rounded-sm p-3 space-y-3"
role="group" role="group"
aria-label="Advanced filters"> aria-label="Advanced filters">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
@@ -458,7 +458,7 @@ export default function ArticleFeed() {
id={`${feedId}-author`} id={`${feedId}-author`}
value={authorFilter} value={authorFilter}
onChange={(e) => { setAuthorFilter(e.target.value); setPage(1); }} onChange={(e) => { setAuthorFilter(e.target.value); setPage(1); }}
className="w-full bg-[#111] border border-[#3a322b] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#F0A623] focus:ring-1 focus:ring-[#F0A623]/30 transition-colors [color-scheme:dark]" className="w-full bg-[#111] border border-[#DCE6F2] text-[#ccc] font-body text-sm rounded-sm px-3 py-1.5 pr-8 appearance-none focus:outline-none focus:border-[#1D4ED8] focus:ring-1 focus:ring-[#1D4ED8]/30 transition-colors [color-scheme:dark]"
aria-label="Filter by author"> aria-label="Filter by author">
<option value="All">All Authors</option> <option value="All">All Authors</option>
{uniqueAuthors.map((author) => ( {uniqueAuthors.map((author) => (
@@ -475,7 +475,7 @@ export default function ArticleFeed() {
{/* Source filter inside advanced panel */} {/* Source filter inside advanced panel */}
<div className="space-y-1"> <div className="space-y-1">
<span className="font-body text-[10px] text-[#555] uppercase tracking-wide block">Source</span> <span className="font-body text-[10px] text-[#555] uppercase tracking-wide block">Source</span>
<div className="flex items-center gap-1 bg-[#111] border border-[#3a322b] rounded-sm p-0.5 w-fit" role="group" aria-label="Filter by source"> <div className="flex items-center gap-1 bg-[#111] border border-[#DCE6F2] rounded-sm p-0.5 w-fit" role="group" aria-label="Filter by source">
{SOURCE_FILTERS.map((sf) => { {SOURCE_FILTERS.map((sf) => {
const isActive = sourceFilter === sf; const isActive = sourceFilter === sf;
return ( return (
@@ -483,10 +483,10 @@ export default function ArticleFeed() {
key={sf} key={sf}
onClick={() => { setSourceFilter(sf); setPage(1); }} onClick={() => { setSourceFilter(sf); setPage(1); }}
aria-pressed={isActive} aria-pressed={isActive}
className={`px-3 py-1 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${ className={`px-3 py-1 rounded-sm font-body text-[11px] font-semibold uppercase tracking-wide transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
isActive isActive
? sf === "Imported" ?"bg-amber-500/20 text-amber-400 border border-amber-500/40" ? sf === "Imported" ?"bg-amber-500/20 text-amber-400 border border-amber-500/40"
: sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#F0A623]/20 text-[#F0A623] border border-[#F0A623]/40" :"text-[#666] hover:text-[#aaa] border border-transparent" : sf === "Live" ?"bg-emerald-500/20 text-emerald-400 border border-emerald-500/40" :"bg-[#1D4ED8]/20 text-[#1D4ED8] border border-[#1D4ED8]/40" :"text-[#666] hover:text-[#aaa] border border-transparent"
}`}> }`}>
{sf} {sf}
{sf === "Imported" && importedCount > 0 && ( {sf === "Imported" && importedCount > 0 && (
@@ -507,7 +507,7 @@ export default function ArticleFeed() {
<button <button
type="button" type="button"
onClick={() => { setAuthorFilter("All"); setDateFrom(""); setDateTo(""); setPage(1); }} onClick={() => { setAuthorFilter("All"); setDateFrom(""); setDateTo(""); setPage(1); }}
className="font-body text-[11px] text-[#F0A623] hover:underline focus:outline-none focus-visible:underline"> className="font-body text-[11px] text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Clear advanced filters Clear advanced filters
</button> </button>
</div> </div>
@@ -537,7 +537,7 @@ export default function ArticleFeed() {
{sortedFiltered.length === 0 && ( {sortedFiltered.length === 0 && (
<button <button
onClick={clearAllFilters} onClick={clearAllFilters}
className="ml-1 text-[#F0A623] hover:underline focus:outline-none focus-visible:underline"> className="ml-1 text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Clear filters Clear filters
</button> </button>
)} )}
@@ -557,7 +557,7 @@ export default function ArticleFeed() {
</p> </p>
<button <button
onClick={clearAllFilters} onClick={clearAllFilters}
className="mt-4 font-body text-sm text-[#F0A623] hover:underline focus:outline-none focus-visible:underline"> className="mt-4 font-body text-sm text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Clear filters Clear filters
</button> </button>
</div> </div>
@@ -585,7 +585,7 @@ export default function ArticleFeed() {
const promotedClass = promoted === 'premium' const promotedClass = promoted === 'premium'
? 'border-l-4 border-amber-400 bg-gradient-to-r from-amber-500/10 to-transparent pl-3 -ml-2 mr-0' ? 'border-l-4 border-amber-400 bg-gradient-to-r from-amber-500/10 to-transparent pl-3 -ml-2 mr-0'
: promoted === 'client' : promoted === 'client'
? 'border-l-4 border-[#F0A623] bg-gradient-to-r from-[#F0A623]/8 to-transparent pl-3 -ml-2 mr-0' ? 'border-l-4 border-[#1D4ED8] bg-gradient-to-r from-[#1D4ED8]/8 to-transparent pl-3 -ml-2 mr-0'
: ''; : '';
return ( return (
<Fragment key={`feed-${i}`}> <Fragment key={`feed-${i}`}>
@@ -593,7 +593,7 @@ export default function ArticleFeed() {
<Link <Link
href={articleHref} href={articleHref}
{...linkProps} {...linkProps}
className="flex-shrink-0 img-zoom overflow-hidden w-[100px] h-[70px] sm:w-[140px] sm:h-[95px] md:w-[190px] md:h-[128px] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]" className="flex-shrink-0 img-zoom overflow-hidden w-[100px] h-[70px] sm:w-[140px] sm:h-[95px] md:w-[190px] md:h-[128px] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]"
tabIndex={-1} tabIndex={-1}
aria-hidden="true" aria-hidden="true"
> >
@@ -614,21 +614,21 @@ export default function ArticleFeed() {
</span> </span>
)} )}
{promoted === 'client' && ( {promoted === 'client' && (
<span className="font-body text-[9px] font-bold text-[#7aa7d4] bg-[#F0A623]/15 border border-[#F0A623]/40 px-1.5 py-0.5 rounded uppercase tracking-widest" title={article?.promoted_advertiser ? `Advertiser coverage: ${article.promoted_advertiser}` : 'Advertiser coverage'}> <span className="font-body text-[9px] font-bold text-[#7aa7d4] bg-[#1D4ED8]/15 border border-[#1D4ED8]/40 px-1.5 py-0.5 rounded uppercase tracking-widest" title={article?.promoted_advertiser ? `Advertiser coverage: ${article.promoted_advertiser}` : 'Advertiser coverage'}>
Advertiser Advertiser
</span> </span>
)} )}
{article?.category && article.category.toLowerCase() !== "legacy" && ( {article?.category && article.category.toLowerCase() !== "legacy" && (
<span className="font-body text-[10px] font-bold text-[#F0A623] uppercase tracking-wide"> <span className="font-body text-[10px] font-bold text-[#1D4ED8] uppercase tracking-wide">
{article.category} {article.category}
</span> </span>
)} )}
</div> </div>
<h3 className="font-heading text-[#e0e0e0] font-bold text-[0.875rem] md:text-[1rem] leading-snug mb-1 md:mb-1.5 group-hover:text-[#F0A623] transition-colors duration-200 line-clamp-2"> <h3 className="font-heading text-[#e0e0e0] font-bold text-[0.875rem] md:text-[1rem] leading-snug mb-1 md:mb-1.5 group-hover:text-[#1D4ED8] transition-colors duration-200 line-clamp-2">
<Link <Link
href={articleHref} href={articleHref}
{...linkProps} {...linkProps}
className="focus:outline-none focus-visible:text-[#F0A623] focus-visible:underline"> className="focus:outline-none focus-visible:text-[#1D4ED8] focus-visible:underline">
{article?.title} {article?.title}
</Link> </Link>
</h3> </h3>
@@ -667,42 +667,42 @@ export default function ArticleFeed() {
const shareUrl = `https://avbeat.com${articleHref.startsWith('/') ? articleHref : `/articles/${article?.slug}`}`; const shareUrl = `https://avbeat.com${articleHref.startsWith('/') ? articleHref : `/articles/${article?.slug}`}`;
const shareTitle = article?.title || ''; const shareTitle = article?.title || '';
return ( return (
<div className="flex items-center gap-1.5 mt-2 pt-2 border-t border-[#231d18]"> <div className="flex items-center gap-1.5 mt-2 pt-2 border-t border-[#FFFFFF]">
<span className="text-[#444] text-[9px] font-body uppercase tracking-wider mr-1">Share:</span> <span className="text-[#444] text-[9px] font-body uppercase tracking-wider mr-1">Share:</span>
<a href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`} <a href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on LinkedIn" title="LinkedIn" target="_blank" rel="noopener noreferrer" aria-label="Share on LinkedIn" title="LinkedIn"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#0a66c2] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#0a66c2] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 0h-14C2.24 0 0 2.24 0 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM8 19H5V8h3v11zM6.5 6.73a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5zM20 19h-3v-5.6c0-3.37-4-3.12-4 0V19h-3V8h3v1.77c1.4-2.59 7-2.78 7 2.48V19z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 0h-14C2.24 0 0 2.24 0 5v14c0 2.76 2.24 5 5 5h14c2.76 0 5-2.24 5-5V5c0-2.76-2.24-5-5-5zM8 19H5V8h3v11zM6.5 6.73a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5zM20 19h-3v-5.6c0-3.37-4-3.12-4 0V19h-3V8h3v1.77c1.4-2.59 7-2.78 7 2.48V19z"/></svg>
</a> </a>
<a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=BroadcastBeat`} <a href={`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}&via=BroadcastBeat`}
target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)" target="_blank" rel="noopener noreferrer" aria-label="Share on X" title="X (Twitter)"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-white hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-white hover:bg-[#FFFFFF] transition-colors">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a> </a>
<a href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`} <a href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook" title="Facebook" target="_blank" rel="noopener noreferrer" aria-label="Share on Facebook" title="Facebook"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#1877f2] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#1877f2] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12S0 5.446 0 12.073C0 18.062 4.388 23.027 10.125 23.927v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12S0 5.446 0 12.073C0 18.062 4.388 23.027 10.125 23.927v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a> </a>
<a href={`https://www.reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(shareTitle)}`} <a href={`https://www.reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(shareTitle)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on Reddit" title="Reddit" target="_blank" rel="noopener noreferrer" aria-label="Share on Reddit" title="Reddit"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#ff4500] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#ff4500] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249z"/></svg>
</a> </a>
<a href={`https://api.whatsapp.com/send?text=${encodeURIComponent(shareTitle + ' ' + shareUrl)}`} <a href={`https://api.whatsapp.com/send?text=${encodeURIComponent(shareTitle + ' ' + shareUrl)}`}
target="_blank" rel="noopener noreferrer" aria-label="Share on WhatsApp" title="WhatsApp" target="_blank" rel="noopener noreferrer" aria-label="Share on WhatsApp" title="WhatsApp"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#25d366] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#25d366] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.945C.16 5.335 5.495 0 12.05 0a11.817 11.817 0 0 1 8.413 3.488 11.824 11.824 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981z"/></svg> <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M.057 24l1.687-6.163a11.867 11.867 0 0 1-1.587-5.945C.16 5.335 5.495 0 12.05 0a11.817 11.817 0 0 1 8.413 3.488 11.824 11.824 0 0 1 3.48 8.414c-.003 6.557-5.338 11.892-11.893 11.892a11.9 11.9 0 0 1-5.688-1.448L.057 24zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981z"/></svg>
</a> </a>
<a href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`} <a href={`mailto:?subject=${encodeURIComponent(shareTitle)}&body=${encodeURIComponent(shareUrl)}`}
aria-label="Share via email" title="Email" aria-label="Share via email" title="Email"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#F0A623] hover:bg-[#231d18] transition-colors"> className="flex items-center justify-center w-8 h-8 rounded-sm text-[#666] hover:text-[#1D4ED8] hover:bg-[#FFFFFF] transition-colors">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"> <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
<rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /> <rect x="2" y="4" width="20" height="16" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg> </svg>
@@ -735,9 +735,9 @@ export default function ArticleFeed() {
{isLoadingMore && ( {isLoadingMore && (
<div className="flex items-center justify-center gap-2 py-6" aria-live="polite" aria-label="Loading more articles"> <div className="flex items-center justify-center gap-2 py-6" aria-live="polite" aria-label="Loading more articles">
<div className="flex gap-1"> <div className="flex gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] animate-bounce" style={{ animationDelay: "0ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] animate-bounce" style={{ animationDelay: "0ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] animate-bounce" style={{ animationDelay: "150ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] animate-bounce" style={{ animationDelay: "150ms" }} />
<span className="w-1.5 h-1.5 rounded-full bg-[#F0A623] animate-bounce" style={{ animationDelay: "300ms" }} /> <span className="w-1.5 h-1.5 rounded-full bg-[#1D4ED8] animate-bounce" style={{ animationDelay: "300ms" }} />
</div> </div>
<span className="font-body text-xs text-[#555]">Loading more articles</span> <span className="font-body text-xs text-[#555]">Loading more articles</span>
</div> </div>
@@ -762,7 +762,7 @@ export default function ArticleFeed() {
</p> </p>
<div className="flex items-center gap-1" role="navigation" aria-label="Pagination"> <div className="flex items-center gap-1" role="navigation" aria-label="Pagination">
<button <button
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]" className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]"
disabled={page === 1} disabled={page === 1}
onClick={() => { setPage(Math.max(1, page - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }} onClick={() => { setPage(Math.max(1, page - 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label="Previous page"> aria-label="Previous page">
@@ -786,7 +786,7 @@ export default function ArticleFeed() {
) : ( ) : (
<button <button
key={p} key={p}
className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${page === p ? "active" : ""}`} className={`page-btn focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${page === p ? "active" : ""}`}
onClick={() => { setPage(p as number); window.scrollTo({ top: 0, behavior: "smooth" }); }} onClick={() => { setPage(p as number); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label={`Page ${p}`} aria-label={`Page ${p}`}
aria-current={page === p ? "page" : undefined}> aria-current={page === p ? "page" : undefined}>
@@ -796,7 +796,7 @@ export default function ArticleFeed() {
); );
})()} })()}
<button <button
className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]" className="page-btn inline-flex items-center gap-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]"
disabled={page === totalPages} disabled={page === totalPages}
onClick={() => { setPage(Math.min(totalPages, page + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }} onClick={() => { setPage(Math.min(totalPages, page + 1)); window.scrollTo({ top: 0, behavior: "smooth" }); }}
aria-label="Next page"> aria-label="Next page">

View File

@@ -157,7 +157,7 @@ export default function FeaturedBento() {
{/* Section header */} {/* Section header */}
<div className="flex items-center gap-3 mb-4 md:mb-5"> <div className="flex items-center gap-3 mb-4 md:mb-5">
<span className="section-label">Featured</span> <span className="section-label">Featured</span>
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
{/* Navigation arrows */} {/* Navigation arrows */}
<div <div
className="flex items-center gap-1.5" className="flex items-center gap-1.5"
@@ -173,7 +173,7 @@ export default function FeaturedBento() {
type="button" type="button"
onClick={() => { goPrev(); setPaused(true); }} onClick={() => { goPrev(); setPaused(true); }}
aria-label="Previous stories" aria-label="Previous stories"
className="w-8 h-8 flex items-center justify-center bg-[#231d18] border border-[#3a3a3a] hover:bg-[#F0A623] hover:border-[#F0A623] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#F0A623]"> className="w-8 h-8 flex items-center justify-center bg-[#FFFFFF] border border-[#3a3a3a] hover:bg-[#1D4ED8] hover:border-[#1D4ED8] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#1D4ED8]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<polyline points="15 18 9 12 15 6" /> <polyline points="15 18 9 12 15 6" />
</svg> </svg>
@@ -182,7 +182,7 @@ export default function FeaturedBento() {
type="button" type="button"
onClick={() => { goNext(); setPaused(true); }} onClick={() => { goNext(); setPaused(true); }}
aria-label="Next stories" aria-label="Next stories"
className="w-8 h-8 flex items-center justify-center bg-[#231d18] border border-[#3a3a3a] hover:bg-[#F0A623] hover:border-[#F0A623] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#F0A623]"> className="w-8 h-8 flex items-center justify-center bg-[#FFFFFF] border border-[#3a3a3a] hover:bg-[#1D4ED8] hover:border-[#1D4ED8] hover:text-white text-[#ccc] transition-colors rounded-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-[#1D4ED8]">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
<polyline points="9 18 15 12 9 6" /> <polyline points="9 18 15 12 9 6" />
</svg> </svg>
@@ -210,7 +210,7 @@ export default function FeaturedBento() {
/> />
<div className="hero-overlay absolute inset-0" /> <div className="hero-overlay absolute inset-0" />
<div className="absolute inset-0 flex flex-col justify-end p-3 sm:p-4 md:p-5"> <div className="absolute inset-0 flex flex-col justify-end p-3 sm:p-4 md:p-5">
<span className="inline-block bg-[#F0A623] text-white font-body text-[10px] font-bold px-2 py-0.5 tracking-wider uppercase mb-2 w-fit"> <span className="inline-block bg-[#1D4ED8] text-white font-body text-[10px] font-bold px-2 py-0.5 tracking-wider uppercase mb-2 w-fit">
{hero?.category} {hero?.category}
</span> </span>
<h2 className="font-heading text-white text-lg sm:text-xl md:text-2xl font-bold leading-tight mb-2 max-w-xl"> <h2 className="font-heading text-white text-lg sm:text-xl md:text-2xl font-bold leading-tight mb-2 max-w-xl">
@@ -236,7 +236,7 @@ export default function FeaturedBento() {
<Link <Link
key={`${article?.slug}-${offset}-${i}`} key={`${article?.slug}-${offset}-${i}`}
href={`/articles/${article?.slug}`} href={`/articles/${article?.slug}`}
className="bg-[#231d18] border border-[#3a322b] overflow-hidden group cursor-pointer card-accent-hover flex flex-col hover:border-[#F0A623] transition-colors"> className="bg-[#FFFFFF] border border-[#DCE6F2] overflow-hidden group cursor-pointer card-accent-hover flex flex-col hover:border-[#1D4ED8] transition-colors">
<div className="relative overflow-hidden img-zoom h-[90px] sm:h-[110px] md:h-[120px]"> <div className="relative overflow-hidden img-zoom h-[90px] sm:h-[110px] md:h-[120px]">
<AppImage <AppImage
src={article?.image} src={article?.image}
@@ -246,13 +246,13 @@ export default function FeaturedBento() {
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 50vw, 22vw" sizes="(max-width: 640px) 50vw, (max-width: 1024px) 50vw, 22vw"
/> />
<div className="absolute top-0 left-0"> <div className="absolute top-0 left-0">
<span className="bg-[#F0A623] text-white font-body text-[9px] font-bold px-1.5 py-0.5 tracking-wider uppercase"> <span className="bg-[#1D4ED8] text-white font-body text-[9px] font-bold px-1.5 py-0.5 tracking-wider uppercase">
{article?.category} {article?.category}
</span> </span>
</div> </div>
</div> </div>
<div className="p-2 md:p-2.5 flex-1 flex flex-col justify-between"> <div className="p-2 md:p-2.5 flex-1 flex flex-col justify-between">
<h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-3 group-hover:text-[#F0A623] transition-colors duration-200 mb-1.5 md:mb-2"> <h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-3 group-hover:text-[#1D4ED8] transition-colors duration-200 mb-1.5 md:mb-2">
{article?.title} {article?.title}
</h3> </h3>
<span className="read-more inline-block text-[11px]">Read More »</span> <span className="read-more inline-block text-[11px]">Read More »</span>
@@ -270,7 +270,7 @@ export default function FeaturedBento() {
aria-label={`Go to position ${i + 1}`} aria-label={`Go to position ${i + 1}`}
className={`rounded-full transition-all focus:outline-none ${ className={`rounded-full transition-all focus:outline-none ${
i === offset i === offset
? "w-4 h-1.5 bg-[#F0A623]" ? "w-4 h-1.5 bg-[#1D4ED8]"
: "w-1.5 h-1.5 bg-[#333] hover:bg-[#555]" : "w-1.5 h-1.5 bg-[#333] hover:bg-[#555]"
}`} }`}
/> />

View File

@@ -17,7 +17,7 @@ export default function NewsTicker() {
const doubled = [...headlines, ...headlines]; const doubled = [...headlines, ...headlines];
return ( return (
<div className="bg-[#D85A30] overflow-hidden border-b border-[#E24B4A]"> <div className="bg-[#1D4ED8] overflow-hidden border-b border-[#1E3A8A]">
<div className="flex items-stretch h-8"> <div className="flex items-stretch h-8">
{/* Label */} {/* Label */}
<div className="flex-shrink-0 bg-[#080808] flex items-center px-4 z-10"> <div className="flex-shrink-0 bg-[#080808] flex items-center px-4 z-10">

View File

@@ -60,13 +60,13 @@ export default function NewsletterSignup() {
return ( return (
<section className="max-w-container mx-auto px-4" aria-labelledby={`${formId}-heading`}> <section className="max-w-container mx-auto px-4" aria-labelledby={`${formId}-heading`}>
<div className="bg-[#0d1520] border border-[#412402] py-8 md:py-10"> <div className="bg-[#0d1520] border border-[#DCE6F2] py-8 md:py-10">
<div className="max-w-2xl mx-auto text-center px-4"> <div className="max-w-2xl mx-auto text-center px-4">
{/* Header */} {/* Header */}
<div className="flex items-center justify-center gap-3 mb-3"> <div className="flex items-center justify-center gap-3 mb-3">
<div className="flex-1 h-px bg-[#412402] max-w-[80px]" /> <div className="flex-1 h-px bg-[#DCE6F2] max-w-[80px]" />
<span className="section-label" id={`${formId}-heading`}>Newsletter</span> <span className="section-label" id={`${formId}-heading`}>Newsletter</span>
<div className="flex-1 h-px bg-[#412402] max-w-[80px]" /> <div className="flex-1 h-px bg-[#DCE6F2] max-w-[80px]" />
</div> </div>
<p className="font-heading text-[#e0e0e0] text-xl md:text-2xl font-bold mb-1.5"> <p className="font-heading text-[#e0e0e0] text-xl md:text-2xl font-bold mb-1.5">
Stay Ahead of the Pro AV Industry Stay Ahead of the Pro AV Industry
@@ -77,12 +77,12 @@ export default function NewsletterSignup() {
{submitted ? ( {submitted ? (
<div <div
className="bg-[#231d18] border border-[#F0A623] rounded-sm px-6 py-8 text-center" className="bg-[#FFFFFF] border border-[#1D4ED8] rounded-sm px-6 py-8 text-center"
role="status" role="status"
aria-live="polite"> aria-live="polite">
<div className="w-10 h-10 rounded-full bg-[#F0A623]/20 flex items-center justify-center mx-auto mb-3"> <div className="w-10 h-10 rounded-full bg-[#1D4ED8]/20 flex items-center justify-center mx-auto mb-3">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M4 10l4 4 8-8" stroke="#F0A623" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M4 10l4 4 8-8" stroke="#1D4ED8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</div> </div>
<p className="font-heading text-[#e0e0e0] font-bold text-lg mb-1">You&apos;re subscribed!</p> <p className="font-heading text-[#e0e0e0] font-bold text-lg mb-1">You&apos;re subscribed!</p>
@@ -111,7 +111,7 @@ export default function NewsletterSignup() {
className="sr-only" className="sr-only"
aria-label={`Subscribe to ${cat} news`} aria-label={`Subscribe to ${cat} news`}
/> />
<span aria-hidden="true" className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#F0A623] border-[#F0A623]" : "border-[#444]"}`}> <span aria-hidden="true" className={`mr-1.5 inline-block w-3 h-3 rounded-sm border transition-colors ${checked ? "bg-[#1D4ED8] border-[#1D4ED8]" : "border-[#444]"}`}>
{checked && ( {checked && (
<svg viewBox="0 0 12 12" fill="none" className="w-full h-full" aria-hidden="true"> <svg viewBox="0 0 12 12" fill="none" className="w-full h-full" aria-hidden="true">
<path d="M2 6l3 3 5-5" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M2 6l3 3 5-5" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
@@ -147,7 +147,7 @@ export default function NewsletterSignup() {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className={`btn-subscribe py-2.5 px-6 text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1520] ${loading ? "opacity-70 cursor-not-allowed" : ""}`}> className={`btn-subscribe py-2.5 px-6 text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0d1520] ${loading ? "opacity-70 cursor-not-allowed" : ""}`}>
{loading ? "Subscribing..." : "Subscribe Free"} {loading ? "Subscribing..." : "Subscribe Free"}
</button> </button>
</div> </div>
@@ -164,7 +164,7 @@ export default function NewsletterSignup() {
<p className="font-body text-[#555] text-xs mt-3"> <p className="font-body text-[#555] text-xs mt-3">
No spam. Unsubscribe anytime. By subscribing you agree to our{" "} No spam. Unsubscribe anytime. By subscribing you agree to our{" "}
<a href="/privacy" className="text-[#F0A623] hover:underline focus:outline-none focus-visible:underline"> <a href="/privacy" className="text-[#1D4ED8] hover:underline focus:outline-none focus-visible:underline">
Privacy Policy Privacy Policy
</a>. </a>.
</p> </p>

View File

@@ -141,7 +141,7 @@ export default function SpotlightCarousel() {
return ( return (
<section <section
suppressHydrationWarning suppressHydrationWarning
className="bg-[#1c1815] py-6 md:py-8 border-t border-b border-[#222]" className="bg-[#F8FAFC] py-6 md:py-8 border-t border-b border-[#222]"
aria-label="Spotlight carousel" aria-label="Spotlight carousel"
onKeyDown={handleCarouselKeyDown}> onKeyDown={handleCarouselKeyDown}>
<div suppressHydrationWarning className="max-w-container mx-auto px-4"> <div suppressHydrationWarning className="max-w-container mx-auto px-4">
@@ -156,14 +156,14 @@ export default function SpotlightCarousel() {
ref={prevBtnRef} ref={prevBtnRef}
onClick={() => go(-1)} onClick={() => go(-1)}
aria-label="Previous spotlight (Left arrow key)" aria-label="Previous spotlight (Left arrow key)"
className="carousel-btn w-7 h-7 border border-[#333] bg-[#231d18] hover:border-[#F0A623] hover:text-[#F0A623] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"> className="carousel-btn w-7 h-7 border border-[#333] bg-[#FFFFFF] hover:border-[#1D4ED8] hover:text-[#1D4ED8] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]">
<ChevronLeftIcon size={13} /> <ChevronLeftIcon size={13} />
</button> </button>
<button <button
ref={nextBtnRef} ref={nextBtnRef}
onClick={() => go(1)} onClick={() => go(1)}
aria-label="Next spotlight (Right arrow key)" aria-label="Next spotlight (Right arrow key)"
className="carousel-btn w-7 h-7 border border-[#333] bg-[#231d18] hover:border-[#F0A623] hover:text-[#F0A623] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623]"> className="carousel-btn w-7 h-7 border border-[#333] bg-[#FFFFFF] hover:border-[#1D4ED8] hover:text-[#1D4ED8] hover:bg-[#1e1e1e] flex items-center justify-center transition-colors text-[#888] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8]">
<ChevronRightIcon size={13} /> <ChevronRightIcon size={13} />
</button> </button>
</div> </div>
@@ -181,7 +181,7 @@ export default function SpotlightCarousel() {
key={`${startIndex}-${i}`} key={`${startIndex}-${i}`}
href={item.href} href={item.href}
role="listitem" role="listitem"
className={`spotlight-card group block spotlight-slide-in focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${ className={`spotlight-card group block spotlight-slide-in focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
i >= 3 ? "hidden lg:block" : i >= 2 ? "hidden md:block" : "" i >= 3 ? "hidden lg:block" : i >= 2 ? "hidden md:block" : ""
}`} }`}
style={{ animationDelay: `${i * 50}ms` }} style={{ animationDelay: `${i * 50}ms` }}
@@ -200,11 +200,11 @@ export default function SpotlightCarousel() {
</span> </span>
</div> </div>
</div> </div>
<div className="p-2.5 md:p-3 border-t border-[#3a322b]"> <div className="p-2.5 md:p-3 border-t border-[#DCE6F2]">
<p className="font-body text-[11px] font-bold text-[#F0A623] uppercase tracking-wide mb-1"> <p className="font-body text-[11px] font-bold text-[#1D4ED8] uppercase tracking-wide mb-1">
{item.name} {item.name}
</p> </p>
<h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-2 group-hover:text-[#F0A623] transition-colors duration-200"> <h3 className="font-heading text-[#e0e0e0] text-[12px] md:text-[13px] font-bold leading-snug line-clamp-2 group-hover:text-[#1D4ED8] transition-colors duration-200">
{item.title} {item.title}
</h3> </h3>
</div> </div>
@@ -221,7 +221,7 @@ export default function SpotlightCarousel() {
role="tab" role="tab"
aria-selected={Math.floor(startIndex / VISIBLE) === i} aria-selected={Math.floor(startIndex / VISIBLE) === i}
aria-label={`Go to spotlight page ${i + 1}`} aria-label={`Go to spotlight page ${i + 1}`}
className={`h-1.5 rounded-full transition-all duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#F0A623] ${ className={`h-1.5 rounded-full transition-all duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1D4ED8] ${
Math.floor(startIndex / VISIBLE) === i Math.floor(startIndex / VISIBLE) === i
? "w-5 bg-accent" : "w-1.5 bg-[#333] hover:bg-[#555]" ? "w-5 bg-accent" : "w-1.5 bg-[#333] hover:bg-[#555]"
}`} }`}

View File

@@ -54,7 +54,7 @@ function BentoSkeleton() {
<section className="max-w-container mx-auto px-4 py-4 md:py-6"> <section className="max-w-container mx-auto px-4 py-4 md:py-6">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="skeleton h-4 w-20 rounded-sm" /> <div className="skeleton h-4 w-20 rounded-sm" />
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-3 md:gap-4"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-3 md:gap-4">
<div className="lg:col-span-7 skeleton rounded-sm h-[260px] sm:h-[320px] md:h-[380px]" /> <div className="lg:col-span-7 skeleton rounded-sm h-[260px] sm:h-[320px] md:h-[380px]" />
@@ -81,7 +81,7 @@ function CarouselSkeleton() {
<div className="max-w-container mx-auto"> <div className="max-w-container mx-auto">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="skeleton h-4 w-24 rounded-sm" /> <div className="skeleton h-4 w-24 rounded-sm" />
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{[...Array(4)]?.map((_, i) => ( {[...Array(4)]?.map((_, i) => (
@@ -107,7 +107,7 @@ function FeedSkeleton() {
<div className="lg:col-span-8 space-y-1"> <div className="lg:col-span-8 space-y-1">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="skeleton h-4 w-20 rounded-sm" /> <div className="skeleton h-4 w-20 rounded-sm" />
<div className="flex-1 h-px bg-[#3a322b]" /> <div className="flex-1 h-px bg-[#DCE6F2]" />
</div> </div>
{[...Array(5)]?.map((_, i) => ( {[...Array(5)]?.map((_, i) => (
<div key={i} className="flex gap-4 py-4 border-b border-[#222]"> <div key={i} className="flex gap-4 py-4 border-b border-[#222]">

View File

@@ -140,11 +140,11 @@ export default function RootLayout({
}) })
}} /> }} />
</head> </head>
<body className="bb-neon"> <body className="bg-brand-bg text-brand-text antialiased">
{/* Skip to main content — WCAG 2.4.1 */} {/* Skip to main content — WCAG 2.4.1 */}
<a <a
href="#main-content" href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[10000] focus:px-4 focus:py-2 focus:bg-[#F0A623] focus:text-white focus:rounded focus:font-bold focus:text-sm focus:outline-none" className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[10000] focus:px-4 focus:py-2 focus:bg-[#1D4ED8] focus:text-white focus:rounded focus:font-bold focus:text-sm focus:outline-none"
> >
Skip to main content Skip to main content
</a> </a>

View File

@@ -50,7 +50,7 @@ function LoginInner() {
return ( return (
<> <>
<Header /> <Header />
<main className="min-h-screen bg-[#1c1815]"> <main className="min-h-screen bg-[#F8FAFC]">
<div className="max-w-md mx-auto px-4 py-12"> <div className="max-w-md mx-auto px-4 py-12">
<div className="text-center mb-6"> <div className="text-center mb-6">
<h1 className="text-2xl font-heading font-bold text-white">Sign in to AV Beat</h1> <h1 className="text-2xl font-heading font-bold text-white">Sign in to AV Beat</h1>
@@ -60,10 +60,10 @@ function LoginInner() {
</div> </div>
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="bg-[#231d18] border border-[#3a322b] rounded-lg p-6 space-y-4" className="bg-[#FFFFFF] border border-[#DCE6F2] rounded-lg p-6 space-y-4"
> >
{error && ( {error && (
<div className="bg-[#2a0a0a] border border-[#D85A30] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded"> <div className="bg-[#2a0a0a] border border-[#1D4ED8] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error} {error}
</div> </div>
)} )}
@@ -77,7 +77,7 @@ function LoginInner() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<div> <div>
@@ -90,19 +90,19 @@ function LoginInner() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password" autoComplete="current-password"
required required
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#F0A623]" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#1D4ED8]"
/> />
</div> </div>
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-[#F0A623] hover:bg-[#BA7517] disabled:opacity-50 text-white font-body font-semibold py-2 rounded-lg transition-colors" className="w-full bg-[#1D4ED8] hover:bg-[#1E3A8A] disabled:opacity-50 text-white font-body font-semibold py-2 rounded-lg transition-colors"
> >
{loading ? 'Signing in…' : 'Sign in'} {loading ? 'Signing in…' : 'Sign in'}
</button> </button>
<p className="text-xs text-[#666] font-body text-center pt-2"> <p className="text-xs text-[#666] font-body text-center pt-2">
PR firm / contributor?{' '} PR firm / contributor?{' '}
<Link href="/client-login" className="text-[#F0A623] hover:underline"> <Link href="/client-login" className="text-[#1D4ED8] hover:underline">
Use the contributor portal Use the contributor portal
</Link> </Link>
</p> </p>

View File

@@ -77,14 +77,14 @@ export default function ManufacturerDirectoryClient({
return ( return (
<div> <div>
<div className="bg-[#111] border border-[#3a322b] rounded-xl p-4 mb-6 flex flex-col gap-3 md:flex-row md:items-center md:gap-4"> <div className="bg-[#111] border border-[#DCE6F2] rounded-xl p-4 mb-6 flex flex-col gap-3 md:flex-row md:items-center md:gap-4">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<input <input
type="search" type="search"
value={q} value={q}
onChange={(e) => setQ(e.target.value)} onChange={(e) => setQ(e.target.value)}
placeholder="Search by company or product…" placeholder="Search by company or product…"
className="w-full bg-[#1c1815] border border-[#3a322b] rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#F0A623]/40" className="w-full bg-[#F8FAFC] border border-[#DCE6F2] rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#1D4ED8]/40"
/> />
</div> </div>
</div> </div>
@@ -97,7 +97,7 @@ export default function ManufacturerDirectoryClient({
type="button" type="button"
onClick={() => setShowFilter("all")} onClick={() => setShowFilter("all")}
className={`px-3 py-1.5 rounded text-xs font-semibold ${ className={`px-3 py-1.5 rounded text-xs font-semibold ${
showFilter === "all" ? "bg-[#F0A623] text-white" : "bg-[#1c1815] border border-[#3a322b] text-[#aaa] hover:bg-[#231d18]" showFilter === "all" ? "bg-[#1D4ED8] text-white" : "bg-[#F8FAFC] border border-[#DCE6F2] text-[#aaa] hover:bg-[#FFFFFF]"
}`} }`}
> >
All shows <span className="ml-1 opacity-60">({manufacturers.length.toLocaleString()})</span> All shows <span className="ml-1 opacity-60">({manufacturers.length.toLocaleString()})</span>
@@ -106,7 +106,7 @@ export default function ManufacturerDirectoryClient({
type="button" type="button"
onClick={() => setShowFilter("both-major")} onClick={() => setShowFilter("both-major")}
className={`px-3 py-1.5 rounded text-xs font-semibold ${ className={`px-3 py-1.5 rounded text-xs font-semibold ${
showFilter === "both-major" ? "bg-[#F0A623] text-white" : "bg-[#1c1815] border border-[#3a322b] text-[#aaa] hover:bg-[#231d18]" showFilter === "both-major" ? "bg-[#1D4ED8] text-white" : "bg-[#F8FAFC] border border-[#DCE6F2] text-[#aaa] hover:bg-[#FFFFFF]"
}`} }`}
> >
NAB + IBC <span className="ml-1 opacity-60">({manufacturers.filter((m) => m.exhibits_nab && m.exhibits_ibc).length})</span> NAB + IBC <span className="ml-1 opacity-60">({manufacturers.filter((m) => m.exhibits_nab && m.exhibits_ibc).length})</span>
@@ -121,7 +121,7 @@ export default function ManufacturerDirectoryClient({
type="button" type="button"
onClick={() => setShowFilter(s.slug)} onClick={() => setShowFilter(s.slug)}
className={`px-3 py-1.5 rounded text-xs font-semibold ${ className={`px-3 py-1.5 rounded text-xs font-semibold ${
active ? "bg-[#F0A623] text-white" : "bg-[#1c1815] border border-[#3a322b] text-[#aaa] hover:bg-[#231d18]" active ? "bg-[#1D4ED8] text-white" : "bg-[#F8FAFC] border border-[#DCE6F2] text-[#aaa] hover:bg-[#FFFFFF]"
}`} }`}
title={`${s.name} · ${s.start_date}${count === 0 ? " · exhibitor list coming soon" : ""}`} title={`${s.name} · ${s.start_date}${count === 0 ? " · exhibitor list coming soon" : ""}`}
> >
@@ -144,8 +144,8 @@ export default function ManufacturerDirectoryClient({
onClick={() => setLetter(l)} onClick={() => setLetter(l)}
className={`min-w-[28px] h-7 px-1.5 rounded font-semibold ${ className={`min-w-[28px] h-7 px-1.5 rounded font-semibold ${
letter === l letter === l
? "bg-[#F0A623] text-white" ? "bg-[#1D4ED8] text-white"
: "bg-[#1c1815] border border-[#3a322b] text-[#aaa] hover:bg-[#231d18]" : "bg-[#F8FAFC] border border-[#DCE6F2] text-[#aaa] hover:bg-[#FFFFFF]"
}`} }`}
> >
{l} {l}
@@ -162,9 +162,9 @@ export default function ManufacturerDirectoryClient({
<Link <Link
key={m.slug} key={m.slug}
href={`/manufacturers/${m.slug}`} href={`/manufacturers/${m.slug}`}
className="group bg-[#1c1815] border border-[#3a322b] rounded-xl p-4 hover:border-[#F0A623] hover:shadow-sm transition-all flex gap-3" className="group bg-[#F8FAFC] border border-[#DCE6F2] rounded-xl p-4 hover:border-[#1D4ED8] hover:shadow-sm transition-all flex gap-3"
> >
<div className="w-14 h-14 rounded-lg bg-[#231d18] flex items-center justify-center overflow-hidden flex-shrink-0"> <div className="w-14 h-14 rounded-lg bg-[#FFFFFF] flex items-center justify-center overflow-hidden flex-shrink-0">
{m.logo_url ? ( {m.logo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
<img <img
@@ -180,7 +180,7 @@ export default function ManufacturerDirectoryClient({
)} )}
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h3 className="font-semibold text-[#e0e0e0] truncate group-hover:text-[#F0A623] transition-colors"> <h3 className="font-semibold text-[#e0e0e0] truncate group-hover:text-[#1D4ED8] transition-colors">
{m.company_name} {m.company_name}
</h3> </h3>
<div className="flex items-center gap-1.5 mt-1 mb-1.5 flex-wrap"> <div className="flex items-center gap-1.5 mt-1 mb-1.5 flex-wrap">
@@ -195,7 +195,7 @@ export default function ManufacturerDirectoryClient({
</span> </span>
)} )}
{m.featured && ( {m.featured && (
<span className="text-[10px] font-semibold uppercase tracking-wider bg-[#F0A623]/15 text-[#F0A623] px-1.5 py-0.5 rounded"> <span className="text-[10px] font-semibold uppercase tracking-wider bg-[#1D4ED8]/15 text-[#1D4ED8] px-1.5 py-0.5 rounded">
Featured Featured
</span> </span>
)} )}

View File

@@ -29,7 +29,7 @@ export default function BackLink() {
} }
} }
return ( return (
<Link href="/manufacturers" onClick={onClick} className="hover:text-[#F0A623]"> <Link href="/manufacturers" onClick={onClick} className="hover:text-[#1D4ED8]">
Back Back
</Link> </Link>
); );

View File

@@ -159,7 +159,7 @@ export default async function ManufacturerProfile({
}; };
return ( return (
<div className="min-h-screen bg-[#1c1815]"> <div className="min-h-screen bg-[#F8FAFC]">
<Header /> <Header />
<script <script
type="application/ld+json" type="application/ld+json"
@@ -170,8 +170,8 @@ export default async function ManufacturerProfile({
<BackLink /> <BackLink />
</nav> </nav>
<header className="flex flex-col sm:flex-row gap-6 mb-8 pb-8 border-b border-[#3a322b]"> <header className="flex flex-col sm:flex-row gap-6 mb-8 pb-8 border-b border-[#DCE6F2]">
<div className="w-24 h-24 rounded-xl bg-[#231d18] flex items-center justify-center overflow-hidden flex-shrink-0"> <div className="w-24 h-24 rounded-xl bg-[#FFFFFF] flex items-center justify-center overflow-hidden flex-shrink-0">
{company.logo_url ? ( {company.logo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
<img <img
@@ -206,7 +206,7 @@ export default async function ManufacturerProfile({
</span> </span>
)} )}
{company.phone && ( {company.phone && (
<a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#888] hover:text-[#F0A623]"> <a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#888] hover:text-[#1D4ED8]">
{company.phone} {company.phone}
</a> </a>
)} )}
@@ -217,7 +217,7 @@ export default async function ManufacturerProfile({
href={company.company_website} href={company.company_website}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#F0A623] hover:underline" className="text-[#1D4ED8] hover:underline"
> >
Website Website
</a> </a>
@@ -227,7 +227,7 @@ export default async function ManufacturerProfile({
href={company.press_url} href={company.press_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#F0A623] hover:underline" className="text-[#1D4ED8] hover:underline"
> >
Newsroom Newsroom
</a> </a>
@@ -237,7 +237,7 @@ export default async function ManufacturerProfile({
href={company.contact_url} href={company.contact_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#F0A623] hover:underline" className="text-[#1D4ED8] hover:underline"
> >
Contact Contact
</a> </a>
@@ -247,7 +247,7 @@ export default async function ManufacturerProfile({
href={company.linkedin_url} href={company.linkedin_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#888] hover:text-[#F0A623]" className="text-[#888] hover:text-[#1D4ED8]"
> >
LinkedIn LinkedIn
</a> </a>
@@ -257,7 +257,7 @@ export default async function ManufacturerProfile({
href={`https://twitter.com/${company.twitter_handle}`} href={`https://twitter.com/${company.twitter_handle}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[#888] hover:text-[#F0A623]" className="text-[#888] hover:text-[#1D4ED8]"
> >
X / Twitter X / Twitter
</a> </a>
@@ -283,17 +283,17 @@ export default async function ManufacturerProfile({
<h2 className="text-lg font-display font-bold text-[#e0e0e0] mb-4">Executive leadership</h2> <h2 className="text-lg font-display font-bold text-[#e0e0e0] mb-4">Executive leadership</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{executives.map((e: any) => ( {executives.map((e: any) => (
<div key={e.id} className="bg-[#111] border border-[#3a322b] rounded-lg p-4 flex gap-3"> <div key={e.id} className="bg-[#111] border border-[#DCE6F2] rounded-lg p-4 flex gap-3">
{e.photo_url ? ( {e.photo_url ? (
/* eslint-disable-next-line @next/next/no-img-element */ /* eslint-disable-next-line @next/next/no-img-element */
<img <img
src={e.photo_url} src={e.photo_url}
alt={e.name} alt={e.name}
className="w-16 h-16 rounded-full object-cover bg-[#231d18] flex-shrink-0" className="w-16 h-16 rounded-full object-cover bg-[#FFFFFF] flex-shrink-0"
loading="lazy" loading="lazy"
/> />
) : ( ) : (
<div className="w-16 h-16 rounded-full bg-[#231d18] flex items-center justify-center text-[#666] font-bold flex-shrink-0"> <div className="w-16 h-16 rounded-full bg-[#FFFFFF] flex items-center justify-center text-[#666] font-bold flex-shrink-0">
{(e.name || "?").trim().charAt(0).toUpperCase()} {(e.name || "?").trim().charAt(0).toUpperCase()}
</div> </div>
)} )}
@@ -306,7 +306,7 @@ export default async function ManufacturerProfile({
href={e.linkedin_url} href={e.linkedin_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-[11px] text-[#F0A623] hover:underline" className="text-[11px] text-[#1D4ED8] hover:underline"
> >
LinkedIn LinkedIn
</a> </a>
@@ -328,14 +328,14 @@ export default async function ManufacturerProfile({
)} )}
{company.phone && ( {company.phone && (
<div className="mt-1"> <div className="mt-1">
<a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#F0A623] hover:underline"> <a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-[#1D4ED8] hover:underline">
{company.phone} {company.phone}
</a> </a>
</div> </div>
)} )}
{company.contact_email && ( {company.contact_email && (
<div> <div>
<a href={`mailto:${company.contact_email}`} className="text-[#F0A623] hover:underline"> <a href={`mailto:${company.contact_email}`} className="text-[#1D4ED8] hover:underline">
{company.contact_email} {company.contact_email}
</a> </a>
</div> </div>
@@ -356,7 +356,7 @@ export default async function ManufacturerProfile({
{cleanCategories.map((c: string) => ( {cleanCategories.map((c: string) => (
<span <span
key={c} key={c}
className="text-xs bg-[#231d18] text-[#aaa] px-2.5 py-1 rounded-full" className="text-xs bg-[#FFFFFF] text-[#aaa] px-2.5 py-1 rounded-full"
> >
{c} {c}
</span> </span>
@@ -382,7 +382,7 @@ export default async function ManufacturerProfile({
return ( return (
<li <li
key={`${s.show}-${s.show_year}-${s.booth || "?"}`} key={`${s.show}-${s.show_year}-${s.booth || "?"}`}
className="bg-[#111] border border-[#3a322b] rounded-md px-4 py-3 flex items-center justify-between gap-4" className="bg-[#111] border border-[#DCE6F2] rounded-md px-4 py-3 flex items-center justify-between gap-4"
> >
<div className="min-w-0"> <div className="min-w-0">
<span className="font-semibold text-[#e0e0e0]">{label}</span> <span className="font-semibold text-[#e0e0e0]">{label}</span>
@@ -401,7 +401,7 @@ export default async function ManufacturerProfile({
<section className="mb-10"> <section className="mb-10">
<header className="flex items-baseline justify-between mb-3"> <header className="flex items-baseline justify-between mb-3">
<h2 className="text-lg font-display font-bold text-[#e0e0e0]">Recent coverage</h2> <h2 className="text-lg font-display font-bold text-[#e0e0e0]">Recent coverage</h2>
<Link href={`/news?search=${encodeURIComponent(company.company_name)}`} className="text-xs text-[#F0A623] hover:underline"> <Link href={`/news?search=${encodeURIComponent(company.company_name)}`} className="text-xs text-[#1D4ED8] hover:underline">
All stories All stories
</Link> </Link>
</header> </header>
@@ -410,7 +410,7 @@ export default async function ManufacturerProfile({
<Link <Link
key={r.wp_slug} key={r.wp_slug}
href={`/news/${r.wp_slug}`} href={`/news/${r.wp_slug}`}
className="group bg-[#1c1815] border border-[#3a322b] rounded-md p-3 hover:border-[#F0A623] hover:shadow-sm transition-all flex gap-3" className="group bg-[#F8FAFC] border border-[#DCE6F2] rounded-md p-3 hover:border-[#1D4ED8] hover:shadow-sm transition-all flex gap-3"
> >
{/* Image fallback cascade — Ryan rule: never show an empty {/* Image fallback cascade — Ryan rule: never show an empty
gray box on a coverage card. Falls through: gray box on a coverage card. Falls through:
@@ -419,11 +419,11 @@ export default async function ManufacturerProfile({
<img <img
src={r.featured_image || company.logo_url || '/assets/images/article-placeholder.svg'} src={r.featured_image || company.logo_url || '/assets/images/article-placeholder.svg'}
alt="" alt=""
className="w-20 h-14 rounded object-cover bg-[#231d18] flex-shrink-0" className="w-20 h-14 rounded object-cover bg-[#FFFFFF] flex-shrink-0"
loading="lazy" loading="lazy"
/> />
<div className="min-w-0"> <div className="min-w-0">
<h3 className="text-xs font-semibold text-[#e0e0e0] leading-snug group-hover:text-[#F0A623] line-clamp-3"> <h3 className="text-xs font-semibold text-[#e0e0e0] leading-snug group-hover:text-[#1D4ED8] line-clamp-3">
{r.title} {r.title}
</h3> </h3>
{r.wp_published_at && ( {r.wp_published_at && (
@@ -447,7 +447,7 @@ export default async function ManufacturerProfile({
{p.slug ? ( {p.slug ? (
<Link <Link
href={`/news/${p.slug}`} href={`/news/${p.slug}`}
className="text-[#F0A623] hover:underline" className="text-[#1D4ED8] hover:underline"
> >
{p.title} {p.title}
</Link> </Link>
@@ -465,7 +465,7 @@ export default async function ManufacturerProfile({
</section> </section>
)} )}
<footer className="text-xs text-[#666] mt-12 pt-6 border-t border-[#3a322b]"> <footer className="text-xs text-[#666] mt-12 pt-6 border-t border-[#DCE6F2]">
Listing maintained by AV Beat editorial. Listing maintained by AV Beat editorial.
{company.last_mentioned && ( {company.last_mentioned && (
<> Last mentioned in coverage on {new Date(company.last_mentioned).toISOString().slice(0, 10)}.</> <> Last mentioned in coverage on {new Date(company.last_mentioned).toISOString().slice(0, 10)}.</>

View File

@@ -72,7 +72,7 @@ export default async function ManufacturersIndex() {
const ibcCount = list.filter((m) => m.exhibits_ibc).length; const ibcCount = list.filter((m) => m.exhibits_ibc).length;
return ( return (
<div className="min-h-screen bg-[#1c1815]"> <div className="min-h-screen bg-[#F8FAFC]">
<Header /> <Header />
<div className="max-w-6xl mx-auto px-4 py-10"> <div className="max-w-6xl mx-auto px-4 py-10">
<header className="mb-8"> <header className="mb-8">
@@ -94,7 +94,7 @@ export default async function ManufacturersIndex() {
<ManufacturerDirectoryClient manufacturers={list} shows={events} /> <ManufacturerDirectoryClient manufacturers={list} shows={events} />
<section className="mt-16 border-t border-[#3a322b] pt-8 text-sm text-[#888] leading-relaxed"> <section className="mt-16 border-t border-[#DCE6F2] pt-8 text-sm text-[#888] leading-relaxed">
<p> <p>
Sources: 2026 NAB Show exhibitor list (Map Your Show), IBC exhibitor list, Sources: 2026 NAB Show exhibitor list (Map Your Show), IBC exhibitor list,
manufacturer-direct press releases, and reporting tracked on AV Beat. manufacturer-direct press releases, and reporting tracked on AV Beat.
@@ -102,7 +102,7 @@ export default async function ManufacturersIndex() {
</p> </p>
<p className="mt-2"> <p className="mt-2">
Are you a manufacturer and want to update your listing?{" "} Are you a manufacturer and want to update your listing?{" "}
<a href="mailto:editors@avbeat.com?subject=Manufacturer%20Directory%20Update" className="text-[#F0A623] hover:underline"> <a href="mailto:editors@avbeat.com?subject=Manufacturer%20Directory%20Update" className="text-[#1D4ED8] hover:underline">
Contact our editors Contact our editors
</a> </a>
. .

View File

@@ -8,7 +8,7 @@ export const metadata = {
export default function BillingPage() { export default function BillingPage() {
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-3xl mx-auto px-4 py-24"> <div className="max-w-3xl mx-auto px-4 py-24">
<div className="flex items-center gap-2 text-sm text-gray-500 mb-8"> <div className="flex items-center gap-2 text-sm text-gray-500 mb-8">
@@ -84,7 +84,7 @@ export default function BillingPage() {
className={`rounded-xl p-6 border relative ${ className={`rounded-xl p-6 border relative ${
plan?.highlight plan?.highlight
? 'bg-[#CC0000]/10 border-[#CC0000]' ? 'bg-[#CC0000]/10 border-[#CC0000]'
: 'bg-[#231d18] border-white/10' : 'bg-[#FFFFFF] border-white/10'
}`} }`}
> >
{plan?.highlight && ( {plan?.highlight && (
@@ -120,7 +120,7 @@ export default function BillingPage() {
</div> </div>
{/* Job post pricing */} {/* Job post pricing */}
<div className="bg-[#231d18] border border-white/10 rounded-xl p-6 mb-6"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl p-6 mb-6">
<h3 className="font-bold text-lg mb-4">One-Time Listings (Coming Soon)</h3> <h3 className="font-bold text-lg mb-4">One-Time Listings (Coming Soon)</h3>
<div className="grid grid-cols-2 gap-4 text-sm"> <div className="grid grid-cols-2 gap-4 text-sm">
<div className="flex justify-between py-2 border-b border-white/5"> <div className="flex justify-between py-2 border-b border-white/5">

View File

@@ -173,14 +173,14 @@ export default function AccountPage() {
if (loading || profileLoading) { if (loading || profileLoading) {
return ( return (
<div className="min-h-screen bg-[#1c1815] flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] flex items-center justify-center">
<div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
} }
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-4xl mx-auto px-4 py-24"> <div className="max-w-4xl mx-auto px-4 py-24">
<div className="flex items-center gap-2 text-sm text-gray-500 mb-6"> <div className="flex items-center gap-2 text-sm text-gray-500 mb-6">
@@ -195,7 +195,7 @@ export default function AccountPage() {
{/* No profile yet */} {/* No profile yet */}
{!profile && ( {!profile && (
<div className="bg-[#231d18] border border-white/10 rounded-xl p-8 text-center"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl p-8 text-center">
<div className="text-4xl mb-4">👤</div> <div className="text-4xl mb-4">👤</div>
<h2 className="text-xl font-bold mb-3">No Profile Yet</h2> <h2 className="text-xl font-bold mb-3">No Profile Yet</h2>
<p className="text-gray-400 mb-6"> <p className="text-gray-400 mb-6">
@@ -213,7 +213,7 @@ export default function AccountPage() {
{/* Profile status card */} {/* Profile status card */}
{profile && ( {profile && (
<div className="grid md:grid-cols-3 gap-6 mb-8"> <div className="grid md:grid-cols-3 gap-6 mb-8">
<div className="bg-[#231d18] border border-white/10 rounded-xl p-6"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl p-6">
<div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Profile Status</div> <div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Profile Status</div>
<div <div
className={`text-lg font-bold capitalize ${ className={`text-lg font-bold capitalize ${
@@ -234,7 +234,7 @@ export default function AccountPage() {
)} )}
</div> </div>
<div className="bg-[#231d18] border border-white/10 rounded-xl p-6"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl p-6">
<div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Plan</div> <div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Plan</div>
<div className="text-lg font-bold capitalize text-white"> <div className="text-lg font-bold capitalize text-white">
{profile.subscription_tier === 'featured' ?'⭐ Featured' {profile.subscription_tier === 'featured' ?'⭐ Featured'
@@ -248,7 +248,7 @@ export default function AccountPage() {
</Link> </Link>
</div> </div>
<div className="bg-[#231d18] border border-white/10 rounded-xl p-6"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl p-6">
<div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Profile Type</div> <div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Profile Type</div>
<div className="text-lg font-bold capitalize text-white"> <div className="text-lg font-bold capitalize text-white">
{profile.profile_type === 'crew' ? '🎬 Crew Member' : '🏢 Vendor / Company'} {profile.profile_type === 'crew' ? '🎬 Crew Member' : '🏢 Vendor / Company'}
@@ -272,7 +272,7 @@ export default function AccountPage() {
{/* Subscription gate notice */} {/* Subscription gate notice */}
{!hasActiveSubscription && ( {!hasActiveSubscription && (
<div className="bg-[#231d18] border border-yellow-500/30 rounded-xl p-6"> <div className="bg-[#FFFFFF] border border-yellow-500/30 rounded-xl p-6">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="text-2xl">💳</div> <div className="text-2xl">💳</div>
<div> <div>
@@ -293,7 +293,7 @@ export default function AccountPage() {
)} )}
{/* Job post form */} {/* Job post form */}
<div className="bg-[#231d18] border border-white/10 rounded-xl overflow-hidden"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl overflow-hidden">
<button <button
onClick={() => setShowJobForm((p) => !p)} onClick={() => setShowJobForm((p) => !p)}
disabled={!hasActiveSubscription} disabled={!hasActiveSubscription}
@@ -315,7 +315,7 @@ export default function AccountPage() {
value={jobTitle} value={jobTitle}
onChange={(e) => setJobTitle(e.target.value)} onChange={(e) => setJobTitle(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="e.g. Senior AV Integrator" placeholder="e.g. Senior AV Integrator"
/> />
</div> </div>
@@ -325,7 +325,7 @@ export default function AccountPage() {
value={jobCompany} value={jobCompany}
onChange={(e) => setJobCompany(e.target.value)} onChange={(e) => setJobCompany(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
/> />
</div> </div>
<div> <div>
@@ -334,7 +334,7 @@ export default function AccountPage() {
value={jobLocation} value={jobLocation}
onChange={(e) => setJobLocation(e.target.value)} onChange={(e) => setJobLocation(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="City, State or Remote" placeholder="City, State or Remote"
/> />
</div> </div>
@@ -343,7 +343,7 @@ export default function AccountPage() {
<select <select
value={jobType} value={jobType}
onChange={(e) => setJobType(e.target.value)} onChange={(e) => setJobType(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
> >
{JOB_TYPES.map((t) => ( {JOB_TYPES.map((t) => (
<option key={t.value} value={t.value}> <option key={t.value} value={t.value}>
@@ -359,14 +359,14 @@ export default function AccountPage() {
type="number" type="number"
value={jobSalaryMin} value={jobSalaryMin}
onChange={(e) => setJobSalaryMin(e.target.value)} onChange={(e) => setJobSalaryMin(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="Min $" placeholder="Min $"
/> />
<input <input
type="number" type="number"
value={jobSalaryMax} value={jobSalaryMax}
onChange={(e) => setJobSalaryMax(e.target.value)} onChange={(e) => setJobSalaryMax(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="Max $" placeholder="Max $"
/> />
</div> </div>
@@ -378,7 +378,7 @@ export default function AccountPage() {
onChange={(e) => setJobDescription(e.target.value)} onChange={(e) => setJobDescription(e.target.value)}
required required
rows={5} rows={5}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000] resize-none" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000] resize-none"
placeholder="Job description, requirements, responsibilities..." placeholder="Job description, requirements, responsibilities..."
/> />
</div> </div>
@@ -387,7 +387,7 @@ export default function AccountPage() {
<input <input
value={jobApplyUrl} value={jobApplyUrl}
onChange={(e) => setJobApplyUrl(e.target.value)} onChange={(e) => setJobApplyUrl(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="https://..." placeholder="https://..."
/> />
</div> </div>
@@ -397,7 +397,7 @@ export default function AccountPage() {
type="email" type="email"
value={jobApplyEmail} value={jobApplyEmail}
onChange={(e) => setJobApplyEmail(e.target.value)} onChange={(e) => setJobApplyEmail(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="jobs@company.com" placeholder="jobs@company.com"
/> />
</div> </div>
@@ -424,7 +424,7 @@ export default function AccountPage() {
</div> </div>
{/* Gig post form */} {/* Gig post form */}
<div className="bg-[#231d18] border border-white/10 rounded-xl overflow-hidden"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl overflow-hidden">
<button <button
onClick={() => setShowGigForm((p) => !p)} onClick={() => setShowGigForm((p) => !p)}
disabled={!hasActiveSubscription} disabled={!hasActiveSubscription}
@@ -446,7 +446,7 @@ export default function AccountPage() {
value={gigTitle} value={gigTitle}
onChange={(e) => setGigTitle(e.target.value)} onChange={(e) => setGigTitle(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="e.g. DP needed for 2-day commercial shoot" placeholder="e.g. DP needed for 2-day commercial shoot"
/> />
</div> </div>
@@ -456,7 +456,7 @@ export default function AccountPage() {
value={gigCompany} value={gigCompany}
onChange={(e) => setGigCompany(e.target.value)} onChange={(e) => setGigCompany(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
/> />
</div> </div>
<div> <div>
@@ -465,7 +465,7 @@ export default function AccountPage() {
value={gigLocation} value={gigLocation}
onChange={(e) => setGigLocation(e.target.value)} onChange={(e) => setGigLocation(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
/> />
</div> </div>
<div> <div>
@@ -474,7 +474,7 @@ export default function AccountPage() {
value={gigType} value={gigType}
onChange={(e) => setGigType(e.target.value)} onChange={(e) => setGigType(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
> >
<option value="">Select type...</option> <option value="">Select type...</option>
{GIG_TYPES.map((t) => ( {GIG_TYPES.map((t) => (
@@ -489,7 +489,7 @@ export default function AccountPage() {
<input <input
value={gigRate} value={gigRate}
onChange={(e) => setGigRate(e.target.value)} onChange={(e) => setGigRate(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="e.g. $750/day or negotiable" placeholder="e.g. $750/day or negotiable"
/> />
</div> </div>
@@ -499,7 +499,7 @@ export default function AccountPage() {
type="date" type="date"
value={gigStartDate} value={gigStartDate}
onChange={(e) => setGigStartDate(e.target.value)} onChange={(e) => setGigStartDate(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
/> />
</div> </div>
<div> <div>
@@ -508,7 +508,7 @@ export default function AccountPage() {
type="date" type="date"
value={gigEndDate} value={gigEndDate}
onChange={(e) => setGigEndDate(e.target.value)} onChange={(e) => setGigEndDate(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
/> />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
@@ -518,7 +518,7 @@ export default function AccountPage() {
onChange={(e) => setGigDescription(e.target.value)} onChange={(e) => setGigDescription(e.target.value)}
required required
rows={4} rows={4}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000] resize-none" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000] resize-none"
placeholder="Project details, crew needed, requirements..." placeholder="Project details, crew needed, requirements..."
/> />
</div> </div>
@@ -527,7 +527,7 @@ export default function AccountPage() {
<input <input
value={gigContact} value={gigContact}
onChange={(e) => setGigContact(e.target.value)} onChange={(e) => setGigContact(e.target.value)}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-[#CC0000]"
placeholder="Email address, phone, or instructions" placeholder="Email address, phone, or instructions"
/> />
</div> </div>

View File

@@ -173,7 +173,7 @@ export default function AdminQueuePage() {
if (loading || checkingAdmin) { if (loading || checkingAdmin) {
return ( return (
<div className="min-h-screen bg-[#1c1815] flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] flex items-center justify-center">
<div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
@@ -181,7 +181,7 @@ export default function AdminQueuePage() {
if (!isAdmin) { if (!isAdmin) {
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] text-white flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-4xl mb-4">🔒</div> <div className="text-4xl mb-4">🔒</div>
<h1 className="text-2xl font-bold mb-2">Access Denied</h1> <h1 className="text-2xl font-bold mb-2">Access Denied</h1>
@@ -197,7 +197,7 @@ export default function AdminQueuePage() {
const pendingCount = profiles.filter((p) => p.status === 'pending').length; const pendingCount = profiles.filter((p) => p.status === 'pending').length;
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-6xl mx-auto px-4 py-24"> <div className="max-w-6xl mx-auto px-4 py-24">
{/* Header */} {/* Header */}
@@ -235,7 +235,7 @@ export default function AdminQueuePage() {
className={`px-4 py-2 rounded-lg text-sm font-bold capitalize transition-colors ${ className={`px-4 py-2 rounded-lg text-sm font-bold capitalize transition-colors ${
filter === f filter === f
? 'bg-[#CC0000] text-white' ? 'bg-[#CC0000] text-white'
: 'bg-[#231d18] text-gray-400 hover:text-white border border-white/10' : 'bg-[#FFFFFF] text-gray-400 hover:text-white border border-white/10'
}`} }`}
> >
{f} {f}
@@ -266,7 +266,7 @@ export default function AdminQueuePage() {
return ( return (
<div <div
key={profile.id} key={profile.id}
className={`bg-[#231d18] rounded-xl border overflow-hidden ${ className={`bg-[#FFFFFF] rounded-xl border overflow-hidden ${
isPending ? 'border-yellow-500/30' : 'border-white/10' isPending ? 'border-yellow-500/30' : 'border-white/10'
}`} }`}
> >
@@ -377,7 +377,7 @@ export default function AdminQueuePage() {
[profile.id]: e.target.value, [profile.id]: e.target.value,
})) }))
} }
className="flex-1 bg-[#1c1815] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-red-500" className="flex-1 bg-[#F8FAFC] border border-white/20 rounded-lg px-3 py-2.5 text-white text-sm focus:outline-none focus:border-red-500"
placeholder="Rejection reason (required)..." placeholder="Rejection reason (required)..."
/> />
<button <button

View File

@@ -31,7 +31,7 @@ export default function GigDetailPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#1c1815] flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] flex items-center justify-center">
<div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
@@ -39,7 +39,7 @@ export default function GigDetailPage() {
if (!gig) { if (!gig) {
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] text-white flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<h1 className="text-2xl font-bold mb-2">Gig Not Found</h1> <h1 className="text-2xl font-bold mb-2">Gig Not Found</h1>
<Link href="/marketplace/gigs" className="text-[#CC0000] hover:underline">Back to Gigs</Link> <Link href="/marketplace/gigs" className="text-[#CC0000] hover:underline">Back to Gigs</Link>
@@ -49,7 +49,7 @@ export default function GigDetailPage() {
} }
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-3xl mx-auto px-4 py-24"> <div className="max-w-3xl mx-auto px-4 py-24">
<div className="flex items-center gap-2 text-sm text-gray-500 mb-8"> <div className="flex items-center gap-2 text-sm text-gray-500 mb-8">
@@ -60,7 +60,7 @@ export default function GigDetailPage() {
<span className="text-white truncate">{gig.title}</span> <span className="text-white truncate">{gig.title}</span>
</div> </div>
<div className="bg-[#231d18] rounded-2xl border border-white/10 overflow-hidden"> <div className="bg-[#FFFFFF] rounded-2xl border border-white/10 overflow-hidden">
<div className="bg-gradient-to-r from-[#CC0000]/20 to-transparent p-8"> <div className="bg-gradient-to-r from-[#CC0000]/20 to-transparent p-8">
<h1 className="text-2xl font-black mb-2">{gig.title}</h1> <h1 className="text-2xl font-black mb-2">{gig.title}</h1>
<div className="text-[#CC0000] font-bold text-lg mb-4">{gig.company}</div> <div className="text-[#CC0000] font-bold text-lg mb-4">{gig.company}</div>
@@ -85,7 +85,7 @@ export default function GigDetailPage() {
<div className="border-t border-white/10 pt-6"> <div className="border-t border-white/10 pt-6">
<h2 className="text-sm font-bold uppercase tracking-wide text-gray-400 mb-4">How to Respond</h2> <h2 className="text-sm font-bold uppercase tracking-wide text-gray-400 mb-4">How to Respond</h2>
{gig.contact_method ? ( {gig.contact_method ? (
<div className="bg-[#1c1815] rounded-lg p-4 text-sm text-gray-300">{gig.contact_method}</div> <div className="bg-[#F8FAFC] rounded-lg p-4 text-sm text-gray-300">{gig.contact_method}</div>
) : ( ) : (
<p className="text-gray-400 text-sm">Contact the poster directly through the platform.</p> <p className="text-gray-400 text-sm">Contact the poster directly through the platform.</p>
)} )}

View File

@@ -26,7 +26,7 @@ export default function GigsPage() {
}, []); }, []);
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-5xl mx-auto px-4 py-24"> <div className="max-w-5xl mx-auto px-4 py-24">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
@@ -49,7 +49,7 @@ export default function GigsPage() {
</Link> </Link>
</div> </div>
<div className="bg-[#231d18] border border-yellow-500/30 rounded-lg p-4 text-sm text-yellow-400 mb-8"> <div className="bg-[#FFFFFF] border border-yellow-500/30 rounded-lg p-4 text-sm text-yellow-400 mb-8">
💳 <strong>Gig posting requires a Pro subscription.</strong> Billing is coming soon 💳 <strong>Gig posting requires a Pro subscription.</strong> Billing is coming soon
create your free profile now and post gigs when payments launch. create your free profile now and post gigs when payments launch.
</div> </div>
@@ -57,7 +57,7 @@ export default function GigsPage() {
{loading ? ( {loading ? (
<div className="space-y-4"> <div className="space-y-4">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<div key={i} className="bg-[#231d18] rounded-xl p-6 animate-pulse h-28" /> <div key={i} className="bg-[#FFFFFF] rounded-xl p-6 animate-pulse h-28" />
))} ))}
</div> </div>
) : gigs.length === 0 ? ( ) : gigs.length === 0 ? (
@@ -72,7 +72,7 @@ export default function GigsPage() {
<Link <Link
key={gig.id} key={gig.id}
href={`/marketplace/gigs/${gig.id}`} href={`/marketplace/gigs/${gig.id}`}
className="block bg-[#231d18] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/30 rounded-xl p-6 transition-all" className="block bg-[#FFFFFF] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/30 rounded-xl p-6 transition-all"
> >
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4">
<div className="flex-1"> <div className="flex-1">

View File

@@ -31,7 +31,7 @@ export default function JobDetailPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-[#1c1815] flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] flex items-center justify-center">
<div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" /> <div className="w-8 h-8 border-4 border-[#CC0000] border-t-transparent rounded-full animate-spin" />
</div> </div>
); );
@@ -39,7 +39,7 @@ export default function JobDetailPage() {
if (!job) { if (!job) {
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white flex items-center justify-center"> <div className="min-h-screen bg-[#F8FAFC] text-white flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<h1 className="text-2xl font-bold mb-2">Job Not Found</h1> <h1 className="text-2xl font-bold mb-2">Job Not Found</h1>
<Link href="/marketplace/jobs" className="text-[#CC0000] hover:underline">Back to Jobs</Link> <Link href="/marketplace/jobs" className="text-[#CC0000] hover:underline">Back to Jobs</Link>
@@ -49,7 +49,7 @@ export default function JobDetailPage() {
} }
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-3xl mx-auto px-4 py-24"> <div className="max-w-3xl mx-auto px-4 py-24">
<div className="flex items-center gap-2 text-sm text-gray-500 mb-8"> <div className="flex items-center gap-2 text-sm text-gray-500 mb-8">
@@ -60,7 +60,7 @@ export default function JobDetailPage() {
<span className="text-white truncate">{job.title}</span> <span className="text-white truncate">{job.title}</span>
</div> </div>
<div className="bg-[#231d18] rounded-2xl border border-white/10 overflow-hidden"> <div className="bg-[#FFFFFF] rounded-2xl border border-white/10 overflow-hidden">
<div className="bg-gradient-to-r from-[#CC0000]/20 to-transparent p-8"> <div className="bg-gradient-to-r from-[#CC0000]/20 to-transparent p-8">
<h1 className="text-2xl font-black mb-2">{job.title}</h1> <h1 className="text-2xl font-black mb-2">{job.title}</h1>
<div className="text-[#CC0000] font-bold text-lg mb-4">{job.company}</div> <div className="text-[#CC0000] font-bold text-lg mb-4">{job.company}</div>

View File

@@ -26,7 +26,7 @@ export default function JobsPage() {
}, []); }, []);
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-5xl mx-auto px-4 py-24"> <div className="max-w-5xl mx-auto px-4 py-24">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
@@ -49,7 +49,7 @@ export default function JobsPage() {
</Link> </Link>
</div> </div>
<div className="bg-[#231d18] border border-yellow-500/30 rounded-lg p-4 text-sm text-yellow-400 mb-8"> <div className="bg-[#FFFFFF] border border-yellow-500/30 rounded-lg p-4 text-sm text-yellow-400 mb-8">
💳 <strong>Job posting requires a Pro subscription.</strong> Billing is coming soon 💳 <strong>Job posting requires a Pro subscription.</strong> Billing is coming soon
create your free profile now and post jobs when payments launch. create your free profile now and post jobs when payments launch.
</div> </div>
@@ -57,7 +57,7 @@ export default function JobsPage() {
{loading ? ( {loading ? (
<div className="space-y-4"> <div className="space-y-4">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<div key={i} className="bg-[#231d18] rounded-xl p-6 animate-pulse h-28" /> <div key={i} className="bg-[#FFFFFF] rounded-xl p-6 animate-pulse h-28" />
))} ))}
</div> </div>
) : jobs.length === 0 ? ( ) : jobs.length === 0 ? (
@@ -72,7 +72,7 @@ export default function JobsPage() {
<Link <Link
key={job.id} key={job.id}
href={`/marketplace/jobs/${job.id}`} href={`/marketplace/jobs/${job.id}`}
className="block bg-[#231d18] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/30 rounded-xl p-6 transition-all" className="block bg-[#FFFFFF] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/30 rounded-xl p-6 transition-all"
> >
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4">
<div className="flex-1"> <div className="flex-1">

View File

@@ -243,7 +243,7 @@ export default function JoinMarketplacePage() {
}; };
return ( return (
<div className="min-h-screen bg-[#1c1815] text-white"> <div className="min-h-screen bg-[#F8FAFC] text-white">
<Header /> <Header />
<div className="max-w-2xl mx-auto px-4 py-24"> <div className="max-w-2xl mx-auto px-4 py-24">
{/* Header */} {/* Header */}
@@ -261,7 +261,7 @@ export default function JoinMarketplacePage() {
{/* Auth gate */} {/* Auth gate */}
{!user && ( {!user && (
<div className="bg-[#231d18] border border-white/10 rounded-xl p-8 mb-8"> <div className="bg-[#FFFFFF] border border-white/10 rounded-xl p-8 mb-8">
<h2 className="text-lg font-bold mb-6 text-center"> <h2 className="text-lg font-bold mb-6 text-center">
{authMode === 'signup' ? 'Create an Account' : 'Sign In to Continue'} {authMode === 'signup' ? 'Create an Account' : 'Sign In to Continue'}
</h2> </h2>
@@ -273,7 +273,7 @@ export default function JoinMarketplacePage() {
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required required
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="you@example.com" placeholder="you@example.com"
/> />
</div> </div>
@@ -285,7 +285,7 @@ export default function JoinMarketplacePage() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
required required
minLength={6} minLength={6}
className="w-full bg-[#1c1815] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#F8FAFC] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="••••••••" placeholder="••••••••"
/> />
</div> </div>
@@ -325,7 +325,7 @@ export default function JoinMarketplacePage() {
setProfileType('crew'); setProfileType('crew');
setStep('details'); setStep('details');
}} }}
className="w-full bg-[#231d18] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/50 rounded-xl p-6 text-left transition-all group" className="w-full bg-[#FFFFFF] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/50 rounded-xl p-6 text-left transition-all group"
> >
<div className="text-2xl mb-2">🎬</div> <div className="text-2xl mb-2">🎬</div>
<div className="font-bold text-lg group-hover:text-[#CC0000] transition-colors"> <div className="font-bold text-lg group-hover:text-[#CC0000] transition-colors">
@@ -340,7 +340,7 @@ export default function JoinMarketplacePage() {
setProfileType('vendor'); setProfileType('vendor');
setStep('details'); setStep('details');
}} }}
className="w-full bg-[#231d18] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/50 rounded-xl p-6 text-left transition-all group" className="w-full bg-[#FFFFFF] hover:bg-[#222] border border-white/10 hover:border-[#CC0000]/50 rounded-xl p-6 text-left transition-all group"
> >
<div className="text-2xl mb-2">🏢</div> <div className="text-2xl mb-2">🏢</div>
<div className="font-bold text-lg group-hover:text-[#CC0000] transition-colors"> <div className="font-bold text-lg group-hover:text-[#CC0000] transition-colors">
@@ -376,7 +376,7 @@ export default function JoinMarketplacePage() {
value={crewName} value={crewName}
onChange={(e) => setCrewName(e.target.value)} onChange={(e) => setCrewName(e.target.value)}
required required
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="Your professional name" placeholder="Your professional name"
/> />
</div> </div>
@@ -388,7 +388,7 @@ export default function JoinMarketplacePage() {
value={crewLocation} value={crewLocation}
onChange={(e) => setCrewLocation(e.target.value)} onChange={(e) => setCrewLocation(e.target.value)}
required required
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="City, State / Country" placeholder="City, State / Country"
/> />
</div> </div>
@@ -397,7 +397,7 @@ export default function JoinMarketplacePage() {
<select <select
value={crewUnion} value={crewUnion}
onChange={(e) => setCrewUnion(e.target.value)} onChange={(e) => setCrewUnion(e.target.value)}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
> >
<option value="non_union">Non-Union</option> <option value="non_union">Non-Union</option>
<option value="iatse">IATSE</option> <option value="iatse">IATSE</option>
@@ -412,7 +412,7 @@ export default function JoinMarketplacePage() {
type="number" type="number"
value={crewRate} value={crewRate}
onChange={(e) => setCrewRate(e.target.value)} onChange={(e) => setCrewRate(e.target.value)}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="e.g. 750" placeholder="e.g. 750"
min="0" min="0"
/> />
@@ -426,7 +426,7 @@ export default function JoinMarketplacePage() {
onChange={(e) => setCrewBio(e.target.value)} onChange={(e) => setCrewBio(e.target.value)}
rows={4} rows={4}
maxLength={500} maxLength={500}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000] resize-none" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000] resize-none"
placeholder="Brief professional bio (500 chars max)" placeholder="Brief professional bio (500 chars max)"
/> />
<div className="text-xs text-gray-500 text-right mt-1">{crewBio.length}/500</div> <div className="text-xs text-gray-500 text-right mt-1">{crewBio.length}/500</div>
@@ -457,7 +457,7 @@ export default function JoinMarketplacePage() {
<input <input
value={crewEquipment} value={crewEquipment}
onChange={(e) => setCrewEquipment(e.target.value)} onChange={(e) => setCrewEquipment(e.target.value)}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="e.g. Sony FX9, DJI Ronin, Sennheiser MKH416 (comma-separated)" placeholder="e.g. Sony FX9, DJI Ronin, Sennheiser MKH416 (comma-separated)"
/> />
</div> </div>
@@ -467,7 +467,7 @@ export default function JoinMarketplacePage() {
<input <input
value={crewReel} value={crewReel}
onChange={(e) => setCrewReel(e.target.value)} onChange={(e) => setCrewReel(e.target.value)}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="https://vimeo.com/..." placeholder="https://vimeo.com/..."
/> />
</div> </div>
@@ -488,7 +488,7 @@ export default function JoinMarketplacePage() {
type="file" type="file"
accept="application/pdf,.doc,.docx" accept="application/pdf,.doc,.docx"
onChange={(e) => setResumeFile(e.target.files?.[0] || null)} onChange={(e) => setResumeFile(e.target.files?.[0] || null)}
className="w-full text-sm text-gray-400 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-[#231d18] file:border file:border-white/20 file:text-white file:cursor-pointer" className="w-full text-sm text-gray-400 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-[#FFFFFF] file:border file:border-white/20 file:text-white file:cursor-pointer"
/> />
</div> </div>
</div> </div>
@@ -532,7 +532,7 @@ export default function JoinMarketplacePage() {
value={vendorName} value={vendorName}
onChange={(e) => setVendorName(e.target.value)} onChange={(e) => setVendorName(e.target.value)}
required required
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="Your company name" placeholder="Your company name"
/> />
</div> </div>
@@ -546,7 +546,7 @@ export default function JoinMarketplacePage() {
value={vendorLocation} value={vendorLocation}
onChange={(e) => setVendorLocation(e.target.value)} onChange={(e) => setVendorLocation(e.target.value)}
required required
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="City, State / Country" placeholder="City, State / Country"
/> />
</div> </div>
@@ -558,7 +558,7 @@ export default function JoinMarketplacePage() {
value={vendorCategory} value={vendorCategory}
onChange={(e) => setVendorCategory(e.target.value)} onChange={(e) => setVendorCategory(e.target.value)}
required required
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
> >
<option value="">Select category...</option> <option value="">Select category...</option>
{VENDOR_CATEGORIES.map((c) => ( {VENDOR_CATEGORIES.map((c) => (
@@ -577,7 +577,7 @@ export default function JoinMarketplacePage() {
onChange={(e) => setVendorDescription(e.target.value)} onChange={(e) => setVendorDescription(e.target.value)}
rows={4} rows={4}
maxLength={1000} maxLength={1000}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000] resize-none" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000] resize-none"
placeholder="Describe your services, specialties, and what makes you stand out" placeholder="Describe your services, specialties, and what makes you stand out"
/> />
</div> </div>
@@ -588,7 +588,7 @@ export default function JoinMarketplacePage() {
<input <input
value={vendorWebsite} value={vendorWebsite}
onChange={(e) => setVendorWebsite(e.target.value)} onChange={(e) => setVendorWebsite(e.target.value)}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="https://yourcompany.com" placeholder="https://yourcompany.com"
/> />
</div> </div>
@@ -598,7 +598,7 @@ export default function JoinMarketplacePage() {
type="email" type="email"
value={vendorEmail} value={vendorEmail}
onChange={(e) => setVendorEmail(e.target.value)} onChange={(e) => setVendorEmail(e.target.value)}
className="w-full bg-[#231d18] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]" className="w-full bg-[#FFFFFF] border border-white/20 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-[#CC0000]"
placeholder="contact@yourcompany.com" placeholder="contact@yourcompany.com"
/> />
</div> </div>

Some files were not shown because too many files have changed in this diff Show More