Skip to content

Commit a3a1c66

Browse files
authored
feat: create a separate url for comparison mode and add a share button to it (JhaSourav07#2064)
## Description Previously on entering comparison mode there was no new route created. this PR fixes that and creates a new route for comparison. Previously when the user refreshes the page they exit from comparison mode now due to new route that is prevented. Fixes JhaSourav07#1717 ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview BEFORE <img width="1920" height="1200" alt="Screenshot 2026-05-31 155050" src="https://github.com/user-attachments/assets/9ed8866c-bbc0-40f3-bc0d-a9c7ea53283b" /> AFTER <img width="1920" height="1200" alt="Screenshot 2026-05-31 155028" src="https://github.com/user-attachments/assets/a6d1d37a-57f3-4134-83de-639c223ee842" /> ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [ ] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have started the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [ ] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents 44ed4f1 + becaff5 commit a3a1c66

2 files changed

Lines changed: 73 additions & 6 deletions

File tree

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export default async function DashboardPage({
6363
params: Promise<{ username: string }>;
6464
searchParams: Promise<{
6565
refresh?: string;
66+
compare?: string;
6667
year?: string;
6768
month?: string;
6869
from?: string;
@@ -72,6 +73,7 @@ export default async function DashboardPage({
7273
const { username } = await params;
7374
const resolvedSearchParams = await searchParams;
7475
const bypassCache = resolvedSearchParams?.refresh === 'true';
76+
const compareUsername = resolvedSearchParams?.compare;
7577
const period = resolveDashboardPeriod({
7678
year: resolvedSearchParams?.year,
7779
month: resolvedSearchParams?.month,
@@ -90,10 +92,11 @@ export default async function DashboardPage({
9092
});
9193
} catch (error) {
9294
if (error instanceof Error && error.message.includes('not found')) {
93-
// Smart Redirect: If the GraphQL "user" query fails, check if it's actually an Organization
9495
let fallbackProfile;
9596
try {
96-
fallbackProfile = await fetchUserProfile(username, { bypassCache });
97+
fallbackProfile = await fetchUserProfile(username, {
98+
bypassCache,
99+
});
97100
} catch {
98101
return notFound();
99102
}
@@ -105,5 +108,24 @@ export default async function DashboardPage({
105108
throw error;
106109
}
107110

108-
return <DashboardClient initialData={data} username={username} period={period} />;
111+
let compareData = null;
112+
113+
if (compareUsername && compareUsername.toLowerCase() !== username.toLowerCase()) {
114+
try {
115+
compareData = await getFullDashboardData(compareUsername, {
116+
bypassCache,
117+
});
118+
} catch {
119+
compareData = null;
120+
}
121+
}
122+
123+
return (
124+
<DashboardClient
125+
initialData={data}
126+
username={username}
127+
compareData={compareData}
128+
period={period}
129+
/>
130+
);
109131
}

components/dashboard/DashboardClient.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import RepositoryGraph from './RepositoryGraph';
2222
import ComparisonStatsCard from './ComparisonStatsCard';
2323
import RadarChart from './RadarChart';
2424
import GrowthTrendChart from './GrowthTrendChart';
25+
import { useRouter } from 'next/navigation';
2526
import ProfileOptimizerModal from './ProfileOptimizerModal';
2627
import ResumeProfileSection from './ResumeProfileSection';
2728
import type { DashboardPeriod } from '@/utils/dashboardPeriod';
@@ -78,6 +79,7 @@ interface DashboardData {
7879
interface DashboardClientProps {
7980
initialData: DashboardData;
8081
username: string;
82+
compareData?: DashboardData | null;
8183
period: DashboardPeriod;
8284
}
8385

@@ -322,14 +324,20 @@ function getPersonalityTags(
322324
// DashboardClient Component
323325
// ------------------------------------------------------------
324326

325-
export default function DashboardClient({ initialData, username, period }: DashboardClientProps) {
326-
const [secondUserData, setSecondUserData] = useState<DashboardData | null>(null);
327-
const [isCompareMode, setIsCompareMode] = useState(false);
327+
export default function DashboardClient({
328+
initialData,
329+
username,
330+
compareData = null,
331+
period,
332+
}: DashboardClientProps) {
333+
const [secondUserData, setSecondUserData] = useState<DashboardData | null>(compareData);
334+
const [isCompareMode, setIsCompareMode] = useState(Boolean(compareData));
328335
const [isModalOpen, setIsModalOpen] = useState(false);
329336
const [isOptimizerOpen, setIsOptimizerOpen] = useState(false);
330337
const [secondUsernameInput, setSecondUsernameInput] = useState('');
331338
const [isLoadingSecond, setIsLoadingSecond] = useState(false);
332339
const [compareError, setCompareError] = useState<string | null>(null);
340+
const router = useRouter();
333341

334342
const modalRef = useRef<HTMLDivElement>(null);
335343
const compareInputRef = useRef<HTMLInputElement>(null);
@@ -425,6 +433,9 @@ export default function DashboardClient({ initialData, username, period }: Dashb
425433
const data = await res.json();
426434
setSecondUserData(data);
427435
setIsCompareMode(true);
436+
437+
router.replace(`/dashboard/${username}?compare=${data.profile.username}`);
438+
428439
setIsModalOpen(false);
429440
toast.success(`Comparing ${username} vs ${data.profile.username}`);
430441
} catch (err: unknown) {
@@ -439,9 +450,33 @@ export default function DashboardClient({ initialData, username, period }: Dashb
439450
const handleExitCompare = () => {
440451
setIsCompareMode(false);
441452
setSecondUserData(null);
453+
454+
router.replace(`/dashboard/${username}`);
455+
442456
toast.info('Returned to single profile view');
443457
};
444458

459+
const handleShareComparison = async () => {
460+
if (!secondUserData) return;
461+
462+
const compareUrl = `${window.location.origin}/dashboard/${username}?compare=${secondUserData.profile.username}`;
463+
464+
try {
465+
if (navigator.share) {
466+
await navigator.share({
467+
title: `${username} vs ${secondUserData.profile.username}`,
468+
text: 'Check out this GitHub profile comparison',
469+
url: compareUrl,
470+
});
471+
} else {
472+
await navigator.clipboard.writeText(compareUrl);
473+
toast.success('Comparison link copied!');
474+
}
475+
} catch {
476+
// user cancelled share dialog
477+
}
478+
};
479+
445480
// ------------------------------------------------------------
446481
// Compare Mode Statistics Calculations
447482
// ------------------------------------------------------------
@@ -563,6 +598,16 @@ export default function DashboardClient({ initialData, username, period }: Dashb
563598
</button>
564599
</>
565600
)}
601+
{isCompareMode && secondUserData && (
602+
<button
603+
onClick={handleShareComparison}
604+
className="flex items-center gap-2 rounded-xl border border-black/10 dark:border-[rgba(255,255,255,0.15)] bg-blue-600 hover:bg-blue-700 px-4 py-2 text-sm font-semibold text-white transition-all duration-200 active:scale-[0.98]"
605+
>
606+
<Share2 size={16} />
607+
Share Comparison
608+
</button>
609+
)}
610+
566611
<RefreshButton username={username} />
567612
<button
568613
onClick={() => {

0 commit comments

Comments
 (0)