Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 24 additions & 165 deletions src/app/dashboard/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -21,11 +22,7 @@ interface UserSettings {
bio: 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 {
Expand Down Expand Up @@ -139,19 +136,10 @@ function SettingsPageContent() {
const [showBioPreview, setShowBioPreview] = useState(false);
const [savingBio, setSavingBio] = useState(false);
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<string | null>(null);

// Spotlight Repos States
const [userRepos, setUserRepos] = useState<string[]>([]);
const [loadingRepos, setLoadingRepos] = useState(false);
const [repoSearchQuery, setRepoSearchQuery] = useState("");

const statusMessage = useMemo(
() =>
getStatusMessage(searchParams.get("success"), searchParams.get("error")),
Expand Down Expand Up @@ -243,6 +231,10 @@ function SettingsPageContent() {
try {
const res = await fetch("/api/user/settings");
if (res.ok) {
const data = await res.json();
setSettings(data);
setBioDraft(data.bio ?? "");
}
const data = await res.json();
setSettings(data);
setBioDraft(data.bio ?? "");
Expand All @@ -260,78 +252,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;
Expand Down Expand Up @@ -409,30 +329,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);
Expand Down Expand Up @@ -460,6 +356,7 @@ function SettingsPageContent() {
}
};


const handleSaveBio = async () => {
if (!settings || bioDraft.length > 500) return;

Expand Down Expand Up @@ -488,59 +385,8 @@ function SettingsPageContent() {
setSavingBio(false);
}
};

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}`;
Expand Down Expand Up @@ -639,11 +485,10 @@ function SettingsPageContent() {

{statusMessage && (
<div
className={`mb-6 rounded-xl border p-4 text-sm ${
statusMessage.kind === "success"
className={`mb-6 rounded-xl border p-4 text-sm ${statusMessage.kind === "success"
? "border-[var(--success)]/30 bg-[var(--success)]/10 text-[var(--success)]"
: "border-[var(--error)]/30 bg-[var(--error)]/10 text-[var(--error)]"
}`}
}`}
>
{statusMessage.message}
</div>
Expand Down Expand Up @@ -902,6 +747,20 @@ function SettingsPageContent() {
)}
</div>

<div className="mt-6 rounded-2xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-[varcar(--shadow-soft)]">
<div className="mb-5">
<h3 className="text-lg font-semibold text-[var(--card-foreground)]">
Dashboard Theme
</h3>

<p className="mt-1 text-sm text-[var(--muted-foreground)]">
Personalize your dashboard appearance with curated developer themes.
</p>
</div>

<ThemePresetPicker />
</div>

<div className="mt-6 rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm">
<div className="flex items-start justify-between gap-4">
<div>
Expand Down
Loading
Loading