Skip to content

Commit 9c01c35

Browse files
committed
Merge branch 'main' into test/svg-dimensions-per-size
2 parents bbdd929 + 0d4d1d4 commit 9c01c35

62 files changed

Lines changed: 23075 additions & 1178 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/scripts/issue-management/claim-handler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async function handleClaim({ github, context }) {
8989
owner,
9090
repo,
9191
issue_number: issueNumber,
92-
body: `✅ Successfully assigned issue to @${commenter}\n\n> 💡 Please read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) if you haven't already. Good luck! 🚀`,
92+
body: `🎉 **Assigned!** Welcome to the project, @${commenter}.\n\n⏳ **Reminder:** You have **3 days** to submit a Pull Request. After 3 days of inactivity, you will be automatically unassigned to give others a chance (as per our GSSoC anti-hoarding policy).\n\n> 💡 Please read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) if you haven't already.\n\nHappy coding! 🚀`,
9393
});
9494
}
9595

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Sourav Jha
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ URL Parameter > Theme Default > System Fallback
155155
| `hide_background` | `boolean` | No | `false` | Remove the background rect, letting the monolith float on the page |
156156
| `hide_stats` | `boolean` | No | `false` | Hides the bottom row displaying Current Streak, Annual Sync Total, and Peak Streak stats when set to `true` or `1`. |
157157
| `tz` | `string` | No | Omitted = UTC | IANA timezone (e.g. `Asia/Kolkata`, `America/New_York`) — aligns "today" with the user local midnight. Note: `?tz=UTC` is valid but cached separately from omitting `tz`. |
158-
| `lang` | `string` | No | `en` | Language code for labels (`en`, `es`, `hi`, `fr`, `pt`, `ko`, `ja`) |
158+
| `lang` | `string` | No | `en` | Language code for labels (`en`, `es`, `hi`, `fr`, `pt`, `ko`, `ja`, `de`) |
159159
| `view` | `string` | No | `default` | Rendering mode: `default` (3D Monolith) or `monthly` (Compact monthly stats) |
160160
| `delta_format` | `string` | No | `percent` | Format for month-over-month delta in monthly view: `percent` (e.g. +12%), `absolute` (e.g. +15 commits), or `both` |
161161
| `width` | `number` | No | `300` | Custom width for the SVG canvas (currently only applies to `view=monthly`) |

app/(root)/dashboard/[username]/page.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ describe('DashboardPage', () => {
110110

111111
expect(metadata.title).toBe("octocat's Commit Pulse");
112112
expect(metadata.description).toContain("octocat's GitHub contribution pulse");
113-
expect(openGraphImage.url).toContain('api/og?username=octocat');
113+
expect(openGraphImage.url).toContain('api/og?user=octocat');
114114
expect(openGraphImage.width).toBe(1200);
115115
expect(openGraphImage.height).toBe(630);
116116
expect(openGraphImage.alt).toContain(username);
@@ -131,7 +131,7 @@ describe('DashboardPage', () => {
131131
bypassCache: false,
132132
});
133133

134-
const generateLink = screen.getByText('Generate Your Own Dashboard').closest('a');
134+
const generateLink = screen.getByText('Generate Your Own').closest('a');
135135
expect(generateLink).toBeDefined();
136136
expect(generateLink?.getAttribute('href')).toBe('/');
137137
expect(screen.getByTestId('profile-card')).toBeDefined();
@@ -160,6 +160,7 @@ describe('DashboardPage', () => {
160160
});
161161
});
162162
});
163+
163164
it('passes the correct activity data to Heatmap', async () => {
164165
const PageContent = await DashboardPage({
165166
params: Promise.resolve({ username: 'octocat' }),

app/(root)/dashboard/[username]/page.tsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
// app/(root)/dashboard/[username]/page.tsx
2+
13
import type { Metadata } from 'next';
4+
import { notFound, redirect } from 'next/navigation';
5+
import Link from 'next/link';
6+
27
import RefreshButton from '@/components/dashboard/RefreshButton';
38
import ProfileCard from '@/components/dashboard/ProfileCard';
49
import ActivityLandscape from '@/components/dashboard/ActivityLandscape';
@@ -8,9 +13,7 @@ import CommitClock from '@/components/dashboard/CommitClock';
813
import Heatmap from '@/components/dashboard/Heatmap';
914
import AIInsights from '@/components/dashboard/AIInsights';
1015
import Achievements from '@/components/dashboard/Achievements';
11-
import { getFullDashboardData } from '@/lib/github';
12-
import Link from 'next/link';
13-
import { notFound } from 'next/navigation';
16+
import { getFullDashboardData, fetchUserProfile } from '@/lib/github';
1417

1518
export const revalidate = 3600; // Cache for 1 hour
1619

@@ -23,10 +26,8 @@ export async function generateMetadata({
2326
}: {
2427
params: Promise<{ username: string }>;
2528
}): Promise<Metadata> {
26-
// Lightweight — no API calls here.
27-
// Real data is fetched by /api/og on demand when social platforms render the preview.
2829
const { username } = await params;
29-
const ogImage = `${BASE_URL}/api/og?username=${username}`;
30+
const ogImage = `${BASE_URL}/api/og?user=${username}`;
3031
const title = `${username}'s Commit Pulse`;
3132
const description = `Check out ${username}'s GitHub contribution pulse — streaks, insights, and more on CommitPulse.`;
3233

@@ -62,19 +63,25 @@ export default async function DashboardPage({
6263
const refreshParams = await searchParams;
6364
const bypassCache = refreshParams?.refresh === 'true';
6465

65-
// Fetch real GitHub data
6666
let data;
6767

6868
try {
69-
data = await getFullDashboardData(username, {
70-
bypassCache,
71-
});
69+
data = await getFullDashboardData(username, { bypassCache });
7270
} catch (error) {
73-
if (error instanceof Error) {
71+
if (error instanceof Error && error.message.includes('not found')) {
72+
// Smart Redirect: If the GraphQL "user" query fails, check if it's actually an Organization
73+
try {
74+
const fallbackProfile = await fetchUserProfile(username, { bypassCache });
75+
if (fallbackProfile.type === 'Organization') {
76+
redirect(`/dashboard/org/${username}`);
77+
}
78+
} catch {
79+
// If it's truly neither a user nor an org, show 404
80+
return notFound();
81+
}
7482
return notFound();
7583
}
76-
77-
throw Error;
84+
throw error;
7885
}
7986

8087
return (
@@ -98,7 +105,7 @@ export default async function DashboardPage({
98105
>
99106
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
100107
</svg>
101-
Generate Your Own Dashboard
108+
Generate Your Own
102109
</Link>
103110
</div>
104111

@@ -112,9 +119,9 @@ export default async function DashboardPage({
112119
languages: data.languages,
113120
}}
114121
/>
115-
{/* We omit real achievements data generation for now and just show a placeholder based on streaks */}
116122
<Achievements achievements={data.achievements} />
117123
</aside>
124+
118125
{/* Main Content */}
119126
<div className="flex flex-col gap-6 lg:gap-8 min-w-0">
120127
<section>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { notFound } from 'next/navigation';
2+
import type { Metadata } from 'next';
3+
import GithubWrapped from '@/components/dashboard/GithubWrapped';
4+
import { getFullDashboardData, getWrappedData } from '@/lib/github';
5+
6+
export async function generateMetadata({
7+
params,
8+
}: {
9+
params: Promise<{ username: string }>;
10+
}): Promise<Metadata> {
11+
const { username } = await params;
12+
return {
13+
title: `${username}'s GitHub Wrapped`,
14+
description: `A cinematic year-in-review of ${username}'s open-source contributions.`,
15+
};
16+
}
17+
18+
export default async function WrappedPage({
19+
params,
20+
searchParams,
21+
}: {
22+
params: Promise<{ username: string }>;
23+
searchParams: Promise<{ year?: string }>;
24+
}) {
25+
const { username } = await params;
26+
const resolvedSearchParams = await searchParams;
27+
const targetYear = resolvedSearchParams?.year || new Date().getFullYear().toString();
28+
29+
// 1. Fetch data safely.
30+
// If this fails, the error will bubble up to the nearest error.tsx file.
31+
let dashboardData;
32+
let wrappedData;
33+
34+
try {
35+
[dashboardData, wrappedData] = await Promise.all([
36+
getFullDashboardData(username),
37+
getWrappedData(username, targetYear),
38+
]);
39+
} catch (error) {
40+
console.error('[Wrapped] Failed to load wrapped data:', error);
41+
// If the user doesn't exist or API fails, trigger the 404 page
42+
return notFound();
43+
}
44+
45+
// 2. Render the successful component outside of any try/catch blocks.
46+
return (
47+
<div className="min-h-[85vh] p-4 md:p-8 flex items-center justify-center relative">
48+
<GithubWrapped profile={dashboardData.profile} wrappedData={wrappedData} />
49+
</div>
50+
);
51+
}

app/(root)/dashboard/loading.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import StatsCardSkeleton from '@/components/dashboard/StatsCardSkeleton';
22
import AchievementsSkeleton from '@/components/dashboard/AchievementsSkeleton';
3+
import AIInsightsSkeleton from '@/components/dashboard/AIInsightsSkeleton';
34

45
export default function DashboardLoading() {
56
return (
6-
<div className="p-4 md:p-6 lg:p-8 min-h-screen">
7+
<div className="p-4 md:p-6 lg:p-8 min-h-screen bg-black text-white">
78
<div className="grid grid-cols-1 lg:grid-cols-[300px_1fr_320px] gap-6 lg:gap-8">
89
{/* Left Sidebar Skeleton */}
910
<div className="flex flex-col gap-6">
@@ -36,11 +37,11 @@ export default function DashboardLoading() {
3637
</div>
3738

3839
{/* Right Sidebar Skeleton */}
39-
<div className="flex flex-col gap-4">
40+
<div className="flex flex-col gap-6">
4041
<div className="h-24 rounded-2xl shimmer border border-white/10" />
4142
<div className="h-24 rounded-2xl shimmer border border-white/10" />
4243
<div className="h-24 rounded-2xl shimmer border border-white/10" />
43-
<div className="h-48 rounded-2xl shimmer border border-white/10" />
44+
<AIInsightsSkeleton />
4445
</div>
4546
</div>
4647
</div>

0 commit comments

Comments
 (0)