Skip to content

Commit 045c1cb

Browse files
authored
Merge pull request #25 from Iank-code/feat-protect-routes
feat: protect routes
2 parents 3f0a670 + e82561c commit 045c1cb

File tree

8 files changed

+119
-92
lines changed

8 files changed

+119
-92
lines changed

src/app/(protected)/home/page.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import React, { useEffect, useState } from "react";
33
import axios from "axios";
44
import SearchWrapper from "@/components/wrappers/searchWrapper";
5-
import { user } from "../../../../types";
5+
import { album, user } from "../../../../types";
66
import UserCard from "@/components/card/userCard";
77

88
export default function Users() {
9-
const [numberOfAlbums, setNumberOfAlbums] = useState<any>([]);
9+
const [albums, setAlbums] = useState<album[]>([]);
1010
const [users, setUsers] = useState<user[]>([]);
1111

1212
useEffect(() => {
@@ -22,14 +22,10 @@ export default function Users() {
2222
setUsers(usersResponse.data);
2323

2424
// Fetch albums for each user
25-
const albumsPromises = usersResponse.data.map((user: user) =>
26-
axios.get(
27-
`https://jsonplaceholder.typicode.com/albums?userId=${user.id}`
28-
)
25+
const albumsPromises = await axios.get(
26+
"https://jsonplaceholder.typicode.com/albums"
2927
);
30-
const albumsResponses = await Promise.all(albumsPromises);
31-
const albumsData = albumsResponses.map((response) => response.data);
32-
setNumberOfAlbums(albumsData);
28+
setAlbums(albumsPromises.data);
3329
} catch (error) {
3430
console.log(error);
3531
}
@@ -46,13 +42,18 @@ export default function Users() {
4642

4743
<div className="grid grid-cols-5 gap-7 px-10 py-20 max-[600px]:grid-cols-2 max-[600px]:px-3 max-[600px]:gap-3 max-[930px]:grid-cols-3">
4844
{users &&
49-
users.map((user: user, index: number) => (
50-
<UserCard
51-
key={user.id}
52-
userData={user}
53-
albums={numberOfAlbums[user.id]}
54-
/>
55-
))}
45+
users.map((user: user, index: number) => {
46+
const numberOfAlbums =
47+
albums && albums.filter((data: album) => data.userId === user.id);
48+
console.log(numberOfAlbums.length);
49+
return (
50+
<UserCard
51+
key={user.id}
52+
userData={user}
53+
albums={numberOfAlbums.length}
54+
/>
55+
);
56+
})}
5657
</div>
5758
</div>
5859
);

src/app/(protected)/layout.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client"
2+
3+
import Navbar from "@/components/navbar/Navbar";
4+
import { SessionProvider, useSession } from "next-auth/react";
5+
import { redirect } from "next/navigation";
6+
7+
export default function ProtectedLayout({
8+
children,
9+
}: Readonly<{ children: React.ReactNode }>) {
10+
const { status } = useSession()
11+
12+
if (status === "unauthenticated"){
13+
redirect("/")
14+
}
15+
return (
16+
<html>
17+
<body>
18+
<SessionProvider refetchInterval={2 * 60}>
19+
<Navbar />
20+
{children}
21+
</SessionProvider>
22+
</body>
23+
</html>
24+
);
25+
}

src/app/layout.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import "./globals.css";
44
import { Provider } from "react-redux";
55
import store from "@/lib/store";
66
import { SessionProvider } from "next-auth/react";
7-
import Navbar from "@/components/navbar/Navbar";
87

98
const inter = Inter({ subsets: ["latin"] });
109
import "./globals.css";
@@ -17,7 +16,6 @@ export default function RootLayout({
1716
<body className={inter.className}>
1817
<Provider store={store}>
1918
<SessionProvider refetchInterval={2 * 60}>
20-
<Navbar />
2119
{children}
2220
</SessionProvider>
2321
</Provider>

src/app/page.tsx

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRouter } from "next/navigation";
33
import Image from "next/image";
44
import Link from "next/link";
55
import AuthenticateButton from "@/components/authenticateButton/AuthenticateButton";
6+
import Navbar from "@/components/navbar/Navbar";
67

78
const instructionData = [
89
{
@@ -50,73 +51,76 @@ const copyToClipboardFunction = (text: string) => {
5051
export default function Home() {
5152
const router = useRouter();
5253
return (
53-
<main className="flex min-h-screen py-3 gap-8 justify-start px-10 max-[600px]:flex-col max-[600px]:px-5">
54-
<div className="flex flex-col w-1/2 gap-4 max-[600px]:w-full">
55-
<div>
56-
<h2 className="font-bold text-2xl">
57-
Savannah Informatics - Frontend Role
58-
</h2>
59-
<p>
60-
Welcome to my submission for the Savannah Informatics frontend
61-
engineering assessment. This project demonstrates my skills in
62-
modern web frontend development, fulfilling the requirements
63-
outlined in the assessment brief. The completed project is available
64-
in the public repository linked below.
65-
</p>
66-
<h5 className="font-bold">
67-
Repository Link:{" "}
68-
<Link
69-
href="https://github.com/Iank-code/SIL-2.0"
70-
target="_blank"
71-
className="text-blue-600 hover:underline"
72-
>
73-
Github Repository
74-
</Link>
75-
</h5>
76-
</div>
54+
<div className="flex flex-col">
55+
<Navbar />
56+
<main className="flex min-h-screen py-3 gap-8 justify-start px-10 max-[600px]:flex-col max-[600px]:px-5">
57+
<div className="flex flex-col w-1/2 gap-4 max-[600px]:w-full">
58+
<div>
59+
<h2 className="font-bold text-2xl">
60+
Savannah Informatics - Frontend Role
61+
</h2>
62+
<p>
63+
Welcome to my submission for the Savannah Informatics frontend
64+
engineering assessment. This project demonstrates my skills in
65+
modern web frontend development, fulfilling the requirements
66+
outlined in the assessment brief. The completed project is
67+
available in the public repository linked below.
68+
</p>
69+
<h5 className="font-bold">
70+
Repository Link:{" "}
71+
<Link
72+
href="https://github.com/Iank-code/SIL-2.0"
73+
target="_blank"
74+
className="text-blue-600 hover:underline"
75+
>
76+
Github Repository
77+
</Link>
78+
</h5>
79+
</div>
7780

78-
<div>
79-
<h2 className="font-bold text-2xl">Instructions</h2>
80-
<ol className="list-decimal">
81-
{instructionData.map((data, index) => (
82-
<div key={index}>
83-
<li>{data.name}</li>
84-
<code
85-
className="font-bold bg-black text-white p-1 cursor-pointer"
86-
onClick={() => copyToClipboardFunction(data.value)}
87-
>
88-
{data.value}
89-
</code>
90-
</div>
91-
))}
92-
</ol>
93-
</div>
81+
<div>
82+
<h2 className="font-bold text-2xl">Instructions</h2>
83+
<ol className="list-decimal">
84+
{instructionData.map((data, index) => (
85+
<div key={index}>
86+
<li>{data.name}</li>
87+
<code
88+
className="font-bold bg-black text-white p-1 cursor-pointer"
89+
onClick={() => copyToClipboardFunction(data.value)}
90+
>
91+
{data.value}
92+
</code>
93+
</div>
94+
))}
95+
</ol>
96+
</div>
9497

95-
<div>
96-
<h2 className="font-bold text-2xl">Summary</h2>
97-
<p>
98-
This project features a fully responsive design for seamless use
99-
across devices and data fetching from a mock API. It also includes
100-
Google authentication for smooth login and logout. The code follows
101-
best practices in organization, commenting, and version control,
102-
with ESLint ensuring quality. The project is well-documented with a
103-
comprehensive README containing setup instructions, a project
104-
description, and usage guidelines.
105-
</p>
98+
<div>
99+
<h2 className="font-bold text-2xl">Summary</h2>
100+
<p>
101+
This project features a fully responsive design for seamless use
102+
across devices and data fetching from a mock API. It also includes
103+
Google authentication for smooth login and logout. The code
104+
follows best practices in organization, commenting, and version
105+
control, with ESLint ensuring quality. The project is
106+
well-documented with a comprehensive README containing setup
107+
instructions, a project description, and usage guidelines.
108+
</p>
109+
</div>
106110
</div>
107-
</div>
108111

109-
<div className="flex flex-col">
110-
<Image
111-
src={"agreement.svg"}
112-
alt="agreement.svg"
113-
width={400}
114-
height={400}
115-
/>
116-
<div></div>
117-
<AuthenticateButton />
118-
<Link href="#">Don&apos;t have an account? Create account</Link>
119-
</div>
120-
</main>
112+
<div className="flex flex-col">
113+
<Image
114+
src={"agreement.svg"}
115+
alt="agreement.svg"
116+
width={400}
117+
height={400}
118+
/>
119+
<div></div>
120+
<AuthenticateButton />
121+
<Link href="#">Don&apos;t have an account? Create account</Link>
122+
</div>
123+
</main>
124+
</div>
121125
);
122126
}

src/components/authenticateButton/AuthenticateButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button } from "../ui/button";
55
export default function AuthenticateButton() {
66
const { data: session, status } = useSession();
77
return (
8-
<Button onClick={() => signIn("google")}>
8+
<Button onClick={() => signIn("google", { callbackUrl: "/home" })}>
99
{status === "authenticated"
1010
? `Signed in as ${session.user!.email}`
1111
: "Login With Google"}

src/components/card/userCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ interface userData {
99
}
1010
interface propType {
1111
userData: userData;
12-
albums?: any;
12+
albums?: number;
1313
}
1414
// #DAE7FF4D
1515
export default function UserCard({ userData, albums }: propType) {
16+
console.log(albums);
1617
return (
1718
<Link
1819
href={`/user/${userData.id}`}
@@ -27,7 +28,7 @@ export default function UserCard({ userData, albums }: propType) {
2728
{userData.email}
2829
</span>
2930
<span className="text-md max-[600px]:text-sm">
30-
{albums && albums.length} albums
31+
{albums && albums} albums
3132
</span>
3233
</div>
3334
</Link>

src/components/navbar/Navbar.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useSession, signOut } from "next-auth/react";
33
import AuthenticateButton from "../authenticateButton/AuthenticateButton";
44
import { Button } from "../ui/button";
55
import { AvatarProfile } from "../avator/avator";
6+
import Link from "next/link";
67

78
export default function Navbar() {
89
const { data: session, status } = useSession();
@@ -13,13 +14,10 @@ export default function Navbar() {
1314
</span>
1415
{session ? (
1516
<div className="flex items-center gap-4">
17+
<Link href="/home"> Home </Link>
1618
<AvatarProfile name={`${session.user!.name}`} />
1719
<h4 className="max-[600px]:hidden">{session.user!.name}</h4>
18-
<Button
19-
onClick={() => signOut({callbackUrl: "/"})}
20-
>
21-
Logout
22-
</Button>
20+
<Button onClick={() => signOut({ callbackUrl: "/" })}>Logout</Button>
2321
</div>
2422
) : (
2523
<div>

types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ export interface user {
1111
email: string;
1212
}
1313

14-
export interface album {
14+
export type album = {
1515
id: number;
1616
title: string;
1717
userId: number;
18-
}
18+
};
1919

2020
export interface userPropType {
2121
user: user;

0 commit comments

Comments
 (0)