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
3 changes: 2 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @type {import("next").NextConfig} */
const nextConfig = {
transpilePackages: ["driver.js"],
images: {
remotePatterns: [
{
Expand All @@ -10,4 +11,4 @@ const nextConfig = {
},
};

export default nextConfig;
export default nextConfig;
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"html-to-image": "^1.11.13",
"driver.js": "^1.4.0",
"jspdf": "^4.2.1",
"jspdf-autotable": "^5.0.7",
"next": "^14.2.35",
Expand Down
75 changes: 49 additions & 26 deletions src/app/api/user/settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export async function GET(req: NextRequest) {
// Fetch user from Supabase
const { data, error } = await supabaseAdmin
.from("users")
.select("id, github_login, is_public, leaderboard_opt_in")
.select(
"id, github_login, is_public, leaderboard_opt_in, seen_onboarding"
)
.eq("id", user.id)
.single();

if (error) {
console.error("Error fetching user:", error);

return NextResponse.json(
{ error: "Failed to fetch user settings" },
{ status: 500 }
Expand All @@ -46,18 +48,23 @@ export async function PATCH(req: NextRequest) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Get user ID from Supabase
const user = await resolveAppUser(session.githubId, session.githubLogin);
const user = await resolveAppUser(session.githubId, session.githubLogin);

if (!user) {
return NextResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}
if (!user) {
console.error("Error fetching user");

return NextResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}

let body: {
is_public?: boolean;
leaderboard_opt_in?: boolean;
seen_onboarding?: boolean;
};

// Parse request body
let body: { is_public?: boolean; leaderboard_opt_in?: boolean };
try {
body = await req.json();
} catch {
Expand All @@ -67,49 +74,65 @@ export async function PATCH(req: NextRequest) {
);
}

const { is_public, leaderboard_opt_in } = body;
const {
is_public,
leaderboard_opt_in,
seen_onboarding,
} = body;

if (
typeof is_public !== "boolean" &&
typeof leaderboard_opt_in !== "boolean"
) {
return NextResponse.json(
{ error: "At least one boolean setting is required" },
{ status: 400 }
);
}
const updates: {
is_public?: boolean;
leaderboard_opt_in?: boolean;
seen_onboarding?: boolean;
} = {};

const updates: { is_public?: boolean; leaderboard_opt_in?: boolean } = {};
if (typeof is_public === "boolean") {
updates.is_public = is_public;
}

if (typeof leaderboard_opt_in === "boolean") {
updates.leaderboard_opt_in = leaderboard_opt_in;

// If user opts into leaderboard, profile must be public
if (leaderboard_opt_in) {
updates.is_public = true;
}
}

if (typeof seen_onboarding === "boolean") {
updates.seen_onboarding = seen_onboarding;
}

if (Object.keys(updates).length === 0) {
return NextResponse.json(
{ error: "No valid fields to update" },
{ status: 400 }
);
}

const { data: updated, error: updateError } = await supabaseAdmin
.from("users")
.update(updates)
.eq("id", user.id)
.select("id, github_login, is_public, leaderboard_opt_in")
.select(
"id, github_login, is_public, leaderboard_opt_in, seen_onboarding"
)
.single();

if (updateError || !updated) {
console.error("Error updating settings:", updateError);
console.error("Failed to update settings:", updateError);

return NextResponse.json(
{ error: "Failed to update settings" },
{ status: 500 }
);
}

// Return updated user (only safe fields)
return NextResponse.json({
id: updated.id,
github_login: updated.github_login,
is_public: updated.is_public,
leaderboard_opt_in: updated.leaderboard_opt_in ?? false,
seen_onboarding: updated.seen_onboarding,
});
}
20 changes: 15 additions & 5 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,27 @@ export default async function DashboardPage() {
{/* Row 1: Contribution graph + Streak + Friend Comparison */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<ContributionGraph />
<div id="widget-contribution-graph">
<ContributionGraph />
</div>
<div className="mt-6">
<ContributionHeatmap />
</div>
</div>

<div className="flex flex-col gap-6">
<StreakTracker />
<div id="widget-streak">
<StreakTracker />
</div>
<FriendComparison />
</div>
</div>

{/* Row 2: PR metrics, PR breakdown & Time Chart */}
<div className="mt-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
<PRMetrics />
<div id="widget-pr-metrics">
<PRMetrics />
</div>
<PRBreakdownChart />
<CommitTimeChart />
</div>
Expand All @@ -98,9 +104,13 @@ export default async function DashboardPage() {

{/* Row 5: Top repos + Language breakdown + Goal tracker */}
<div className="mt-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
<TopRepos />
<div id="widget-top-repos">
<TopRepos />
</div>
<LanguageBreakdown />
<GoalTracker />
<div id="widget-goals">
<GoalTracker />
</div>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Providers from "./providers";
import "driver.js/dist/driver.css";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });
Expand Down
Loading
Loading