Skip to content
Open
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
133 changes: 5 additions & 128 deletions src/app/dashboard/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -684,135 +684,13 @@ 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, discord_muted_until: discordMutedUntil }),
});
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 handleMuteDiscord = async () => {
if (!settings) return;
const dayLabel = muteDuration === 1 ? "day" : "days";
const mutedUntil = new Date();
mutedUntil.setDate(mutedUntil.getDate() + muteDuration);
setSavingDiscord(true);
try {
const res = await fetch("/api/user/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ discord_muted_until: mutedUntil.toISOString() }),
});
if (res.ok) {
const updated = await res.json();
setSettings(updated);
setDiscordMutedUntil(updated.discord_muted_until);
setIsDirty(false);
toast.success("Discord notifications muted for " + muteDuration + " " + dayLabel);
} else {
const err = await res.json();
toast.error(err.error || "Failed to mute notifications");
}
} catch (_err) {
console.error("mute error", _err);
toast.error("Failed to mute notifications");
} finally {
setSavingDiscord(false);
}
};

const handleUnmuteDiscord = 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_muted_until: null }),
});
const updated = await res.json();
if (!res.ok) {
toast.error(updated.error || "Failed to unmute notifications");
return;
}
setSettings(updated);
setDiscordMutedUntil(null);
setIsDirty(false);
toast.success("Discord notifications unmuted");
} catch (_err) {
console.error("unmute error", _err);
toast.error("Failed to unmute notifications");
} finally {
setSavingDiscord(false);
}
};

const copyShareLink = () => {
if (!profileUrl) return;

if (copyResetTimerRef.current) {
window.clearTimeout(copyResetTimerRef.current);
copyResetTimerRef.current = null;
}

if (!navigator.clipboard?.writeText) {
toast.error("Clipboard access is not available in this browser.");
return;
}

navigator.clipboard.writeText(profileUrl).then(() => {
const link = `${window.location.origin}/u/${settings.github_login}`;
navigator.clipboard.writeText(link).then(() => {
setCopied(true);
toast.success("Link copied successfully!");
copyResetTimerRef.current = window.setTimeout(() => {
setCopied(false);
copyResetTimerRef.current = null;
}, 2000);
}).catch((err) => {
console.error("Clipboard copy failed:", err);
toast.error("Failed to copy profile URL");
setTimeout(() => setCopied(false), 2000);
}).catch(() => {
toast.error("Failed to copy link");
});
};

Expand Down Expand Up @@ -909,7 +787,6 @@ function SettingsPageContent() {
{statusMessage.message}
</div>
)}

{/* Public Profile Section */}
<div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm">
<div className="flex items-start justify-between mb-2 gap-4">
Expand Down
3 changes: 2 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@

import ParticleBackground from "@/components/ParticleBackground";
import { Syne, DM_Sans, JetBrains_Mono } from 'next/font/google';

Check failure on line 2 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Type check

Duplicate identifier 'JetBrains_Mono'.

Check failure on line 2 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Type check

Duplicate identifier 'DM_Sans'.

Check failure on line 2 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Type check

Duplicate identifier 'Syne'.
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { redirect } from "next/navigation";
import LandingPage, { type RepoStats } from "@/components/landing/LandingPage";
import { supabaseAdmin } from "@/lib/supabase";

import { Syne, DM_Sans, JetBrains_Mono } from "next/font/google";

Check failure on line 9 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Type check

Duplicate identifier 'JetBrains_Mono'.

Check failure on line 9 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Type check

Duplicate identifier 'DM_Sans'.

Check failure on line 9 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / Type check

Duplicate identifier 'Syne'.

const syne = Syne({
subsets: ["latin"],
Expand Down
80 changes: 29 additions & 51 deletions src/components/StreakTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -620,61 +620,39 @@
</span>
)}
</div>
{data && <div className="h-8 w-24" />}
</div>
<div className="grid grid-cols-2 gap-2 xs:gap-3">
{stats.map((stat) => (
<div
key={stat.label}
className={`rounded-lg p-3 text-center ${stat.highlight
? "border border-[var(--accent)]/40 bg-[var(--accent-soft)]"
: "bg-[var(--control)]"
}`}
aria-label={stat.tooltip}
>
<div className="flex justify-center mb-1">
<stat.icon size={20} className="text-[var(--accent)]" aria-hidden="true" />
</div>
<div
className={`text-lg font-bold leading-tight tabular-nums ${stat.highlight ? "text-[var(--accent)]" : "text-[var(--accent)]"
}`}
>
{stat.value}
{stat.unit && (
<span className="ml-1 text-sm font-normal text-[var(--muted-foreground)]">
{stat.unit}
</span>
)}
</div>
<div className="mt-1 flex items-center justify-center gap-1 text-xs text-[var(--muted-foreground)]">
<span>{stat.label}</span>
<div className="mt-1 flex items-center justify-center gap-1 text-xs text-[var(--muted-foreground)]">
<span>{stat.label}</span>

Check failure on line 624 in src/components/StreakTracker.tsx

View workflow job for this annotation

GitHub Actions / Type check

Cannot find name 'stat'. Did you mean 'stats'?

<button
type="button"
aria-label={`More info about ${stat.label}`}
title={stat.tooltip}
className="text-[var(--muted-foreground)] hover:text-[var(--accent)]"
<div className="group relative cursor-help">
<span
role="img"
aria-label={stat.tooltip}

Check failure on line 629 in src/components/StreakTracker.tsx

View workflow job for this annotation

GitHub Actions / Type check

Cannot find name 'stat'. Did you mean 'stats'?
tabIndex={0}
className="flex h-4 w-4 items-center justify-center rounded-full text-[var(--muted-foreground)] hover:text-[var(--accent)] focus:text-[var(--accent)] focus:outline-none transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
</button>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="16" x2="12" y2="12" />
<line x1="12" y1="8" x2="12.01" y2="8" />
</svg>
</span>
<div className="absolute bottom-full left-1/2 mb-2 -translate-x-1/2 w-48 rounded-lg bg-[var(--foreground)] px-3 py-2 text-xs font-medium leading-relaxed text-[var(--background)] opacity-0 pointer-events-none group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity z-20 shadow-lg text-center">
{stat.tooltip}

Check failure on line 651 in src/components/StreakTracker.tsx

View workflow job for this annotation

GitHub Actions / Type check

Cannot find name 'stat'. Did you mean 'stats'?
<div className="absolute top-full left-1/2 h-1 w-1 -translate-x-1/2 border-4 border-t-[var(--foreground)] border-transparent" />
</div>
</div>
))}
</div>
</div>
{monthlyTrend.isValid && (
<div className="mt-3 flex items-center justify-between rounded-lg border border-[var(--border)] bg-[var(--card)] px-4 py-2.5 text-xs shadow-sm">
Expand Down
Loading