From 70e62b738c308c156b08900d645e4c1cc2481604 Mon Sep 17 00:00:00 2001 From: Pragati Sharma <1432pragga@gmail.com> Date: Thu, 28 May 2026 21:57:54 +0530 Subject: [PATCH 1/2] feat: add dashboard theme presets --- src/app/dashboard/settings/page.tsx | 437 +----------------- src/app/globals.css | 136 +++++- src/components/ThemeContext.tsx | 43 +- src/components/ThemePresetPicker.tsx | 87 ++++ src/components/ThemeToggle.tsx | 5 +- src/lib/themes.ts | 81 ++++ .../20260526000000_add_wakatime_stats.sql | 10 +- 7 files changed, 348 insertions(+), 451 deletions(-) create mode 100644 src/components/ThemePresetPicker.tsx create mode 100644 src/lib/themes.ts diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx index 814eff6fe..64f88f2a2 100644 --- a/src/app/dashboard/settings/page.tsx +++ b/src/app/dashboard/settings/page.tsx @@ -1,5 +1,6 @@ "use client"; +import ThemePresetPicker from "@/components/ThemePresetPicker"; import { Suspense, useEffect, useMemo, useState } from "react"; import { useSession } from "next-auth/react"; import { redirect, useSearchParams } from "next/navigation"; @@ -15,11 +16,7 @@ interface UserSettings { github_login: string; is_public: boolean; leaderboard_opt_in: boolean; - weekly_digest_opt_in: boolean; has_wakatime_key?: boolean; - discord_webhook_url?: string; - timezone?: string; - pinned_repos?: string[]; } interface LinkedAccount { @@ -126,19 +123,10 @@ function SettingsPageContent() { ); const [wakatimeKey, setWakatimeKey] = useState(""); const [savingWakatime, setSavingWakatime] = useState(false); - const [discordWebhook, setDiscordWebhook] = useState(""); - const [timezone, setTimezone] = useState(""); - const [savingDiscord, setSavingDiscord] = useState(false); - const [testingDiscord, setTestingDiscord] = useState(false); const [isDirty, setIsDirty] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false); const [pendingPath, setPendingPath] = useState(null); - // Spotlight Repos States - const [userRepos, setUserRepos] = useState([]); - const [loadingRepos, setLoadingRepos] = useState(false); - const [repoSearchQuery, setRepoSearchQuery] = useState(""); - const statusMessage = useMemo( () => getStatusMessage(searchParams.get("success"), searchParams.get("error")), @@ -232,8 +220,6 @@ function SettingsPageContent() { if (res.ok) { const data = await res.json(); setSettings(data); - setDiscordWebhook(data.discord_webhook_url || ""); - setTimezone(data.timezone || "UTC"); } } catch (error) { console.error("Failed to load settings:", error); @@ -245,78 +231,6 @@ function SettingsPageContent() { loadSettings(); }, [session, status]); - // Load active repos for spotlight pinning - useEffect(() => { - if (status !== "authenticated") return; - setLoadingRepos(true); - fetch("/api/metrics/repos?days=90") - .then((r) => r.json()) - .then((d) => { - const names = (d.repos ?? []).map((r: any) => r.name); - setUserRepos(names); - }) - .catch((err) => console.error("Failed to load user repos:", err)) - .finally(() => setLoadingRepos(false)); - }, [status]); - - const handleUpdatePinnedRepos = async (newPins: string[]) => { - if (!settings) return; - setSaving(true); - try { - const res = await fetch("/api/user/settings", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ pinned_repos: newPins }), - }); - if (res.ok) { - const updated = await res.json(); - setSettings(updated); - toast.success("Spotlight repositories updated successfully!"); - } else { - toast.error("Failed to update spotlight repositories."); - } - } catch (err) { - console.error(err); - toast.error("Error updating spotlight repositories."); - } finally { - setSaving(false); - } - }; - - const handlePinRepo = async (repoName: string) => { - if (!settings) return; - const currentPins = settings.pinned_repos || []; - if (currentPins.includes(repoName)) return; - if (currentPins.length >= 3) { - toast.error("Maximum 3 pinned repositories allowed!"); - return; - } - - const updatedPins = [...currentPins, repoName]; - await handleUpdatePinnedRepos(updatedPins); - }; - - const handleUnpinRepo = async (repoName: string) => { - if (!settings) return; - const currentPins = settings.pinned_repos || []; - const updatedPins = currentPins.filter((name) => name !== repoName); - await handleUpdatePinnedRepos(updatedPins); - }; - - const handleMovePin = async (index: number, direction: "up" | "down") => { - if (!settings) return; - const currentPins = [...(settings.pinned_repos || [])]; - const targetIndex = direction === "up" ? index - 1 : index + 1; - if (targetIndex < 0 || targetIndex >= currentPins.length) return; - - // Swap elements - const temp = currentPins[index]; - currentPins[index] = currentPins[targetIndex]; - currentPins[targetIndex] = temp; - - await handleUpdatePinnedRepos(currentPins); - }; - useEffect(() => { if (status !== "authenticated" || !session?.githubLogin) { return; @@ -393,30 +307,6 @@ function SettingsPageContent() { } }; - const handleToggleWeeklyDigest = async (value: boolean) => { - if (!settings) return; - - setSaving(true); - try { - const res = await fetch("/api/user/settings", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ weekly_digest_opt_in: value }), - }); - - if (res.ok) { - const updated = await res.json(); - setSettings(updated); - } else { - console.error("Failed to update weekly digest setting"); - } - } catch (error) { - console.error("Error updating weekly digest setting:", error); - } finally { - setSaving(false); - } - }; - const handleSaveWakatime = async () => { if (!settings) return; setSavingWakatime(true); @@ -444,58 +334,6 @@ function SettingsPageContent() { } }; - const handleSaveDiscord = async () => { - if (!settings) return; - setSavingDiscord(true); - try { - const res = await fetch("/api/user/settings", { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ discord_webhook_url: discordWebhook, timezone }), - }); - if (res.ok) { - const updated = await res.json(); - setSettings(updated); - setIsDirty(false); - toast.success(discordWebhook === "" ? "Discord Webhook removed" : "Discord settings saved successfully!"); - } else { - const errorData = await res.json(); - toast.error(errorData.error || "Failed to update Discord settings"); - } - } catch (error) { - console.error("Error updating Discord settings:", error); - toast.error("Failed to update Discord settings"); - } finally { - setSavingDiscord(false); - } - }; - - const handleTestDiscord = async () => { - if (!discordWebhook) { - toast.error("Please enter a Webhook URL first"); - return; - } - setTestingDiscord(true); - try { - const res = await fetch("/api/user/settings/discord-test", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ webhookUrl: discordWebhook }), - }); - if (res.ok) { - toast.success("Test notification sent! Check your Discord server."); - } else { - const errorData = await res.json(); - toast.error(errorData.error || "Failed to send test notification"); - } - } catch (error) { - console.error("Error sending test notification:", error); - toast.error("Failed to send test notification"); - } finally { - setTestingDiscord(false); - } - }; - const copyShareLink = () => { if (!settings) return; const link = `${window.location.origin}/u/${settings.github_login}`; @@ -594,11 +432,10 @@ function SettingsPageContent() { {statusMessage && (
{statusMessage.message}
@@ -732,6 +569,20 @@ function SettingsPageContent() { )} +
+
+

+ Dashboard Theme +

+ +

+ Personalize your dashboard appearance with curated developer themes. +

+
+ + +
+
@@ -775,174 +626,6 @@ function SettingsPageContent() {
- {/* Repository Spotlight Section */} -
-

- Repository Spotlight 🚀 -

-

- Pin up to 3 repositories to showcase on your dashboard and public profile. -

- - {/* Currently Pinned */} -
-

- Pinned Repositories ({(settings.pinned_repos || []).length}/3) -

- {(settings.pinned_repos || []).length === 0 ? ( -
- No repositories pinned yet. Use the search below to spotlight your best projects! -
- ) : ( -
- {(settings.pinned_repos || []).map((repoName, index) => ( -
-
- - {repoName} - -
- -
- {/* Reorder Buttons */} - - - - {/* Unpin Button */} - -
-
- ))} -
- )} -
- - {/* Pin New Repos (Search) */} - {(settings.pinned_repos || []).length < 3 && ( -
-

- Search & Pin Repositories -

- setRepoSearchQuery(e.target.value)} - placeholder="Type to search your repositories..." - aria-label="Search repositories to pin" - className="w-full rounded-lg border border-[var(--border)] bg-[var(--control)] px-4 py-2 text-sm text-[var(--card-foreground)] placeholder:text-[var(--muted-foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--accent)] mb-4" - /> - - {loadingRepos ? ( -
- Loading your repositories... -
- ) : ( -
- {userRepos - .filter( - (repoName) => - !(settings.pinned_repos || []).includes(repoName) && - repoName.toLowerCase().includes(repoSearchQuery.toLowerCase()) - ) - .slice(0, 5) - .map((repoName) => ( -
- - {repoName} - - -
- ))} - {userRepos.filter( - (repoName) => - !(settings.pinned_repos || []).includes(repoName) && - repoName.toLowerCase().includes(repoSearchQuery.toLowerCase()) - ).length === 0 && ( -
- No repositories available to pin. -
- )} -
- )} -
- )} -
- -
-
-
-

- Weekly Email Digest -

-

- Receive an optional weekly email digest every Monday morning summarizing your coding habits. -

-
- -