"use client"; import { useState, useEffect, useCallback, useRef } from "react"; import Image from "next/image"; import { Send, FileText, Clock, CheckCircle, XCircle, Plus, User, Loader2, AtSign } from "lucide-react"; import { useAuth } from "@/hooks/useAuth"; import { api } from "@/lib/api"; import { shortenPubkey } from "@/lib/nostr"; import { formatDate } from "@/lib/utils"; import { Button } from "@/components/ui/Button"; interface Submission { id: string; eventId?: string; naddr?: string; title: string; status: string; reviewNote?: string; createdAt: string; } const STATUS_CONFIG: Record = { PENDING: { label: "Pending Review", icon: Clock, className: "text-primary bg-primary/10", }, APPROVED: { label: "Approved", icon: CheckCircle, className: "text-green-400 bg-green-400/10", }, REJECTED: { label: "Rejected", icon: XCircle, className: "text-error bg-error/10", }, }; type Tab = "submissions" | "profile"; type UsernameStatus = | { state: "idle" } | { state: "checking" } | { state: "available" } | { state: "unavailable"; reason: string }; export default function DashboardPage() { const { user, login } = useAuth(); const [activeTab, setActiveTab] = useState("submissions"); // Submissions state const [submissions, setSubmissions] = useState([]); const [loadingSubs, setLoadingSubs] = useState(true); const [showForm, setShowForm] = useState(false); const [title, setTitle] = useState(""); const [eventId, setEventId] = useState(""); const [naddr, setNaddr] = useState(""); const [submitting, setSubmitting] = useState(false); const [formError, setFormError] = useState(""); const [formSuccess, setFormSuccess] = useState(""); // Profile state const [username, setUsername] = useState(""); const [usernameStatus, setUsernameStatus] = useState({ state: "idle" }); const [saving, setSaving] = useState(false); const [saveError, setSaveError] = useState(""); const [saveSuccess, setSaveSuccess] = useState(""); const [hostname, setHostname] = useState(""); const debounceRef = useRef | null>(null); const displayName = user?.name || user?.displayName || shortenPubkey(user?.pubkey || ""); useEffect(() => { setHostname(window.location.hostname); }, []); useEffect(() => { if (user?.username) { setUsername(user.username); } }, [user?.username]); const loadSubmissions = useCallback(async () => { try { const data = await api.getMySubmissions(); setSubmissions(data); } catch { // Silently handle } finally { setLoadingSubs(false); } }, []); useEffect(() => { loadSubmissions(); }, [loadSubmissions]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setFormError(""); setFormSuccess(""); if (!title.trim()) { setFormError("Title is required"); return; } if (!eventId.trim() && !naddr.trim()) { setFormError("Either an Event ID or naddr is required"); return; } setSubmitting(true); try { await api.createSubmission({ title: title.trim(), eventId: eventId.trim() || undefined, naddr: naddr.trim() || undefined, }); setFormSuccess("Submission sent for review!"); setTitle(""); setEventId(""); setNaddr(""); setShowForm(false); await loadSubmissions(); } catch (err: any) { setFormError(err.message || "Failed to submit"); } finally { setSubmitting(false); } }; const handleUsernameChange = (value: string) => { setUsername(value); setSaveError(""); setSaveSuccess(""); if (debounceRef.current) clearTimeout(debounceRef.current); const trimmed = value.trim().toLowerCase(); if (!trimmed || trimmed === (user?.username ?? "")) { setUsernameStatus({ state: "idle" }); return; } setUsernameStatus({ state: "checking" }); debounceRef.current = setTimeout(async () => { try { const result = await api.checkUsername(trimmed); if (result.available) { setUsernameStatus({ state: "available" }); } else { setUsernameStatus({ state: "unavailable", reason: result.reason || "Username is not available" }); } } catch { setUsernameStatus({ state: "unavailable", reason: "Could not check availability" }); } }, 500); }; const handleSaveProfile = async (e: React.FormEvent) => { e.preventDefault(); setSaveError(""); setSaveSuccess(""); const trimmed = username.trim().toLowerCase(); if (!trimmed) { setSaveError("Username is required"); return; } setSaving(true); try { const updated = await api.updateProfile({ username: trimmed }); setSaveSuccess(`Username saved! Your NIP-05 address is ${updated.username}@${hostname}`); setUsernameStatus({ state: "idle" }); // Persist updated username into stored user const stored = localStorage.getItem("bbe_user"); if (stored) { try { const parsed = JSON.parse(stored); localStorage.setItem("bbe_user", JSON.stringify({ ...parsed, username: updated.username })); } catch { // ignore } } } catch (err: any) { setSaveError(err.message || "Failed to save username"); } finally { setSaving(false); } }; const isSaveDisabled = saving || usernameStatus.state === "checking" || usernameStatus.state === "unavailable" || !username.trim(); return (
{user?.picture ? ( {displayName} ) : (
{(displayName)[0]?.toUpperCase() || "?"}
)}

{displayName}

Your Dashboard

{/* Tabs */}
{/* Submissions tab */} {activeTab === "submissions" && ( <>

Submit a Post

{!showForm && ( )}
{formSuccess && (
{formSuccess}
)} {showForm && (

Submit a Nostr longform post for moderator review. Provide the event ID or naddr of the article you'd like published on the blog.

setTitle(e.target.value)} placeholder="My Bitcoin Article" className="w-full bg-surface-container-highest text-on-surface rounded-lg px-4 py-3 placeholder:text-on-surface-variant/40 focus:outline-none focus:ring-1 focus:ring-primary/40" />
setEventId(e.target.value)} placeholder="note1... or hex event id" className="w-full bg-surface-container-highest text-on-surface rounded-lg px-4 py-3 font-mono text-sm placeholder:text-on-surface-variant/40 focus:outline-none focus:ring-1 focus:ring-primary/40" />
setNaddr(e.target.value)} placeholder="naddr1..." className="w-full bg-surface-container-highest text-on-surface rounded-lg px-4 py-3 font-mono text-sm placeholder:text-on-surface-variant/40 focus:outline-none focus:ring-1 focus:ring-primary/40" />
{formError && (

{formError}

)}
)}

My Submissions

{loadingSubs ? (
{[1, 2, 3].map((i) => (
))}
) : submissions.length === 0 ? (

No submissions yet. Submit a Nostr longform post for review.

) : (
{submissions.map((sub) => { const statusCfg = STATUS_CONFIG[sub.status] || STATUS_CONFIG.PENDING; const StatusIcon = statusCfg.icon; return (

{sub.title}

{formatDate(sub.createdAt)} {sub.eventId && ( {sub.eventId.slice(0, 16)}... )} {sub.naddr && ( {sub.naddr.slice(0, 20)}... )}

{statusCfg.label}
{sub.reviewNote && (

{sub.reviewNote}

)}
); })}
)}
)} {/* Profile tab */} {activeTab === "profile" && (

NIP-05 Username

Claim a NIP-05 verified Nostr address hosted on this site. Other Nostr clients will display your identity as{" "} username@{hostname || "…"}.

handleUsernameChange(e.target.value)} placeholder="yourname" maxLength={50} className="w-full bg-surface-container-highest text-on-surface rounded-lg pl-10 pr-10 py-3 font-mono text-sm placeholder:text-on-surface-variant/40 focus:outline-none focus:ring-1 focus:ring-primary/40" />
{usernameStatus.state === "checking" && ( )} {usernameStatus.state === "available" && ( )} {usernameStatus.state === "unavailable" && ( )}
{/* Status message */}
{usernameStatus.state === "checking" && (

Checking availability…

)} {usernameStatus.state === "available" && (

Available

)} {usernameStatus.state === "unavailable" && (

{usernameStatus.reason}

)}
{/* NIP-05 preview */} {username.trim() && (

NIP-05 Address

{username.trim().toLowerCase()}@{hostname || "…"}

)} {saveError && (

{saveError}

)} {saveSuccess && (
{saveSuccess}
)}
)}
); }