diff --git a/frontend/app/(main)/admin/BestPracticesAnnouncementPanel.tsx b/frontend/app/(main)/admin/BestPracticesAnnouncementPanel.tsx new file mode 100644 index 00000000..0ed66dd9 --- /dev/null +++ b/frontend/app/(main)/admin/BestPracticesAnnouncementPanel.tsx @@ -0,0 +1,226 @@ +"use client"; + +import React, { useState } from "react"; +import { + Activity, + CheckCircle2, + Trophy, + Heart, + Sparkles, + Star, + ExternalLink, +} from "lucide-react"; +import { sendBestPracticesAnnouncement } from "@/lib/api"; +import { toast } from "@/lib/toast"; +import { cn } from "@/lib/utils"; + +export default function BestPracticesAnnouncementPanel() { + const [subject, setSubject] = useState(""); + const [ctaUrl, setCtaUrl] = useState(""); + const [testEmail, setTestEmail] = useState(""); + const [sendToAll, setSendToAll] = useState(false); + const [sending, setSending] = useState(false); + + const effectiveSubject = + subject.trim() || "Koder — Discover Best Practices: See how top developers solve problems"; + + const canSend = sendToAll || testEmail.trim().length > 0; + + const handleSend = async () => { + if (!sendToAll && !testEmail.trim()) { + toast.error("Enter a test email or enable Send to all."); + return; + } + + setSending(true); + + const frontendBase = window.location.origin; + let finalCta = ctaUrl.trim(); + if (!finalCta) { + finalCta = `${frontendBase}/home?bp_tab=all`; + } else if (finalCta.startsWith("/")) { + finalCta = `${frontendBase}${finalCta}`; + } + + const payload = { + subject: effectiveSubject, + cta_url: finalCta, + test_email: testEmail.trim() || undefined, + send_to_all: sendToAll, + }; + + const res = await sendBestPracticesAnnouncement(payload); + setSending(false); + + if (!res.success) { + toast.error(res.error?.message || "Failed to send announcement."); + return; + } + + const attempted = res.data?.attempted ?? 0; + const sent = res.data?.sent ?? 0; + const failed = res.data?.failed ?? 0; + + if (sendToAll) { + toast.success( + `Best Practices announcement queued to ${attempted} users; ${sent} sent, ${failed} failed.`, + ); + } else { + toast.success(`Test email sent to ${testEmail.trim()}.`); + } + }; + + return ( +
+
+
+ +
+
+

+ Best Practices Announcement +

+

+ Send an introductory email about the Best Practices feature with live stats and solution previews. +

+
+
+ +
+ + + + +
+ + +
+ +
+
+ {sendToAll + ? "This email will be delivered to all registered users with an email address." + : "Enter a test email to send a single preview email."} +
+ +
+ + {/* Email preview */} +
+
+
+

+ Email preview +

+

+ {effectiveSubject} +

+
+
+ + Feature announcement +
+
+ +
+ {/* Stats preview */} +
+
+
+
--
+
Solutions
+
+
+
--
+
Developers
+
+
+
--
+
Likes
+
+
+
--
+
Go / Python
+
+
+
+ + {/* Feature cards preview */} +
+ {[ + { icon: Heart, label: "Community Solutions", color: "bg-amber-100" }, + { icon: Sparkles, label: "AI-Powered Code Analysis", color: "bg-purple-100" }, + { icon: Star, label: "How to Get Featured", color: "bg-yellow-100" }, + ].map(({ icon: Icon, label, color }) => ( +
+
+ +
+ {label} +
+ ))} +
+ +
+ + + {ctaUrl.trim() || "https://.../home?bp_tab=all"} + +
+
+
+
+
+ ); +} diff --git a/frontend/app/(main)/admin/page.tsx b/frontend/app/(main)/admin/page.tsx index 9965e6e6..5c63b3d9 100644 --- a/frontend/app/(main)/admin/page.tsx +++ b/frontend/app/(main)/admin/page.tsx @@ -71,6 +71,7 @@ import PendingContributions from "./PendingContributions"; import FeedbackPanel from "./FeedbackPanel"; import BroadcastPanel from "./BroadcastPanel"; import EmailBroadcastPanel from "./EmailBroadcastPanel"; +import BestPracticesAnnouncementPanel from "./BestPracticesAnnouncementPanel"; import ProblemEditPanel from "./ProblemEditPanel"; import ProblemReports from "./ProblemReports"; import UserVerificationPanel from "./UserVerificationPanel"; @@ -1293,6 +1294,13 @@ export default function AdminDashboard() { + {/* Best Practices Announcement */} +
+
+ +
+
+ {/* Broadcasts */}
diff --git a/frontend/app/(main)/home/page.tsx b/frontend/app/(main)/home/page.tsx index 54da4ac2..813fbfbe 100644 --- a/frontend/app/(main)/home/page.tsx +++ b/frontend/app/(main)/home/page.tsx @@ -14,7 +14,6 @@ import { Trophy, ArrowLeft, BookOpen, - FlaskConical, } from "lucide-react"; import { LanguageLogo } from "@/components/LanguageLogo"; import GoogleLinkBanner from "@/components/GoogleLinkBanner"; @@ -589,17 +588,6 @@ export default function Dashboard() {
)} - ) : user?.role !== "admin" ? ( -
- - -

Best Practices — Coming Soon

-

- This feature is in private beta and currently available to administrators only. - Stay tuned for the public release. -

-
-
) : ( { + const timer = setTimeout(() => { + textareaRef.current?.focus({ preventScroll: true }); + }, 320); + return () => clearTimeout(timer); + }, []); + const submit = () => { if (!question.trim() || loading) return; onSend(); @@ -39,7 +50,6 @@ export function ChatComposer({