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
254 changes: 215 additions & 39 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,114 @@
import TodayFocusHero from "@/components/TodayFocusHero";
import LazyWidget from "@/components/LazyWidget";
import DiscussionsWidget from "@/components/DiscussionsWidget";
import CommunityMetrics from "@/components/CommunityMetrics";
import GoalTracker from "@/components/GoalTracker";
import TodayFocusHero from "@/components/TodayFocusHero";
import DashboardHeader from "@/components/DashboardHeader";
import StreakTracker from "@/components/StreakTracker";
import TopRepos from "@/components/TopRepos";
import PinnedReposWidget from "@/components/PinnedReposWidget";
import InactiveRepositoriesCard from "@/components/InactiveRepositoriesCard";
import LanguageBreakdown from "@/components/LanguageBreakdown";
import CIAnalytics from "@/components/CIAnalytics";
import IssueMetrics from "@/components/IssueMetrics";
import StreakAtRiskBanner from "@/components/StreakAtRiskBanner";
import RepoAnalyticsExplorer from "@/components/repo-analytics/RepoAnalyticsExplorer";
import dynamic from "next/dynamic";
import WeeklySummaryCard from "@/components/WeeklySummaryCard";
import { AIMentorWidget } from "@/components/AIMentorWidget";
import ExportButton from "@/components/ExportButton";
import Link from "next/link";
import { ChevronRight } from "lucide-react";
import PersonalRecords from "@/components/PersonalRecords";
import LocalCodingTime from "@/components/LocalCodingTime";
import CodingTimeWidget from "@/components/CodingTimeWidget";
import RecentActivity from "@/components/RecentActivity";
import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import DashboardSSEProvider from "@/components/DashboardSSEProvider";
import StreakAtRiskBanner from "@/components/StreakAtRiskBanner";
import ThrottleBanner from "@/components/ThrottleBanner";
import CustomizableDashboard from "@/components/dashboard/CustomizableDashboard";

const SkeletonCard = () => (
<div
role="status"
aria-busy="true"
aria-live="polite"
className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm"
>
<div className="h-6 w-48 bg-[var(--card-muted)] rounded mb-4 animate-pulse" />
<div className="h-40 bg-[var(--card-muted)] rounded animate-pulse" />
</div>
);

const ContributionGraphSkeleton = () => (
<div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm">
<h2 className="text-lg font-semibold text-[var(--foreground)]">Your Commits</h2>
<div className="mt-3 h-40 rounded bg-[var(--card-muted)] animate-pulse" />
</div>
);

const PRMetricsSkeleton = () => (
<div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm">
<h2 className="text-lg font-semibold text-[var(--card-foreground)]">PR Analytics</h2>
<div className="mt-3 h-40 rounded bg-[var(--card-muted)] animate-pulse" />
</div>
);

const CodingActivityInsightsCard = dynamic(
() => import("@/components/CodingActivityInsightsCard"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

const FriendComparison = dynamic(
() => import("@/components/FriendComparison"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

const ActivityRingChart = dynamic(
() => import("@/components/ActivityRingChart"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

const ContributionGraph = dynamic(
() => import("@/components/ContributionGraph"),
{ ssr: false, loading: () => <ContributionGraphSkeleton /> },
);

const ContributionHeatmap = dynamic(
() => import("@/components/ContributionHeatmap"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

const PRMetrics = dynamic(() => import("@/components/PRMetrics"), {
ssr: false,
loading: () => <PRMetricsSkeleton />,
});

const PRBreakdownChart = dynamic(
() => import("@/components/PRBreakdownChart"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

const CommitTimeChart = dynamic(
() => import("@/components/CommitTimeChart"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

const PRReviewTrendChart = dynamic(
() => import("@/components/PRReviewTrendChart"),
{ ssr: false, loading: () => <SkeletonCard /> },
);

export default async function DashboardPage() {
const session = await getServerSession(authOptions);
if (!session) redirect("/");

return (
<DashboardSSEProvider>
<div className="min-h-screen bg-[var(--background)] px-4 py-8 text-[var(--foreground)] transition-colors sm:px-6 lg:px-8 max-w-[1600px] mx-auto">
<div className="min-h-screen bg-[var(--background)] px-3 py-5 text-[var(--foreground)] transition-colors sm:px-6 sm:py-8 lg:px-8 max-w-[1600px] mx-auto">
<DashboardHeader />

{/* Quick actions */}
<div className="mt-8 mb-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="mt-6 mb-6 sm:mt-8 sm:mb-8 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
{/* Left side actions */}
<div className="flex flex-wrap items-center gap-3 w-full sm:w-auto">
<Link
Expand All @@ -30,64 +117,153 @@ export default async function DashboardPage() {
>
Year in Code
</Link>

<Link
href="/dashboard/settings"
className="inline-flex w-full sm:w-auto justify-center items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-5 py-2.5 text-sm font-medium transition-all hover:bg-white/10 hover:scale-[1.02]"
>
Settings
</Link>
</div>

{/* Right side exports */}
<div className="w-full sm:w-auto">
<ExportButton />
</div>
</div>

<div className="space-y-4 mb-8">
<ThrottleBanner />
<div className="space-y-4">
<StreakAtRiskBanner />
</div>

{/* Hero Section */}
<section className="mt-8">
<TodayFocusHero userName={session.user?.name ?? null} />
</section>

<section className="mt-14">
<div className="relative overflow-hidden rounded-xl border border-[var(--border)] bg-gradient-to-r from-violet-950/20 via-indigo-950/10 to-transparent p-6 shadow-lg flex flex-col md:flex-row justify-between items-center gap-6">
<div className="space-y-2 max-w-xl">
<div className="flex items-center gap-2">
<span className="text-[10px] uppercase font-bold text-violet-400 tracking-wider px-2 py-0.5 rounded bg-violet-500/10 border border-violet-500/20">
New Feature
</span>
<span className="text-xs text-[var(--muted-foreground)]">
AI Resume Generator
</span>
{/* 1. OVERVIEW SECTION */}
<section className="mt-10 sm:mt-14 space-y-6">
<div className="flex items-center gap-3 border-b border-white/10 pb-4">
<div className="h-7 w-1.5 sm:h-8 rounded-full bg-[var(--accent)] shadow-[0_0_15px_var(--accent)]"></div>
<h2 className="text-xl sm:text-2xl font-bold tracking-tight">Overview</h2>
</div>
<div className="grid grid-cols-1 gap-6 w-full">
<WeeklySummaryCard />
</div>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6 w-full">
<div className="flex flex-col gap-6 w-full overflow-hidden">
<PersonalRecords />
</div>
<div className="flex flex-col gap-6 w-full h-full">
<AIMentorWidget />
</div>
</div>
</section>

{/* 2. ACTIVITY & CODING TIME */}
<section id="streaks" className="mt-10 sm:mt-14 space-y-6 scroll-mt-20 sm:scroll-mt-28">
<div className="flex items-center gap-3 border-b border-white/10 pb-4">
<div className="h-7 w-1.5 sm:h-8 rounded-full bg-emerald-500 shadow-[0_0_15px_rgba(16,185,129,0.5)]"></div>
<h2 className="text-xl sm:text-2xl font-bold tracking-tight">Activity & Coding</h2>
</div>

<div className="grid grid-cols-1 xl:grid-cols-3 gap-6 w-full">
<div className="xl:col-span-2 flex flex-col gap-6 w-full overflow-hidden">
<div className="w-full overflow-x-auto pb-2 -mx-3 px-3 sm:mx-0 sm:px-0">
<ContributionGraph />
</div>
<div className="w-full overflow-x-auto pb-2 -mx-3 px-3 sm:mx-0 sm:px-0">
<ContributionHeatmap />
</div>
<LazyWidget fallback={<SkeletonCard />}>
<ActivityRingChart />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<CodingActivityInsightsCard />
</LazyWidget>
</div>
<div className="flex flex-col gap-6 w-full overflow-hidden">
<StreakTracker />
<LocalCodingTime />
<CodingTimeWidget />
<LazyWidget fallback={<SkeletonCard />}>
<CommitTimeChart />
</LazyWidget>
</div>
</div>
</section>

<h3 className="text-lg font-bold text-[var(--foreground)]">
Generate an ATS-Friendly CV Backed by Your Real Code
</h3>
{/* 3. ANALYTICS & REPOSITORIES */}
<section id="pull-requests" className="mt-10 sm:mt-14 space-y-6 scroll-mt-20 sm:scroll-mt-28">
<div className="flex items-center gap-3 border-b border-white/10 pb-4">
<div className="h-7 w-1.5 sm:h-8 rounded-full bg-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.5)]"></div>
<h2 className="text-xl sm:text-2xl font-bold tracking-tight">Analytics & Repositories</h2>
</div>

<p className="text-xs text-[var(--muted-foreground)] leading-relaxed">
Analyze your GitHub contributions, merged PRs, and lines of code
changed to automatically generate professional bullet points for
your target roles.
</p>
</div>
{/* Repo Analytics Explorer spans full width */}
<div className="w-full overflow-hidden">
<LazyWidget fallback={<SkeletonCard />}>
<RepoAnalyticsExplorer />
</LazyWidget>
</div>

<Link
href="/dashboard/career-intelligence"
className="inline-flex items-center gap-1.5 rounded-lg bg-gradient-to-r from-violet-600 to-indigo-600 px-5 py-2.5 text-xs font-bold text-white shadow-md shadow-indigo-500/20 hover:scale-[1.03] transition-all whitespace-nowrap"
>
Build Resume
<ChevronRight className="h-4 w-4" />
</Link>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full">
<div className="flex flex-col gap-6 w-full overflow-hidden">
<PRMetrics />
<LazyWidget fallback={<SkeletonCard />}>
<PRBreakdownChart />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<PRReviewTrendChart />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<DiscussionsWidget />
</LazyWidget>
</div>
<div className="flex flex-col gap-6 w-full overflow-hidden">
<CommunityMetrics />
<LazyWidget fallback={<SkeletonCard />}>
<PinnedReposWidget />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<TopRepos />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<InactiveRepositoriesCard />
</LazyWidget>
</div>
</div>
</section>

<CustomizableDashboard />
{/* 4. GOALS & INSIGHTS */}
<section id="goals" className="mt-10 sm:mt-14 space-y-6 scroll-mt-20 sm:scroll-mt-28 mb-12">
<div className="flex items-center gap-3 border-b border-white/10 pb-4">
<div className="h-7 w-1.5 sm:h-8 rounded-full bg-purple-500 shadow-[0_0_15px_rgba(168,85,247,0.5)]"></div>
<h2 className="text-xl sm:text-2xl font-bold tracking-tight">Goals & Insights</h2>
</div>

<div className="grid grid-cols-1 xl:grid-cols-3 gap-6 w-full">
<div className="xl:col-span-2 flex flex-col gap-6 w-full overflow-hidden">
<LazyWidget fallback={<SkeletonCard />}>
<IssueMetrics />
</LazyWidget>
<GoalTracker />
<LazyWidget fallback={<SkeletonCard />}>
<RecentActivity />
</LazyWidget>
</div>
<div className="flex flex-col gap-6 w-full overflow-hidden">
<LazyWidget fallback={<SkeletonCard />}>
<CIAnalytics />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<LanguageBreakdown />
</LazyWidget>
<LazyWidget fallback={<SkeletonCard />}>
<FriendComparison />
</LazyWidget>
</div>
</div>
</section>
</div>
</DashboardSSEProvider>
);
}
}
Loading
Loading