From 0391f102f448b1ba946e7ffdd990efb3027a5445 Mon Sep 17 00:00:00 2001 From: Melvin Jones Repol Date: Thu, 26 Mar 2026 08:42:50 +0800 Subject: [PATCH] refractor: auth/profile fetching into helper - Add getUserWithProfile helper to centralize auth/profile fetching - Replace inline createClient/auth/profile calls across dashboard pages - Add Metadata titles and unify login redirects with ?from= path - Return redirect() for unauthenticated pages instead of null - Parallelize user_stats and user_projects upserts with Promise.all - UI/layout tweaks: show "No users found" and adjust dashboard spacing - Fallback DashboardLayout role to "user" when profile is absent --- app/(user)/dashboard/admin/page.tsx | 21 +++--- app/(user)/dashboard/chat/page.tsx | 16 +++-- app/(user)/dashboard/flex/page.tsx | 16 +++-- app/(user)/dashboard/layout.tsx | 21 +----- app/(user)/dashboard/leaderboards/page.tsx | 8 ++- app/(user)/dashboard/page.tsx | 16 +---- app/(user)/dashboard/settings/page.tsx | 10 ++- app/(user)/update-password/page.tsx | 1 - app/api/wakatime/sync/route.ts | 75 ++++++++++------------ app/components/Chat.tsx | 7 ++ app/components/dashboard/WithoutKey.tsx | 4 +- app/lib/supabase/help/user.ts | 20 ++++++ 12 files changed, 105 insertions(+), 110 deletions(-) create mode 100644 app/lib/supabase/help/user.ts diff --git a/app/(user)/dashboard/admin/page.tsx b/app/(user)/dashboard/admin/page.tsx index b6d0f67..5b2ea59 100644 --- a/app/(user)/dashboard/admin/page.tsx +++ b/app/(user)/dashboard/admin/page.tsx @@ -1,24 +1,19 @@ import Dashboard from "@/app/components/admin/Dashbord"; -import { createClient } from "@/app/lib/supabase/server"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; +import { Metadata } from "next"; import { redirect } from "next/navigation"; -export default async function AdminPage() { - const supabase = await createClient(); +export const metadata: Metadata = { + title: "Admin Panel - DevPulse", +}; - const { - data: { user }, - } = await supabase.auth.getUser(); +export default async function AdminPage() { + const { user, profile } = await getUserWithProfile(); if (!user) { - redirect("/login"); + redirect("/login?from=/dashboard/admin"); } - const { data: profile } = await supabase - .from("profiles") - .select("role") - .eq("id", user.id) - .single(); - if (!profile || profile.role !== "admin") { redirect("/dashbord"); } diff --git a/app/(user)/dashboard/chat/page.tsx b/app/(user)/dashboard/chat/page.tsx index 0643530..73e0bdc 100644 --- a/app/(user)/dashboard/chat/page.tsx +++ b/app/(user)/dashboard/chat/page.tsx @@ -1,14 +1,16 @@ import Chat from "@/app/components/Chat"; -import { createClient } from "@/app/lib/supabase/server"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; +import { Metadata } from "next"; +import { redirect } from "next/navigation"; -export default async function ChatPage() { - const supabase = await createClient(); +export const metadata: Metadata = { + title: "Chat - DevPulse", +}; - const { - data: { user }, - } = await supabase.auth.getUser(); +export default async function ChatPage() { + const { user } = await getUserWithProfile(); - if (!user) return null; + if (!user) return redirect("/login?from=/dashboard/chat"); return ; } diff --git a/app/(user)/dashboard/flex/page.tsx b/app/(user)/dashboard/flex/page.tsx index 21fcc6b..5a4b1c0 100644 --- a/app/(user)/dashboard/flex/page.tsx +++ b/app/(user)/dashboard/flex/page.tsx @@ -1,14 +1,16 @@ import Flex from "@/app/components/Flex"; -import { createClient } from "@/app/lib/supabase/server"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; +import { Metadata } from "next"; +import { redirect } from "next/navigation"; -export default async function FlexPage() { - const supabase = await createClient(); +export const metadata: Metadata = { + title: "Flexes - DevPulse", +}; - const { - data: { user }, - } = await supabase.auth.getUser(); +export default async function FlexPage() { + const { user } = await getUserWithProfile(); - if (!user) return null; + if (!user) return redirect("/login?from=/dashboard/flex"); return ; } diff --git a/app/(user)/dashboard/layout.tsx b/app/(user)/dashboard/layout.tsx index 8cfd42a..eaf15ab 100644 --- a/app/(user)/dashboard/layout.tsx +++ b/app/(user)/dashboard/layout.tsx @@ -1,35 +1,20 @@ import { redirect } from "next/navigation"; -import { createClient } from "../../lib/supabase/server"; import DashboardLayout from "@/app/components/dashboard/Navbar"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; export default async function Layout({ children, }: { children: React.ReactNode; }) { - const supabase = await createClient(); - - const { - data: { user }, - } = await supabase.auth.getUser(); - + const { user, profile } = await getUserWithProfile(); if (!user) redirect("/login"); - const { data: profile } = await supabase - .from("profiles") - .select("wakatime_api_key, email, role") - .eq("id", user.id) - .single(); - - if (!profile?.wakatime_api_key) { - return <>{children}; - } - const email = profile?.email || user.email!; const name = user?.user_metadata?.name || email.split("@")[0]; return ( - + {children} ); diff --git a/app/(user)/dashboard/leaderboards/page.tsx b/app/(user)/dashboard/leaderboards/page.tsx index 891096e..ab85c39 100644 --- a/app/(user)/dashboard/leaderboards/page.tsx +++ b/app/(user)/dashboard/leaderboards/page.tsx @@ -1,13 +1,17 @@ import DashboardWithKey from "../../../components/dashboard/WithKey"; import LeaderboardsList from "@/app/components/dashboard/LeaderbordList"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; import { Metadata } from "next"; +import { redirect } from "next/navigation"; export const metadata: Metadata = { title: "Leaderboards - DevPulse", - description: "Create, join, and manage your coding leaderboards.", }; -export default function LeaderboardsPage() { +export default async function LeaderboardsPage() { + const { user } = await getUserWithProfile(); + if (!user) return redirect("/login?from=/dashboard/settings"); + return (
diff --git a/app/(user)/dashboard/page.tsx b/app/(user)/dashboard/page.tsx index e0a5a31..464712c 100644 --- a/app/(user)/dashboard/page.tsx +++ b/app/(user)/dashboard/page.tsx @@ -1,29 +1,17 @@ import { redirect } from "next/navigation"; -import { createClient } from "../../lib/supabase/server"; import DashboardWithoutKey from "../../components/dashboard/WithoutKey"; import Stats from "@/app/components/dashboard/Stats"; import { Metadata } from "next"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; export const metadata: Metadata = { title: "Dashboard - DevPulse", - description: "Monitor your coding activity and manage your leaderboards.", }; export default async function Dashboard() { - const supabase = await createClient(); - - const { - data: { user }, - } = await supabase.auth.getUser(); - + const { user, profile } = await getUserWithProfile(); if (!user) redirect("/login"); - const { data: profile } = await supabase - .from("profiles") - .select("wakatime_api_key, email") - .eq("id", user.id) - .single(); - if (!profile?.wakatime_api_key) { return ; } diff --git a/app/(user)/dashboard/settings/page.tsx b/app/(user)/dashboard/settings/page.tsx index a640b6c..e8c6262 100644 --- a/app/(user)/dashboard/settings/page.tsx +++ b/app/(user)/dashboard/settings/page.tsx @@ -1,18 +1,16 @@ import { Metadata } from "next"; -import { createClient } from "../../../lib/supabase/server"; import UserProfile from "@/app/components/dashboard/Settings/Profile"; import ResetPassword from "@/app/components/dashboard/Settings/ResetPassword"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; +import { redirect } from "next/navigation"; export const metadata: Metadata = { title: "Settings - DevPulse", - description: - "Manage your account settings and including your WakaTime API key.", }; export default async function LeaderboardsPage() { - const supabase = await createClient(); - const { data: userData } = await supabase.auth.getUser(); - const user = userData.user; + const { user } = await getUserWithProfile(); + if (!user) return redirect("/login?from=/dashboard/settings"); return (
diff --git a/app/(user)/update-password/page.tsx b/app/(user)/update-password/page.tsx index 7856d39..e63c719 100644 --- a/app/(user)/update-password/page.tsx +++ b/app/(user)/update-password/page.tsx @@ -5,7 +5,6 @@ import Footer from "@/app/components/layout/Footer"; export const metadata: Metadata = { title: "Update Password - DevPulse", - description: "Update your DevPulse password to keep your account secure.", }; export default async function UpdatePassword() { diff --git a/app/api/wakatime/sync/route.ts b/app/api/wakatime/sync/route.ts index ed1dda7..680c492 100644 --- a/app/api/wakatime/sync/route.ts +++ b/app/api/wakatime/sync/route.ts @@ -1,8 +1,10 @@ import { NextResponse } from "next/server"; import { createClient } from "../../../lib/supabase/server"; +import { getUserWithProfile } from "@/app/lib/supabase/help/user"; export async function GET(request: Request) { const supabase = await createClient(); + const { user, profile } = await getUserWithProfile(); const { searchParams } = new URL(request.url); const apiKey = searchParams.get("apiKey") || ""; let profile$: { wakatime_api_key: string }; @@ -16,22 +18,11 @@ export async function GET(request: Request) { profile$ = { wakatime_api_key: apiKey }; - const { - data: { user }, - } = await supabase.auth.getUser(); - if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (!apiKey) { - // Get profile with API key - const { data: profile } = await supabase - .from("profiles") - .select("wakatime_api_key") - .eq("id", user.id) - .single(); - if (!profile?.wakatime_api_key) { return NextResponse.json({ error: "No API key found" }, { status: 400 }); } @@ -80,7 +71,7 @@ export async function GET(request: Request) { `https://wakatime.com/api/v1/users/current/summaries?start=${startStr}&end=${endStr}`, { headers: { Authorization: authHeader }, - } + }, ), ]); @@ -129,34 +120,38 @@ export async function GET(request: Request) { } } - const { data: statsResult, error: statsError } = await supabase - .from("user_stats") - .upsert({ - user_id: user.id, - total_seconds: Math.floor(wakaStats.total_seconds), - daily_average: Math.floor(wakaStats.daily_average || 0), - languages: wakaStats.languages, - operating_systems: wakaStats.operating_systems, - editors: wakaStats.editors, - machines: wakaStats.machines, - categories: wakaStats.categories, - dependencies: wakaStats.dependencies || [], - best_day: wakaStats.best_day || {}, - daily_stats: daily_stats, - last_fetched_at: new Date().toISOString(), - }) - .select() - .single(); - - const { data: projectsResult, error: projectsError } = await supabase - .from("user_projects") - .upsert({ - user_id: user.id, - projects: wakaStats.projects, - last_fetched_at: new Date().toISOString(), - }) - .select() - .single(); + const [ + { data: statsResult, error: statsError }, + { data: projectsResult, error: projectsError }, + ] = await Promise.all([ + supabase + .from("user_stats") + .upsert({ + user_id: user.id, + total_seconds: Math.floor(wakaStats.total_seconds), + daily_average: Math.floor(wakaStats.daily_average || 0), + languages: wakaStats.languages, + operating_systems: wakaStats.operating_systems, + editors: wakaStats.editors, + machines: wakaStats.machines, + categories: wakaStats.categories, + dependencies: wakaStats.dependencies || [], + best_day: wakaStats.best_day || {}, + daily_stats: daily_stats, + last_fetched_at: new Date().toISOString(), + }) + .select() + .single(), + supabase + .from("user_projects") + .upsert({ + user_id: user.id, + projects: wakaStats.projects, + last_fetched_at: new Date().toISOString(), + }) + .select() + .single(), + ]); const mergedResult = { ...statsResult, diff --git a/app/components/Chat.tsx b/app/components/Chat.tsx index 601cdc0..9d620c1 100644 --- a/app/components/Chat.tsx +++ b/app/components/Chat.tsx @@ -666,6 +666,13 @@ export default function Chat({ user }: { user: User }) { placeholder="Search user..." className="w-full mb-3 px-3 py-2 bg-transparent text-gray-100 placeholder:text-gray-500 border border-neutral-800 rounded-xl outline-none" /> + + {allUsers.length == 0 && ( +
+

No users found.

+
+ )} +
{allUsers .filter((u) => diff --git a/app/components/dashboard/WithoutKey.tsx b/app/components/dashboard/WithoutKey.tsx index 4fdf5c3..75a63a8 100644 --- a/app/components/dashboard/WithoutKey.tsx +++ b/app/components/dashboard/WithoutKey.tsx @@ -53,7 +53,7 @@ export default function DashboardWithoutKey({ email }: { email: string }) { }; return ( -
+

Connect Wakatime

@@ -62,7 +62,7 @@ export default function DashboardWithoutKey({ email }: { email: string }) {

-
+

Welcome {email}. Enter your WakaTime API key to activate your DevPulse dashboard. diff --git a/app/lib/supabase/help/user.ts b/app/lib/supabase/help/user.ts new file mode 100644 index 0000000..58fe3b1 --- /dev/null +++ b/app/lib/supabase/help/user.ts @@ -0,0 +1,20 @@ +import { cache } from "react"; +import { createClient } from "../server"; + +export const getUserWithProfile = cache(async () => { + const supabase = await createClient(); + + const { + data: { user }, + } = await supabase.auth.getUser(); + + if (!user) return { user: null, profile: null }; + + const { data: profile } = await supabase + .from("profiles") + .select("wakatime_api_key, email, role") + .eq("id", user.id) + .single(); + + return { user, profile }; +});