Skip to content

Commit b34247b

Browse files
committed
feat: improve authentication, with protected route, refactor code, and create comment doc
1 parent 8da73f0 commit b34247b

23 files changed

Lines changed: 224 additions & 103 deletions

File tree

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# NEXT_PUBLIC_API_BASE_URL=https://lms-be-development.hammercode.org/api/v1
2+
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1
3+
NODE_ENV=development

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ yarn-debug.log*
2626
yarn-error.log*
2727

2828
# local env files
29+
.env
2930
.env*.local
3031

3132
# vercel

next.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ const nextConfig = {
1515
protocol: "https",
1616
hostname: "lms-be-development.hammercode.org",
1717
},
18+
{
19+
protocol: "http",
20+
port: "8000",
21+
hostname: "localhost",
22+
},
1823
],
1924
},
2025
trailingSlash: true,

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"cookie": "^1.0.2",
3838
"embla-carousel-autoplay": "^8.2.0",
3939
"embla-carousel-react": "^8.2.0",
40+
"js-cookie": "^3.0.5",
4041
"jwt-decode": "^4.0.0",
4142
"lucide-react": "^0.536.0",
4243
"motion": "^12.7.4",
@@ -63,6 +64,7 @@
6364
"@testing-library/jest-dom": "^6.4.8",
6465
"@testing-library/react": "^16.0.0",
6566
"@testing-library/user-event": "^14.5.2",
67+
"@types/js-cookie": "^3.0.6",
6668
"@types/node": "^20",
6769
"@types/react": "19.1.5",
6870
"@types/react-dom": "19.1.5",

pnpm-lock.yaml

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/[locale]/(public)/(user)/layout.tsx

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,46 @@ import { Calendar1, User } from "lucide-react";
66
import { Separator } from "@/components/ui/Separator";
77
import { Link } from "@/lib/navigation";
88
import { useAuthUser } from "@/components/hooks/UseAuthUser";
9+
import ProtectedRoute from "@/components/layout/ProtectedRoute";
910

10-
export default function UserLayout({ children }: { children: React.ReactNode }) {
11+
interface UserLayoutProps {
12+
children: React.ReactNode;
13+
}
14+
15+
export default function UserLayout({ children }: UserLayoutProps) {
1116
const { user } = useAuthUser();
17+
1218
return (
13-
<section className="container mx-auto px-5 pt-24 pb-28">
14-
<div className="grid grid-cols-5 gap-8">
15-
<aside className="fixed right-0 bottom-0 left-0 col-span-1 mt-8 flex w-full flex-col justify-between gap-4 self-start rounded-lg bg-white lg:sticky lg:top-24 lg:flex-col lg:justify-start lg:bg-transparent dark:bg-slate-950">
16-
<div className="flex items-center gap-4">
17-
<Avatar className="h-12 w-12">
18-
<AvatarImage src="" />
19-
<AvatarFallback>US</AvatarFallback>
20-
</Avatar>
21-
<div>
22-
<p className="text-muted-foreground text-sm">Hello</p>
23-
<p className="font-semibold">{user?.username}</p>
19+
<ProtectedRoute>
20+
<section className="container mx-auto px-5 pt-24 pb-28">
21+
<div className="grid grid-cols-5 gap-8">
22+
<aside className="fixed right-0 bottom-0 left-0 col-span-1 mt-8 flex w-full flex-col justify-between gap-4 self-start rounded-lg bg-white lg:sticky lg:top-24 lg:flex-col lg:justify-start lg:bg-transparent dark:bg-slate-950">
23+
<div className="flex items-center gap-4">
24+
<Avatar className="h-12 w-12">
25+
<AvatarImage src="" alt="profile" />
26+
<AvatarFallback>US</AvatarFallback>
27+
</Avatar>
28+
<div>
29+
<p className="text-muted-foreground text-sm">Hello</p>
30+
<p className="font-semibold">{user?.username}</p>
31+
</div>
2432
</div>
25-
</div>
26-
27-
<Separator />
28-
29-
<nav className="flex flex-col gap-3 text-sm">
30-
<Link href="/my-events" className="flex items-center gap-2 text-gray-800 transition hover:text-blue-600">
31-
<Calendar1 size={16} /> My Events
32-
</Link>
33-
<Link href="/profile" className="flex items-center gap-2 text-gray-800 transition hover:text-blue-600">
34-
<User size={16} /> Profil
35-
</Link>
36-
</nav>
37-
</aside>
38-
39-
<div className="col-span-4">{children}</div>
40-
</div>
41-
</section>
33+
34+
<Separator />
35+
36+
<nav className="flex flex-col gap-3 text-sm">
37+
<Link href="/my-events" className="flex items-center gap-2 text-gray-800 transition hover:text-blue-600">
38+
<Calendar1 size={16} /> My Events
39+
</Link>
40+
<Link href="/profile" className="flex items-center gap-2 text-gray-800 transition hover:text-blue-600">
41+
<User size={16} /> Profil
42+
</Link>
43+
</nav>
44+
</aside>
45+
46+
<div className="col-span-4">{children}</div>
47+
</div>
48+
</section>
49+
</ProtectedRoute>
4250
);
4351
}

src/app/[locale]/(public)/(user)/my-events/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MyEventPage } from "@/features/events";
1+
import MyEventPage from "@/features/user/my-events";
22

33
interface MyEventsPageProps {
44
searchParams: Promise<{
@@ -10,5 +10,5 @@ interface MyEventsPageProps {
1010
export default async function MyEventsPage({ searchParams }: MyEventsPageProps) {
1111
const { page, limit } = await searchParams;
1212

13-
return <MyEventPage page={Number(page) || 1} perPage={Number(limit) || 10} />;
13+
return <MyEventPage page={Number(page)} perPage={Number(limit)} />;
1414
}

src/app/[locale]/(public)/(user)/profile/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import ProfilePage from "@/features/users/profile";
1+
import ProfilePage from "@/features/user/profile";
22

33
export default async function Profile() {
44
return <ProfilePage />;

src/app/api/login/route.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { authService } from "@/services/auth";
21
import { AuthJwtPayload } from "@/types";
2+
import axios from "axios";
33
import { jwtDecode } from "jwt-decode";
44
import { NextResponse } from "next/server";
55

66
export async function POST(request: Request) {
77
try {
88
const body = await request.json();
99
const { email, password } = body;
10-
const token = await authService.getToken({ email, password });
10+
const baseURL = process.env.NEXT_PUBLIC_API_BASE_URL;
11+
const token = await axios.post(`${baseURL}/auth/login`, { email, password }).then((res) => res.data.data);
12+
1113
const decoded = jwtDecode<AuthJwtPayload>(token);
1214

1315
const res = NextResponse.json({
@@ -22,7 +24,7 @@ export async function POST(request: Request) {
2224
const now = Math.floor(Date.now() / 1000);
2325
const maxAge = decoded.exp ? decoded.exp - now : undefined;
2426
res.cookies.set("token", token, {
25-
httpOnly: true,
27+
// httpOnly: true,
2628
secure: process.env.NODE_ENV === "production",
2729
maxAge,
2830
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
3+
import { useAuthUser } from "@/components/hooks/UseAuthUser";
4+
import { useRouter } from "@/lib/navigation";
5+
import { useEffect } from "react";
6+
7+
interface ProtectedRouteProps {
8+
children: React.ReactNode;
9+
}
10+
11+
/**
12+
* Protects a route by checking if the user is authenticated
13+
*/
14+
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
15+
const { isAuthenticated } = useAuthUser();
16+
const router = useRouter();
17+
18+
useEffect(() => {
19+
if (!isAuthenticated) {
20+
router.replace("/sign-in");
21+
}
22+
}, [isAuthenticated]);
23+
24+
if (!isAuthenticated) {
25+
return null;
26+
}
27+
28+
return children;
29+
}

0 commit comments

Comments
 (0)