Skip to content

Commit d508d29

Browse files
DESIREDDY MOHITH REDDYDESIREDDY MOHITH REDDY
authored andcommitted
chore: resolve merge conflict in navbar.responsive-breakpoints.test.tsx
2 parents 3ddc0f5 + 25ec7f3 commit d508d29

273 files changed

Lines changed: 19423 additions & 961 deletions

File tree

Some content is hidden

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

.env.local.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,10 @@ NEXT_ALLOWED_DEV_ORIGINS=
5757
# Find your GitHub user ID at https://api.github.com/users/<username> (the "id" field).
5858
# Example: ENTERPRISE_ADMIN_GITHUB_IDS=12345678,87654321
5959
ENTERPRISE_ADMIN_GITHUB_IDS=
60+
61+
# Spotify Integration (Optional)
62+
# Required for the "Currently Playing" SVG feature.
63+
# Follow the setup guide in the documentation to get these credentials.
64+
SPOTIFY_CLIENT_ID=
65+
SPOTIFY_CLIENT_SECRET=
66+
SPOTIFY_REFRESH_TOKEN=

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

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,13 @@ async function handleClaim({ github, context }) {
2828
return;
2929
}
3030

31-
const issueAuthor = context.payload.issue.user.login;
32-
33-
const MAINTAINERS = ['jhasourav07', 'aamod-dev', 'souravjhahind'];
34-
const isOpenedByMaintainer = MAINTAINERS.includes(issueAuthor.toLowerCase());
35-
36-
if (!isOpenedByMaintainer && commenter.toLowerCase() !== issueAuthor.toLowerCase()) {
37-
await github.rest.issues.createComment({
38-
owner,
39-
repo,
40-
issue_number: issueNumber,
41-
body: `❌ Only the author of this issue (@${issueAuthor}) can claim it.`,
42-
});
43-
return;
44-
}
45-
46-
// Re-fetch to avoid stale assignee data from the webhook payload
31+
// Re-fetch to avoid stale issue/assignee/state data from the webhook payload
4732
const { data: freshIssue } = await github.rest.issues.get({
4833
owner,
4934
repo,
5035
issue_number: issueNumber,
5136
});
37+
5238
const currentAssignees = freshIssue.assignees.map((a) => a.login.toLowerCase());
5339

5440
if (currentAssignees.length > 0) {
@@ -71,6 +57,38 @@ async function handleClaim({ github, context }) {
7157
return;
7258
}
7359

60+
const issueAuthor = freshIssue.user.login;
61+
const issueAuthorLower = issueAuthor.toLowerCase();
62+
const commenterLower = commenter.toLowerCase();
63+
64+
const MAINTAINERS = ['jhasourav07', 'aamod-dev', 'souravjhahind'];
65+
const isOpenedByMaintainer = MAINTAINERS.includes(issueAuthorLower);
66+
67+
// Check if the issue is older than 1 week (7 days)
68+
const createdAt = new Date(freshIssue.created_at);
69+
const now = new Date();
70+
const ageInMs = now.getTime() - createdAt.getTime();
71+
const oneWeekInMs = 7 * 24 * 60 * 60 * 1000;
72+
const isOlderThanOneWeek = ageInMs > oneWeekInMs;
73+
74+
// Check if opened by any other person other than jhasourav07 or aamod007 (and aamod-dev for safety)
75+
const isOpenedByOther = !['jhasourav07', 'aamod007', 'aamod-dev'].includes(issueAuthorLower);
76+
77+
const isAuthor = commenterLower === issueAuthorLower;
78+
const canClaimOlderIssue = isOpenedByOther && isOlderThanOneWeek;
79+
80+
const isAllowedToClaim = isAuthor || isOpenedByMaintainer || canClaimOlderIssue;
81+
82+
if (!isAllowedToClaim) {
83+
await github.rest.issues.createComment({
84+
owner,
85+
repo,
86+
issue_number: issueNumber,
87+
body: `❌ Only the author of this issue (@${issueAuthor}) can claim it.`,
88+
});
89+
return;
90+
}
91+
7492
const existingIssues = await findExistingAssignments(github, owner, repo, commenter, issueNumber);
7593
if (existingIssues.length >= MAX_ASSIGNED_ISSUES) {
7694
const issueList = existingIssues

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ jobs:
4747
- name: Run Unit Tests (Vitest)
4848
run: npm run test:coverage
4949

50+
- name: Upload Visual Regression Diffs
51+
if: failure()
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: visual-test-diffs
55+
path: tests/visual/diffs/
56+
if-no-files-found: ignore
57+
5058
build:
5159
name: Production Build
5260
runs-on: ubuntu-latest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
# testing
1414
/coverage
15+
/tests/visual/diffs/
1516

1617
# next.js
1718
/.next/

CONTRIBUTING.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ To maintain high quality in our codebase, we use structured **Issue Templates**
412412
- **🐛 Bug Report Template**: For reporting visual glitches, API errors, or unexpected behavior.
413413
- **✨ Feature Request Template**: For suggesting new isometric monolith designs, themes, or time/accuracy improvements.
414414

415-
By using these templates, you provide maintainers with clear details and context. Since you authored the issue, you can immediately claim it for yourself by commenting `/claim`! (Note: Issues authored by `jhasourav07` can be claimed by anyone).
415+
By using these templates, you provide maintainers with clear details and context. Since you authored the issue, you can immediately claim it for yourself by commenting `/claim`! (Note: Issues authored by `jhasourav07` can be claimed by anyone immediately. Additionally, if an issue is opened by anyone other than `jhasourav07` or `aamod007`, is older than 1 week, and has no assignees, any contributor can claim it).
416416

417417
### 🔍 Semantic Duplicate Detection
418418

@@ -427,13 +427,13 @@ To help maintainers keep the repository organized and prevent multiple contribut
427427

428428
Our automation runs entirely through issue comments. Here is how you interact with it:
429429

430-
| Command | Who Can Use It? | What It Does |
431-
| ----------------------------- | ------------------------------------------------------- | --------------------------------------------------------- |
432-
| `/claim` | **Issue Author (or Anyone if authored by jhasourav07)** | Self-assigns the issue to you. |
433-
| `/unclaim` | **Assigned Contributor** | Removes the assignment from yourself (opens it back up). |
434-
| `/addlabel <label1> <label2>` | **Anyone** | Adds labels to the issue (e.g. `/addlabel frontend bug`). |
435-
| `/unassign @username` | **Maintainers Only** | Removes the assignee from an issue. |
436-
| `/assign @username` | **Maintainers Only** | Manually assigns someone to an issue. |
430+
| Command | Who Can Use It? | What It Does |
431+
| ----------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
432+
| `/claim` | **Issue Author (or Anyone if authored by jhasourav07, or Anyone if older than 1 week with no assignees)** | Self-assigns the issue to you. |
433+
| `/unclaim` | **Assigned Contributor** | Removes the assignment from yourself (opens it back up). |
434+
| `/addlabel <label1> <label2>` | **Anyone** | Adds labels to the issue (e.g. `/addlabel frontend bug`). |
435+
| `/unassign @username` | **Maintainers Only** | Removes the assignee from an issue. |
436+
| `/assign @username` | **Maintainers Only** | Manually assigns someone to an issue. |
437437

438438
### ⏳ The Inactivity Policy (Assignment Expiry)
439439

@@ -458,7 +458,7 @@ If the bot rejects your command, check these common scenarios:
458458
- **"Commands cannot be used on closed issues"**: You cannot claim, assign, or unassign on closed issues. Find an open one!
459459
- **"You already have X/5 active assigned issues"**: You have reached the maximum of 5 concurrent assignments. Finish one of your current tasks before claiming a new issue. If you're stuck, use the `/unclaim` command to unassign yourself from an issue, or ask a maintainer to `/unassign` you.
460460
- **"This issue is already assigned to @username"**: Be faster next time! Look for issues without assignees.
461-
- **"Only the author of this issue can claim it"**: You tried to `/claim` an issue you did not create. You can only claim issues that you authored (unless the issue was authored by `jhasourav07`, which anyone can claim).
461+
- **"Only the author of this issue can claim it"**: You tried to `/claim` an issue you did not create. You can only claim issues that you authored (unless the issue is older than 1 week with no assignees, or was authored by `jhasourav07`, which anyone can claim).
462462
- **"The following label(s) do not exist"**: You can only add existing repo labels. The bot will reply with a list of valid labels you can use.
463463
- **"You don't have permission"**: You tried to use `/assign` or `/unassign`. Please use `/claim` instead.
464464

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,35 @@ Transform your GitHub contribution history into a cinematic 3D monolith.
139139
| `monokai` | Classic vibrant dark | `272822` | `a6e22e` | `f8f8f2` |
140140
| `midnight_ocean` | Deep navy bioluminescent | `020c1b` | `0af5ff` | `ccd6f6` |
141141
| `india` | Saffron & India green | `0a0a0a` | `FF9933` | `ffffff` |
142+
| `ocean` | Deep sea teal & navy | `0a192f` | `64ffda` | `ccd6f6` |
143+
| `sunset` | Warm dusk orange | `1a0a0a` | `ff6b35` | `ffd6c0` |
144+
| `forest` | Emerald woodland green | `0d1f0d` | `39d353` | `c8f0c8` |
145+
| `rose` | Blush pink romance | `1f0d14` | `ff6b9d` | `f0c8d4` |
146+
| `nord` | Arctic frost blue | `2e3440` | `88c0d0` | `d8dee9` |
147+
| `synthwave` | 80s retro pink glow | `0d0221` | `ff2d78` | `f8f8f2` |
148+
| `aurora_cyberpunk` | Aurora violet neon | `090B13` | `9D5CFF` | `EAF2FF` |
149+
| `catppuccin_latte` | Pastel latte light | `eff1f5` | `1e66f5` | `4c4f69` |
150+
| `solarized_light` | Warm solarized light | `fdf6e3` | `268bd2` | `586e75` |
151+
| `gruvbox_light` | Retro warm light | `fbf1c7` | `d65d0e` | `3c3836` |
152+
| `nord_light` | Arctic frost light | `eceff4` | `5e81ac` | `2e3440` |
153+
| `cyberpunk` | Neon yellow cyberpunk | `fce22a` | `ff003c` | `111111` |
154+
| `cyberpunk_neon` | Electric cyan neon | `0d0d14` | `ff0055` | `00f3ff` |
155+
| `enterprise` | Professional indigo dark | `1a1a2e` | `6366f1` | `e2e8f0` |
142156

143157
> **`auto` uses CSS `@media (prefers-color-scheme)`** inside the SVG so the badge switches between the `light` and `dark` palettes based on the viewer's OS setting — no JavaScript required. This is ideal for GitHub profile READMEs where visitors may use either mode.
144158
145159
For all URL parameters and configuration possibilities (including grace periods, custom fonts, timezone overrides, versus comparison mode, heatmap view, LOC mode, and layout dimensions), check out the **[🎨 Customization Guide & Parameters](docs/customization.md)**.
146160

161+
For advanced usage examples including custom gradient backgrounds, multi-user comparisons, organization dashboards, and custom date ranges, see the **[🚀 Advanced Usage Examples](docs/customization.md#-advanced-usage-examples)** section in the customization guide.
162+
147163
---
148164

149165
## ✨ Features
150166

151167
CommitPulse transforms GitHub contribution data into visually engaging and highly customizable SVG badges.
152168

153169
- **🎨 Theme & Customization**: Multiple built-in themes, custom colors (`bg`, `accent`, `text`), dynamic font selection, adjustable dimensions, border radius, opacity, and system-aware `auto` light/dark theme.
170+
- **🎵 Spotify "Currently Playing"**: Showcase your current Spotify playback on your GitHub profile with a customizable, near real-time SVG card.
154171
- **📈 Contribution Analytics**: Current streak and longest streak tracking, monthly contribution summaries, historical year-by-year viewing, and custom grace period configurations.
155172
- **🔥 Visualization Modes**: Isometric 3D monolith rendering (with ghost city blueprint foundations), GitHub-style heatmap, monthly statistics view, and radar chart view.
156173
- **🌍 Localization & Accessibility**: Multi-language support (e.g. English, Hindi, Simplified Chinese, Portuguese), timezone-aware calculations, and high-contrast accessibility themes.
@@ -166,6 +183,7 @@ To keep the repository clean and readable, technical details have been modulariz
166183
- **[🎨 Customization Guide & Parameters](docs/customization.md)**: Explore the list of over 30 URL parameters including `theme`, `view` (e.g. `skyline`, `heatmap`, `radar`, `monthly`), `radius`, `grace`, `tz`, `entrance`, `versus`, and layout dimensions to style your monolith.
167184
- **[🏛️ Architecture & Design Philosophy](docs/architecture.md)**: Read about why we built isometric 3D monolith landscapes instead of flat meters, and check out our Next.js 16 Edge computing pipeline.
168185
- **[🚀 Self-Hosting & Deployment](docs/self_hosting.md)**: Step-by-step instructions to clone, configure `.env.local` with GitHub Personal Access Tokens (PAT), set up MongoDB tracking, and deploy to Vercel with one click.
186+
- **[🎵 Spotify Setup Guide](docs/SPOTIFY_SETUP.md)**: Instructions for setting up Spotify integration for the Currently Playing feature.
169187
- **[🤖 Automated Contributor Workflow](docs/contributor_workflow.md)**: Overview of GSSoC contribution automation, self-claiming comments `/claim`, anti-hoarding rules, stale unassign scripts, and Gemini AI-powered semantic issue duplication check.
170188
- **[🎯 Real-Time Accuracy & Caching](docs/accuracy.md)**: Deep dive into the "off-by-N contributions" problem and how CommitPulse solves it with UTC midnight CDN expiration and no-store GraphQL fetches.
171189
- **[❓ FAQ & Troubleshooting](docs/faq.md)**: Answers to common questions regarding timezone overrides, private contribution visibility, GitHub API rate limits, and troubleshooting.

THEMES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&theme=<slug>
4141
| monokai | `#272822` | `#f8f8f2` | `#a6e22e` |
4242
| retro-terminal | `#000000` | `#00ff41` | `#00ff41` |
4343
| midnight_ocean | `#020c1b` | `#ccd6f6` | `#0af5ff` |
44+
| ayu_mirage | `#212733` | `#D9D7CE` | `#FFCC66` |
4445

4546
---
4647

@@ -394,6 +395,16 @@ https://commitpulse.vercel.app/api/streak?user=YOUR_USERNAME&theme=<slug>
394395

395396
---
396397

398+
### Ayu Mirage
399+
400+
![ayu_mirage](https://commitpulse.vercel.app/api/streak?user=jhasourav07&theme=ayu_mirage)
401+
402+
| Parameter | Value |
403+
| --------- | ------ |
404+
| `bg` | 212733 |
405+
| `text` | D9D7CE |
406+
| `accent` | FFCC66 |
407+
397408
## Custom Theme
398409

399410
Not finding what you want? Build your own using raw color parameters - all values are hex codes **without** the `#` prefix:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export default async function DashboardPage({
9292
month?: string;
9393
from?: string;
9494
to?: string;
95+
excludeBots?: string;
9596
}>;
9697
}) {
9798
const { username } = await params;
@@ -116,9 +117,11 @@ async function DashboardContent({
116117
month?: string;
117118
from?: string;
118119
to?: string;
120+
excludeBots?: string;
119121
};
120122
}) {
121123
const bypassCache = searchParams?.refresh === 'true';
124+
const excludeBots = searchParams?.excludeBots === 'true';
122125
const compareUsername = searchParams?.compare;
123126
const period = resolveDashboardPeriod({
124127
year: searchParams?.year,
@@ -137,6 +140,7 @@ async function DashboardContent({
137140
to: period.to,
138141
rangeLabel: period.label,
139142
token: userToken,
143+
excludeBots,
140144
});
141145
} catch (error) {
142146
if (error instanceof Error && error.message.includes('not found')) {
@@ -176,6 +180,7 @@ async function DashboardContent({
176180
compareData = await getFullDashboardData(compareUsername, {
177181
bypassCache,
178182
token: userToken,
183+
excludeBots,
179184
});
180185
} catch {
181186
compareData = null;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { describe, it, expect, vi } from 'vitest';
3+
import React, { Component, ReactNode, ErrorInfo } from 'react';
4+
import DashboardLayout from './layout';
5+
6+
vi.mock('sonner', () => ({
7+
Toaster: () => <div data-testid="toaster" />,
8+
}));
9+
10+
class TestErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
11+
constructor(props: { children: ReactNode }) {
12+
super(props);
13+
this.state = { hasError: false };
14+
}
15+
static getDerivedStateFromError() {
16+
return { hasError: true };
17+
}
18+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
19+
// catch silently for tests
20+
}
21+
render() {
22+
if (this.state.hasError) {
23+
return <div>Test Fallback UI</div>;
24+
}
25+
return this.props.children;
26+
}
27+
}
28+
29+
const ThrowError = () => {
30+
throw new Error('Hydration error simulated');
31+
};
32+
33+
describe('DashboardLayout - Error Resilience', () => {
34+
it('prevents layout structural crash when child components throw errors', () => {
35+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
36+
37+
render(
38+
<TestErrorBoundary>
39+
<DashboardLayout>
40+
<ThrowError />
41+
</DashboardLayout>
42+
</TestErrorBoundary>
43+
);
44+
45+
expect(screen.getByText('Test Fallback UI')).toBeInTheDocument();
46+
47+
consoleErrorSpy.mockRestore();
48+
});
49+
50+
it('renders gracefully with fallback UI placeholders during hydration', () => {
51+
render(
52+
<DashboardLayout>
53+
<div data-testid="hydration-fallback">Loading layout skeleton...</div>
54+
</DashboardLayout>
55+
);
56+
57+
expect(screen.getByTestId('hydration-fallback')).toBeInTheDocument();
58+
expect(screen.getByTestId('toaster')).toBeInTheDocument();
59+
});
60+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { render, screen, act } from '@testing-library/react';
2+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3+
import DashboardLayout from './layout';
4+
import React from 'react';
5+
6+
vi.mock('sonner', () => ({
7+
Toaster: () => <div data-testid="toaster" />,
8+
}));
9+
10+
describe('DashboardLayout - mock integrations', () => {
11+
beforeEach(() => {
12+
vi.useFakeTimers();
13+
});
14+
15+
afterEach(() => {
16+
vi.clearAllTimers();
17+
vi.useRealTimers();
18+
});
19+
20+
it('renders children asynchronously resolving from a mocked service layer', async () => {
21+
const MockAsyncChild = () => {
22+
const [data, setData] = React.useState<string | null>(null);
23+
React.useEffect(() => {
24+
setTimeout(() => setData('Async Data Loaded'), 100);
25+
}, []);
26+
return <div data-testid="async-child">{data || 'Loading...'}</div>;
27+
};
28+
29+
render(
30+
<DashboardLayout>
31+
<MockAsyncChild />
32+
</DashboardLayout>
33+
);
34+
35+
expect(screen.getByTestId('async-child')).toHaveTextContent('Loading...');
36+
37+
await act(async () => {
38+
vi.advanceTimersByTime(100);
39+
});
40+
41+
expect(screen.getByTestId('async-child')).toHaveTextContent('Async Data Loaded');
42+
});
43+
44+
it('renders correctly with a local cache stub', () => {
45+
const cachedData = { user: 'Test User', cached: true };
46+
render(
47+
<DashboardLayout>
48+
<div data-testid="cache-child">{cachedData.user}</div>
49+
</DashboardLayout>
50+
);
51+
expect(screen.getByTestId('cache-child')).toHaveTextContent('Test User');
52+
expect(screen.getByTestId('toaster')).toBeInTheDocument();
53+
});
54+
});

0 commit comments

Comments
 (0)