From e9a7f301b8150aee02712b2b5af800b066bac678 Mon Sep 17 00:00:00 2001 From: Melvin Jones Repol Date: Thu, 26 Mar 2026 15:56:56 +0800 Subject: [PATCH 1/2] feat: disabled sentry intrumentation on non production environments --- instrumentation-client.ts | 13 +++++++------ sentry.edge.config.ts | 13 +++++++------ sentry.server.config.ts | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/instrumentation-client.ts b/instrumentation-client.ts index cded398..d1fb55c 100644 --- a/instrumentation-client.ts +++ b/instrumentation-client.ts @@ -4,12 +4,13 @@ import * as Sentry from "@sentry/nextjs"; -Sentry.init({ - dsn: process.env.SENTRY_DNS, +if (process.env.NODE_ENV === "production") + Sentry.init({ + dsn: process.env.SENTRY_DNS, - // Enable sending user PII (Personally Identifiable Information) - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, -}); + // Enable sending user PII (Personally Identifiable Information) + // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii + sendDefaultPii: true, + }); export const onRouterTransitionStart = Sentry.captureRouterTransitionStart; diff --git a/sentry.edge.config.ts b/sentry.edge.config.ts index 77d012a..af9b5ba 100644 --- a/sentry.edge.config.ts +++ b/sentry.edge.config.ts @@ -5,10 +5,11 @@ import * as Sentry from "@sentry/nextjs"; -Sentry.init({ - dsn: process.env.SENTRY_DNS, +if (process.env.NODE_ENV === "production") + Sentry.init({ + dsn: process.env.SENTRY_DNS, - // Enable sending user PII (Personally Identifiable Information) - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, -}); + // Enable sending user PII (Personally Identifiable Information) + // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii + sendDefaultPii: true, + }); diff --git a/sentry.server.config.ts b/sentry.server.config.ts index 4341de8..8fac354 100644 --- a/sentry.server.config.ts +++ b/sentry.server.config.ts @@ -4,10 +4,11 @@ import * as Sentry from "@sentry/nextjs"; -Sentry.init({ - dsn: process.env.SENTRY_DNS, +if (process.env.NODE_ENV === "production") + Sentry.init({ + dsn: process.env.SENTRY_DNS, - // Enable sending user PII (Personally Identifiable Information) - // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii - sendDefaultPii: true, -}); + // Enable sending user PII (Personally Identifiable Information) + // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii + sendDefaultPii: true, + }); From 08f9e69f75cfe6c122824900fa27f250780325e9 Mon Sep 17 00:00:00 2001 From: Melvin Jones Repol Date: Thu, 26 Mar 2026 16:21:26 +0800 Subject: [PATCH 2/2] feat: Use getUserWithProfile and add avatars - Replace supabase.auth.getUser() with getUserWithProfile() and fetch user/profile concurrently via Promise.all in leaderboard pages - Pass preferred avatar URL through Dashboard -> Stats and Profile components and render with next/image - Show avatar in header/profile dropdown with initial fallback - Add explanatory text to Reset Password and minor UI/markup tweaks (sync icon SVG, dropdown structure, import formatting) --- app/(public)/leaderboard/[slug]/page.tsx | 16 +- app/(public)/leaderboard/page.tsx | 8 +- app/(user)/dashboard/layout.tsx | 6 +- app/(user)/dashboard/page.tsx | 7 +- app/components/dashboard/Settings/Profile.tsx | 14 + .../dashboard/Settings/ResetPassword.tsx | 4 + app/components/dashboard/Stats.tsx | 239 +++++++++++------- 7 files changed, 193 insertions(+), 101 deletions(-) diff --git a/app/(public)/leaderboard/[slug]/page.tsx b/app/(public)/leaderboard/[slug]/page.tsx index ebfdfc4..da7fc21 100644 --- a/app/(public)/leaderboard/[slug]/page.tsx +++ b/app/(public)/leaderboard/[slug]/page.tsx @@ -1,14 +1,20 @@ import { createClient } from "../../../lib/supabase/server"; -import LeaderboardTable, { NonNullableMember } from "../../../components/LeaderboardTable"; +import LeaderboardTable, { + NonNullableMember, +} from "../../../components/LeaderboardTable"; import LeaderboardHeader from "@/app/components/leaderboard/Header"; import Footer from "@/app/components/layout/Footer"; import CTA from "@/app/components/layout/CTA"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; export default async function LeaderboardPage(props: { params: Promise<{ slug: string }>; }) { - const { slug } = await props.params; - const supabase = await createClient(); + const [{ user }, { slug }, supabase] = await Promise.all([ + getUserWithProfile(), + props.params, + createClient(), + ]); const { data: leaderboard } = await supabase .from("leaderboards") @@ -32,10 +38,6 @@ export default async function LeaderboardPage(props: { .select("*") .eq("leaderboard_id", leaderboard.id); - const { - data: { user }, - } = await supabase.auth.getUser(); - const isOwner = user?.id === leaderboard.owner_id; if (error) { diff --git a/app/(public)/leaderboard/page.tsx b/app/(public)/leaderboard/page.tsx index 1091881..17d4194 100644 --- a/app/(public)/leaderboard/page.tsx +++ b/app/(public)/leaderboard/page.tsx @@ -4,6 +4,7 @@ import Footer from "@/app/components/layout/Footer"; import CTA from "@/app/components/layout/CTA"; import Image from "next/image"; import { Metadata } from "next"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; export const metadata: Metadata = { title: "Leaderboards - DevPulse", @@ -54,17 +55,14 @@ export const metadata: Metadata = { export default async function Leaderboards() { const supabase = await createClient(); - const [leaderboardsResult, userResult] = await Promise.all([ + const [{ data, error }, { user }] = await Promise.all([ supabase .from("leaderboards") .select("id, name, slug") .order("created_at", { ascending: false }), - supabase.auth.getUser(), + getUserWithProfile(), ]); - const { data, error } = leaderboardsResult; - const { data: user } = userResult; - if (error) { return (
diff --git a/app/(user)/dashboard/layout.tsx b/app/(user)/dashboard/layout.tsx index eaf15ab..74e78ca 100644 --- a/app/(user)/dashboard/layout.tsx +++ b/app/(user)/dashboard/layout.tsx @@ -14,7 +14,11 @@ export default async function Layout({ const name = user?.user_metadata?.name || email.split("@")[0]; return ( - + {children} ); diff --git a/app/(user)/dashboard/page.tsx b/app/(user)/dashboard/page.tsx index 464712c..a2aece8 100644 --- a/app/(user)/dashboard/page.tsx +++ b/app/(user)/dashboard/page.tsx @@ -18,6 +18,11 @@ export default async function Dashboard() { const email = profile?.email || user.email!; const name = user?.user_metadata?.name || email.split("@")[0]; + const prefferedAvatar = + user?.user_metadata?.avatar_url || + user?.user_metadata?.picture || + user?.user_metadata?.avatar || + null; - return ; + return ; } diff --git a/app/components/dashboard/Settings/Profile.tsx b/app/components/dashboard/Settings/Profile.tsx index ad45c29..464146a 100644 --- a/app/components/dashboard/Settings/Profile.tsx +++ b/app/components/dashboard/Settings/Profile.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { createClient } from "../../../lib/supabase/client"; import { toast } from "react-toastify"; import type { User } from "@supabase/supabase-js"; +import Image from "next/image"; export default function UserProfile({ user }: { user: User }) { const supabase = createClient(); @@ -12,6 +13,11 @@ export default function UserProfile({ user }: { user: User }) { ); const [name, setName] = useState(originalName); const [loading, setLoading] = useState(false); + const prefferedAvatar = + user?.user_metadata?.avatar_url || + user?.user_metadata?.picture || + user?.user_metadata?.avatar || + null; const isEdited = name !== originalName; @@ -56,6 +62,14 @@ export default function UserProfile({ user }: { user: User }) { Profile + User Avatar + +

+ You will receive an email with instructions to reset your password. +

+ diff --git a/app/components/dashboard/Stats.tsx b/app/components/dashboard/Stats.tsx index 6f901ce..a93e3c9 100644 --- a/app/components/dashboard/Stats.tsx +++ b/app/components/dashboard/Stats.tsx @@ -17,6 +17,7 @@ import Machines from "./widgets/Machines"; import Categories from "./widgets/Categories"; import Dependencies from "./widgets/Dependencies"; +import Image from "next/image"; export interface StatsData { total_seconds: number; @@ -36,10 +37,14 @@ export interface StatsData { interface StatsProps { name?: string; email?: string; + avatar: string | null; } -export default function Stats({ name = "User", email = "user@example.com" }: StatsProps) { - +export default function Stats({ + name = "User", + email = "user@example.com", + avatar = null, +}: StatsProps) { const [syncing, setSyncing] = useState(false); const [animated, setAnimated] = useState(false); const [profileOpen, setProfileOpen] = useState(false); @@ -48,7 +53,10 @@ export default function Stats({ name = "User", email = "user@example.com" }: Sta // Close profile dropdown when clicking outside useEffect(() => { function handleClickOutside(event: MouseEvent) { - if (profileRef.current && !profileRef.current.contains(event.target as Node)) { + if ( + profileRef.current && + !profileRef.current.contains(event.target as Node) + ) { setProfileOpen(false); } } @@ -117,25 +125,28 @@ export default function Stats({ name = "User", email = "user@example.com" }: Sta const topEditor = stats.editors[0]?.name || "N/A"; // Use actual daily_stats if available, otherwise fallback to empty/flat - const dailyData = stats.daily_stats && stats.daily_stats.length > 0 - ? stats.daily_stats.map(d => { - // Parse date to short day name (e.g., "Mon") - const dateObj = new Date(d.date); - const dayStr = dateObj.toLocaleDateString("en-US", { weekday: "short" }); - return { - day: dayStr, - hours: parseFloat((d.total_seconds / 3600).toFixed(1)), - }; - }) - : [ - { day: "Mon", hours: 0 }, - { day: "Tue", hours: 0 }, - { day: "Wed", hours: 0 }, - { day: "Thu", hours: 0 }, - { day: "Fri", hours: 0 }, - { day: "Sat", hours: 0 }, - { day: "Sun", hours: 0 } - ]; + const dailyData = + stats.daily_stats && stats.daily_stats.length > 0 + ? stats.daily_stats.map((d) => { + // Parse date to short day name (e.g., "Mon") + const dateObj = new Date(d.date); + const dayStr = dateObj.toLocaleDateString("en-US", { + weekday: "short", + }); + return { + day: dayStr, + hours: parseFloat((d.total_seconds / 3600).toFixed(1)), + }; + }) + : [ + { day: "Mon", hours: 0 }, + { day: "Tue", hours: 0 }, + { day: "Wed", hours: 0 }, + { day: "Thu", hours: 0 }, + { day: "Fri", hours: 0 }, + { day: "Sat", hours: 0 }, + { day: "Sun", hours: 0 }, + ]; // Pie data const pieData = stats.languages.slice(0, 6).map((l) => ({ @@ -193,8 +204,13 @@ export default function Stats({ name = "User", email = "user@example.com" }: Sta }, { label: "Best Day", - value: stats.best_day?.date && stats.best_day.total_seconds ? formatHours(stats.best_day.total_seconds) : "N/A", - sub: stats.best_day?.date ? new Date(stats.best_day.date).toLocaleDateString() : "", + value: + stats.best_day?.date && stats.best_day.total_seconds + ? formatHours(stats.best_day.total_seconds) + : "N/A", + sub: stats.best_day?.date + ? new Date(stats.best_day.date).toLocaleDateString() + : "", color: "#f59e0b", trend: "Top", trendUp: true, @@ -224,78 +240,127 @@ export default function Stats({ name = "User", email = "user@example.com" }: Sta
- {/* Sync Button as an icon button */} - + + + - {/* Profile Section */} -
setProfileOpen(!profileOpen)} - > + {/* Profile Section */} +
setProfileOpen(!profileOpen)} + > + {avatar ? ( + Profile Avatar + ) : (
{email.charAt(0).toUpperCase()}
- {name} - + )} + + {name} + + - {/* Dropdown Menu */} - {profileOpen && ( -
e.stopPropagation()} + {/* Dropdown Menu */} + {profileOpen && ( +
e.stopPropagation()} + > +
+

+ {name} +

+

{email}

+
+ setProfileOpen(false)} > -
-

{name}

-

{email}

-
- setProfileOpen(false)} + - - - - - Settings - - setProfileOpen(false)} + + + + Settings + + setProfileOpen(false)} + > + - - - - Logout - -
- )} -
+ + + Logout + +
+ )} +