Files
avbeat-com/src/app/account/page.tsx

843 lines
38 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import AppImage from "@/components/ui/AppImage";
import { createClient } from "@/lib/supabase/client";
import { useRouter } from "next/navigation";
interface UserProfile {
id: string;
email: string;
full_name: string;
avatar_url: string | null;
bio: string | null;
website: string | null;
location: string | null;
reputation: number;
created_at: string;
}
interface BookmarkItem {
id: string;
article_slug: string;
article_title: string;
article_excerpt: string | null;
article_image: string | null;
article_image_alt: string | null;
article_category: string | null;
article_author: string | null;
article_read_time: string | null;
article_date: string | null;
saved_at: string;
}
interface HistoryItem {
id: string;
article_slug: string;
article_title: string;
article_excerpt: string | null;
article_image: string | null;
article_image_alt: string | null;
article_category: string | null;
article_author: string | null;
article_read_time: string | null;
article_date: string | null;
viewed_at: string;
}
interface TopicPref {
id: string;
topic: string;
view_count: number;
last_viewed_at: string;
}
type ActiveTab = "profile" | "security" | "bookmarks" | "history" | "topics" | "preferences";
export default function AccountPage() {
const router = useRouter();
const supabase = createClient();
const [user, setUser] = useState<any>(null);
const [profile, setProfile] = useState<UserProfile | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [activeTab, setActiveTab] = useState<ActiveTab>("profile");
const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null);
// Profile form state
const [fullName, setFullName] = useState("");
const [bio, setBio] = useState("");
const [website, setWebsite] = useState("");
const [location, setLocation] = useState("");
// Security form state
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [changingPassword, setChangingPassword] = useState(false);
// Digest preference state
const [weeklyDigest, setWeeklyDigest] = useState(false);
const [savingDigest, setSavingDigest] = useState(false);
// Bookmarks
const [bookmarks, setBookmarks] = useState<BookmarkItem[]>([]);
const [bookmarksLoading, setBookmarksLoading] = useState(false);
// Reading History
const [history, setHistory] = useState<HistoryItem[]>([]);
const [historyLoading, setHistoryLoading] = useState(false);
// Topic Preferences
const [topics, setTopics] = useState<TopicPref[]>([]);
const [topicsLoading, setTopicsLoading] = useState(false);
const showToast = (message: string, type: "success" | "error") => {
setToast({ message, type });
setTimeout(() => setToast(null), 3500);
};
useEffect(() => {
const init = async () => {
const { data: { user: authUser } } = await supabase.auth.getUser();
if (!authUser) {
router.push("/login");
return;
}
setUser(authUser);
const { data: profileData } = await supabase
.from("user_profiles")
.select("*")
.eq("id", authUser.id)
.maybeSingle();
if (profileData) {
setProfile(profileData);
setFullName(profileData.full_name || "");
setBio(profileData.bio || "");
setWebsite(profileData.website || "");
setLocation(profileData.location || "");
}
setLoading(false);
};
init();
}, []);
useEffect(() => {
if (!user) return;
fetch("/api/digest/preferences")
.then((r) => r.json())
.then((d) => setWeeklyDigest(d.weekly_digest_enabled ?? false))
.catch(() => {});
}, [user]);
// Load bookmarks when tab is active
useEffect(() => {
if (activeTab !== "bookmarks" || !user) return;
setBookmarksLoading(true);
supabase
.from("reading_list")
.select("*")
.eq("user_id", user.id)
.order("saved_at", { ascending: false })
.then(({ data }) => {
setBookmarks(data || []);
setBookmarksLoading(false);
});
}, [activeTab, user]);
// Load reading history when tab is active
useEffect(() => {
if (activeTab !== "history" || !user) return;
setHistoryLoading(true);
supabase
.from("reading_history")
.select("*")
.eq("user_id", user.id)
.order("viewed_at", { ascending: false })
.limit(50)
.then(({ data }) => {
setHistory(data || []);
setHistoryLoading(false);
});
}, [activeTab, user]);
// Load topic preferences when tab is active
useEffect(() => {
if (activeTab !== "topics" || !user) return;
setTopicsLoading(true);
supabase
.from("user_topic_preferences")
.select("*")
.eq("user_id", user.id)
.order("view_count", { ascending: false })
.then(({ data }) => {
setTopics(data || []);
setTopicsLoading(false);
});
}, [activeTab, user]);
const handleSaveProfile = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) return;
setSaving(true);
try {
const { error } = await supabase
.from("user_profiles")
.upsert({
id: user.id,
email: user.email,
full_name: fullName,
bio,
website,
location,
});
if (error) throw error;
showToast("Profile updated successfully", "success");
} catch (err: any) {
showToast(err?.message || "Failed to update profile", "error");
} finally {
setSaving(false);
}
};
const handleChangePassword = async (e: React.FormEvent) => {
e.preventDefault();
if (newPassword !== confirmPassword) {
showToast("Passwords do not match", "error");
return;
}
if (newPassword.length < 6) {
showToast("Password must be at least 6 characters", "error");
return;
}
setChangingPassword(true);
try {
const { error } = await supabase.auth.updateUser({ password: newPassword });
if (error) throw error;
setNewPassword("");
setConfirmPassword("");
showToast("Password updated successfully", "success");
} catch (err: any) {
showToast(err?.message || "Failed to update password", "error");
} finally {
setChangingPassword(false);
}
};
const handleSignOut = async () => {
await supabase.auth.signOut();
router.push("/home-page");
};
const handleToggleDigest = async (enabled: boolean) => {
setSavingDigest(true);
try {
const res = await fetch("/api/digest/preferences", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ weekly_digest_enabled: enabled }),
});
if (!res.ok) throw new Error("Failed to update");
setWeeklyDigest(enabled);
showToast(enabled ? "Weekly digest enabled" : "Weekly digest disabled", "success");
} catch {
showToast("Failed to update digest preference", "error");
} finally {
setSavingDigest(false);
}
};
const handleRemoveBookmark = async (id: string, slug: string) => {
if (!user) return;
try {
await supabase.from("reading_list").delete().eq("id", id).eq("user_id", user.id);
setBookmarks((prev) => prev.filter((b) => b.id !== id));
showToast("Bookmark removed", "success");
} catch {
showToast("Failed to remove bookmark", "error");
}
};
const handleClearHistory = async () => {
if (!user) return;
try {
await supabase.from("reading_history").delete().eq("user_id", user.id);
setHistory([]);
showToast("Reading history cleared", "success");
} catch {
showToast("Failed to clear history", "error");
}
};
const tabs: { id: ActiveTab; label: string }[] = [
{ id: "profile", label: "Profile" },
{ id: "security", label: "Security" },
{ id: "bookmarks", label: "Bookmarks" },
{ id: "history", label: "History" },
{ id: "topics", label: "Topics" },
{ id: "preferences", label: "Preferences" },
];
if (loading) {
return (
<div className="min-h-screen bg-[#1c1815]">
<Header />
<div className="max-w-container mx-auto px-4 py-12">
<div className="max-w-3xl mx-auto space-y-4">
<div className="skeleton h-8 w-48 rounded-sm" />
<div className="skeleton h-[400px] rounded-sm" />
</div>
</div>
<Footer />
</div>
);
}
return (
<div className="min-h-screen bg-[#1c1815]">
<Header />
{/* Toast */}
{toast && (
<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 ${
toast.type === "success" ?"bg-[#231d18] border-[#F0A623] text-[#e0e0e0]" :"bg-[#1a0a0a] border-[#D85A30] text-[#e0e0e0]"
}`}>
{toast.type === "success" ? (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#F0A623" strokeWidth="2.5" aria-hidden="true">
<polyline points="20 6 9 17 4 12" />
</svg>
) : (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#D85A30" 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" />
</svg>
)}
{toast.message}
</div>
)}
{/* Breadcrumb */}
<div className="bg-[#1c1815] border-b border-[#1e1e1e]">
<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]">
<Link href="/home-page" className="hover:text-[#F0A623] transition-colors">Home</Link>
<span aria-hidden="true">/</span>
<span className="text-[#888]">Account</span>
</nav>
</div>
</div>
<main className="max-w-container mx-auto px-4 py-8">
<div className="max-w-3xl mx-auto">
{/* Page Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="font-heading text-2xl font-bold text-[#FBEFE0]">Account Settings</h1>
<p className="font-body text-sm text-[#666] mt-1">{user?.email}</p>
</div>
<button
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">
Sign Out
</button>
</div>
{/* Tabs */}
<div className="flex border-b border-[#3a322b] mb-6 overflow-x-auto scrollbar-none">
{tabs.map((tab) => (
<button
key={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 ${
activeTab === tab.id
? "border-[#F0A623] text-[#F0A623]"
: "border-transparent text-[#666] hover:text-[#aaa]"
}`}>
{tab.label}
</button>
))}
</div>
{/* Profile Tab */}
{activeTab === "profile" && (
<form onSubmit={handleSaveProfile} className="space-y-5">
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]">
Public Profile
</h2>
<div className="space-y-4">
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
Full Name
</label>
<input
type="text"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
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]"
/>
</div>
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
Bio
</label>
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="Tell us a bit about yourself..."
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"
/>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
Website
</label>
<input
type="url"
value={website}
onChange={(e) => setWebsite(e.target.value)}
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]"
/>
</div>
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
Location
</label>
<input
type="text"
value={location}
onChange={(e) => setLocation(e.target.value)}
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]"
/>
</div>
</div>
</div>
</div>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-3 pb-3 border-b border-[#3a322b]">
Account Info
</h2>
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
Email Address
</label>
<input
type="email"
value={user?.email || ""}
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"
/>
<p className="font-body text-xs text-[#555] mt-1.5">Email cannot be changed here.</p>
</div>
</div>
<div className="flex justify-end">
<button
type="submit"
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]">
{saving ? "Saving..." : "Save Profile"}
</button>
</div>
</form>
)}
{/* Security Tab */}
{activeTab === "security" && (
<div className="space-y-5">
<form onSubmit={handleChangePassword}>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]">
Change Password
</h2>
<div className="space-y-4">
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
New Password
</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="Enter new password"
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]"
/>
</div>
<div>
<label className="block font-body text-xs font-bold uppercase tracking-wider text-[#888] mb-1.5">
Confirm New Password
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Confirm new password"
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]"
/>
</div>
</div>
<div className="mt-5 flex justify-end">
<button
type="submit"
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]">
{changingPassword ? "Updating..." : "Update Password"}
</button>
</div>
</div>
</form>
<div className="bg-[#1a0a0a] border border-[#3a1a1a] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-2">Danger Zone</h2>
<p className="font-body text-sm text-[#888] mb-4">
Once you sign out, you will need to sign back in to access your account.
</p>
<button
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">
Sign Out of All Sessions
</button>
</div>
</div>
)}
{/* Bookmarks Tab */}
{activeTab === "bookmarks" && (
<div className="space-y-4">
<div className="flex items-center justify-between mb-2">
<h2 className="font-heading text-base font-bold text-[#e0e0e0]">
Saved Articles
{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">
{bookmarks.length}
</span>
)}
</h2>
<Link href="/reading-list" className="font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors">
Full List
</Link>
</div>
{bookmarksLoading ? (
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 animate-pulse">
<div className="flex gap-3">
<div className="w-20 h-14 bg-[#3a322b] rounded-sm flex-shrink-0" />
<div className="flex-1 space-y-2">
<div className="h-3 bg-[#3a322b] rounded w-1/4" />
<div className="h-4 bg-[#3a322b] rounded w-3/4" />
</div>
</div>
</div>
))}
</div>
) : bookmarks.length === 0 ? (
<div className="text-center py-12 bg-[#231d18] border border-[#3a322b] 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">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
</svg>
<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">
Browse News
</Link>
</div>
) : (
<div className="space-y-3">
{bookmarks.map((item) => (
<div key={item.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm hover:border-[#3a3a3a] transition-colors group">
<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">
{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" />
) : (
<div className="w-full h-full bg-[#3a322b] 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">
<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>
</div>
)}
</Link>
<div className="flex-1 min-w-0">
{item.article_category && (
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#F0A623]">{item.article_category}</span>
)}
<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">
{item.article_title}
</h3>
</Link>
<div className="flex items-center justify-between mt-1.5">
<span className="font-body text-[10px] text-[#555]">
Saved {new Date(item.saved_at).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
</span>
<button
onClick={() => handleRemoveBookmark(item.id, item.article_slug)}
className="font-body text-[10px] text-[#555] hover:text-red-400 transition-colors uppercase tracking-wide">
Remove
</button>
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* Reading History Tab */}
{activeTab === "history" && (
<div className="space-y-4">
<div className="flex items-center justify-between mb-2">
<h2 className="font-heading text-base font-bold text-[#e0e0e0]">
Reading History
{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">
{history.length}
</span>
)}
</h2>
{history.length > 0 && (
<button
onClick={handleClearHistory}
className="font-body text-xs text-[#555] hover:text-red-400 transition-colors uppercase tracking-wide">
Clear All
</button>
)}
</div>
{historyLoading ? (
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 animate-pulse">
<div className="flex gap-3">
<div className="w-20 h-14 bg-[#3a322b] rounded-sm flex-shrink-0" />
<div className="flex-1 space-y-2">
<div className="h-3 bg-[#3a322b] rounded w-1/4" />
<div className="h-4 bg-[#3a322b] rounded w-3/4" />
</div>
</div>
</div>
))}
</div>
) : history.length === 0 ? (
<div className="text-center py-12 bg-[#231d18] border border-[#3a322b] 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">
<circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
</svg>
<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">
Browse News
</Link>
</div>
) : (
<div className="space-y-3">
{history.map((item) => (
<div key={item.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm hover:border-[#3a3a3a] transition-colors group">
<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">
{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" />
) : (
<div className="w-full h-full bg-[#3a322b] 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">
<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>
</div>
)}
</Link>
<div className="flex-1 min-w-0">
{item.article_category && (
<span className="font-body text-[9px] font-bold uppercase tracking-widest text-[#F0A623]">{item.article_category}</span>
)}
<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">
{item.article_title}
</h3>
</Link>
<span className="font-body text-[10px] text-[#555] mt-1 block">
Viewed {new Date(item.viewed_at).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* Topic Preferences Tab */}
{activeTab === "topics" && (
<div className="space-y-4">
<div className="mb-2">
<h2 className="font-heading text-base font-bold text-[#e0e0e0]">Topic Interests</h2>
<p className="font-body text-xs text-[#666] mt-1">
Automatically tracked based on the articles you read. Used to personalize your content feed.
</p>
</div>
{topicsLoading ? (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{[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>
) : topics.length === 0 ? (
<div className="text-center py-12 bg-[#231d18] border border-[#3a322b] 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">
<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>
<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>
<Link href="/news" className="inline-block mt-3 font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors">
Browse News
</Link>
</div>
) : (
<>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{topics.map((t) => {
const maxCount = topics[0]?.view_count || 1;
const pct = Math.round((t.view_count / maxCount) * 100);
return (
<div key={t.id} className="bg-[#231d18] border border-[#3a322b] rounded-sm p-4 hover:border-[#3a3a3a] transition-colors">
<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">
{t.topic}
</span>
<span className="font-body text-[10px] text-[#F0A623] font-bold ml-2 flex-shrink-0">
{t.view_count}×
</span>
</div>
<div className="w-full bg-[#3a322b] rounded-full h-1">
<div
className="bg-[#F0A623] h-1 rounded-full transition-all"
style={{ width: `${pct}%` }}
/>
</div>
</div>
);
})}
</div>
<p className="font-body text-xs text-[#555] text-center mt-2">
Based on {topics.reduce((s, t) => s + t.view_count, 0)} article views across {topics.length} topics
</p>
</>
)}
</div>
)}
{/* Preferences Tab */}
{activeTab === "preferences" && (
<div className="space-y-5">
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]">
Forum Reputation
</h2>
<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]">
<span className="font-heading text-3xl font-bold text-[#F0A623]">
{profile?.reputation ?? 0}
</span>
<span className="font-body text-xs text-[#666] mt-1 uppercase tracking-wider">Points</span>
</div>
<div className="flex-1">
<p className="font-body text-sm text-[#aaa] leading-relaxed">
Earn reputation by posting helpful threads and replies in the community forum.
</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">
Go to Forum
</Link>
</div>
</div>
</div>
<div className="bg-[#231d18] border border-[#3a322b] rounded-sm p-6">
<h2 className="font-heading text-base font-bold text-[#e0e0e0] mb-5 pb-3 border-b border-[#3a322b]">
Reading Preferences
</h2>
<div className="space-y-4">
<div className="flex items-center justify-between py-3 border-b border-[#222]">
<div>
<p className="font-body text-sm font-bold text-[#e0e0e0]">Email Newsletter</p>
<p className="font-body text-xs text-[#666] mt-0.5">Receive weekly broadcast industry news</p>
</div>
<Link
href="/home-page"
className="font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors">
Manage
</Link>
</div>
<div className="flex items-center justify-between py-3 border-b border-[#222]">
<div>
<p className="font-body text-sm font-bold text-[#e0e0e0]">Reading List</p>
<p className="font-body text-xs text-[#666] mt-0.5">View your saved articles</p>
</div>
<Link
href="/reading-list"
className="font-body text-xs font-bold uppercase tracking-wide text-[#F0A623] hover:text-[#BA7517] transition-colors">
View List
</Link>
</div>
{/* Weekly Digest Toggle */}
<div className="flex items-center justify-between py-3 border-b border-[#222]">
<div className="flex-1 pr-4">
<p className="font-body text-sm font-bold text-[#e0e0e0]">Weekly Forum Digest</p>
<p className="font-body text-xs text-[#666] mt-0.5">
Get a weekly email summary of your thread replies, upvotes, and mentions
</p>
</div>
<button
type="button"
disabled={savingDigest}
onClick={() => handleToggleDigest(!weeklyDigest)}
aria-pressed={weeklyDigest}
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 ${
weeklyDigest ? "bg-[#F0A623] border-[#F0A623]" : "bg-[#3a322b] border-[#333]"
}`}>
<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 ${
weeklyDigest ? "translate-x-5" : "translate-x-0.5"
}`}
/>
</button>
</div>
<div className="flex items-center justify-between py-3">
<div>
<p className="font-body text-sm font-bold text-[#e0e0e0]">Member Since</p>
<p className="font-body text-xs text-[#666] mt-0.5">
{profile?.created_at
? new Date(profile.created_at).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })
: "—"}
</p>
</div>
</div>
</div>
</div>
</div>
)}
</div>
</main>
<Footer />
</div>
);
}