Skip to content
Closed
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
84 changes: 84 additions & 0 deletions src/app/api/auth/refresh/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { NextRequest, NextResponse } from "next/server";
import {
ACCESS_TOKEN_MAX_AGE,
REFRESH_TOKEN_MAX_AGE,
USE_SECURE_COOKIES,
createAccessToken,
createRefreshToken,
getTokenCookieName,
verifyRefreshToken,
} from "@/lib/auth-tokens";

function unauthorizedResponse() {
const response = NextResponse.json(
{ error: "Invalid refresh token" },
{ status: 401 }
);

response.cookies.set({
name: getTokenCookieName("access"),
value: "",
maxAge: 0,
path: "/",
});
response.cookies.set({
name: getTokenCookieName("refresh"),
value: "",
maxAge: 0,
path: "/",
});

return response;
}

export async function POST(req: NextRequest) {
const refreshToken = req.cookies.get(getTokenCookieName("refresh"))?.value;
if (!refreshToken) {
return unauthorizedResponse();
}

let payload;
try {
payload = verifyRefreshToken(refreshToken);
} catch {
return unauthorizedResponse();
}

const accessToken = createAccessToken({
githubId: payload.githubId,
githubLogin: payload.githubLogin,
});
const newRefreshToken = createRefreshToken({
githubId: payload.githubId,
githubLogin: payload.githubLogin,
});

const response = NextResponse.json({
ok: true,
accessTokenExpiresIn: ACCESS_TOKEN_MAX_AGE,
});

response.cookies.set({
name: getTokenCookieName("access"),
value: accessToken,
httpOnly: true,
sameSite: "lax",
secure: USE_SECURE_COOKIES,
path: "/",
maxAge: ACCESS_TOKEN_MAX_AGE,
});
response.cookies.set({
name: getTokenCookieName("refresh"),
value: newRefreshToken,
httpOnly: true,
sameSite: "lax",
secure: USE_SECURE_COOKIES,
path: "/",
maxAge: REFRESH_TOKEN_MAX_AGE,
});

return response;
}



64 changes: 64 additions & 0 deletions src/app/api/auth/token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import {
ACCESS_TOKEN_MAX_AGE,
REFRESH_TOKEN_MAX_AGE,
USE_SECURE_COOKIES,
createAccessToken,
createRefreshToken,
getTokenCookieName,
} from "@/lib/auth-tokens";

/**
* POST /api/auth/token
*
* This endpoint allows users to exchange their existing NextAuth session
* for the parallel JWT authentication tokens. This is intended for
* setting up access for third-party tools, CLI, or mobile applications.
*/
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions);

if (!session || !session.githubId || !session.githubLogin) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const accessToken = createAccessToken({
githubId: session.githubId,
githubLogin: session.githubLogin,
});

const refreshToken = createRefreshToken({
githubId: session.githubId,
githubLogin: session.githubLogin,
});

const response = NextResponse.json({
ok: true,
message: "Tokens generated successfully",
accessTokenExpiresIn: ACCESS_TOKEN_MAX_AGE,
});

response.cookies.set({
name: getTokenCookieName("access"),
value: accessToken,
httpOnly: true,
sameSite: "lax",
secure: USE_SECURE_COOKIES,
path: "/",
maxAge: ACCESS_TOKEN_MAX_AGE,
});

response.cookies.set({
name: getTokenCookieName("refresh"),
value: refreshToken,
httpOnly: true,
sameSite: "lax",
secure: USE_SECURE_COOKIES,
path: "/",
maxAge: REFRESH_TOKEN_MAX_AGE,
});

return response;
}
25 changes: 21 additions & 4 deletions src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,38 @@ import Link from "next/link";
import PersonalRecords from "@/components/PersonalRecords";
import LocalCodingTime from "@/components/LocalCodingTime";
import RecentActivity from "@/components/RecentActivity";

import { authOptions } from "@/lib/auth";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";

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

if (!session) {
redirect("/");
}

if (session.githubId && session.githubLogin) {
// Note: JWT token generation for third-party clients (mobile apps, CLI, etc.)
// is available via the /api/auth/token endpoint.
}

return (
<div className="min-h-screen bg-[var(--background)] p-4 md:p-8 text-[var(--foreground)] transition-colors">
<DashboardHeader />

<div className="mb-6 flex justify-end items-center gap-2">
<Link
href="/dashboard/settings"
className="rounded-lg border border-[var(--border)] bg-[var(--control)] px-4 py-2 text-sm text-[var(--foreground)] hover:opacity-90 transition-opacity min-w-[140px] flex items-center justify-center"
>
Settings
</Link>

<ExportButton />
</div>

<StreakAtRiskBanner />

<div className="mb-6">
Expand All @@ -54,13 +66,15 @@ export default async function DashboardPage() {
<PersonalRecords />
</div>

{/* Row 1: Contribution graph + Streak + Local Coding Time */}
{/* Row 1 */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<ContributionGraph />

<div className="mt-6">
<ContributionHeatmap />
</div>

<div className="mt-6">
<FriendComparison />
</div>
Expand Down Expand Up @@ -92,19 +106,21 @@ export default async function DashboardPage() {
<PRReviewTrendChart />
</div>

{/* Row 3: Issue metrics + CI analytics */}
{/* Row 3 */}
<div className="mt-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
<IssueMetrics />
</div>

<CIAnalytics />
</div>

{/* Row 4: Pinned repositories */}
{/* Row 4 */}
<div className="mt-6">
<PinnedRepos />
</div>


{/* Row 5: Inactive repository reminder */}
<div className="mt-6">
<InactiveRepositoriesCard />
Expand All @@ -124,3 +140,4 @@ export default async function DashboardPage() {
</div>
);
}

1 change: 1 addition & 0 deletions src/app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,4 @@ export default async function LeaderboardPage({
</main>
);
}

12 changes: 6 additions & 6 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,23 @@ export default function Footer() {
className="transition-colors hover:text-[var(--card-foreground)]"
href="https://github.com/Priyanshu-byte-coder/devtrack/discussions"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
Discussions
</a>
<a
className="transition-colors hover:text-[var(--card-foreground)]"
href="https://github.com/Priyanshu-byte-coder/devtrack/issues"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
Issues
</a>
<a
className="transition-colors hover:text-[var(--card-foreground)]"
href="https://github.com/Priyanshu-byte-coder/devtrack"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
GitHub Repository
</a>
Expand All @@ -79,23 +79,23 @@ export default function Footer() {
className="transition-colors hover:text-[var(--card-foreground)]"
href="https://www.linkedin.com/in/priyanshu-doshi-21a54230a/"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
LinkedIn
</a>
<a
className="transition-colors hover:text-[var(--card-foreground)]"
href="https://github.com/Priyanshu-byte-coder"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
GitHub
</a>
<a
className="transition-colors hover:text-[var(--card-foreground)]"
href="https://portfolio-eta-gilt-84.vercel.app/"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
Portfolio
</a>
Expand Down
Loading
Loading