Skip to content

Commit

Permalink
Merge pull request #88 from whyphi/feat/jwt-auth
Browse files Browse the repository at this point in the history
feat: Implement JWT Authentication Context
  • Loading branch information
jinyoungbang authored Mar 7, 2024
2 parents 6b573cf + 3d73eec commit e2685b4
Show file tree
Hide file tree
Showing 14 changed files with 995 additions and 381 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ jspm_packages
.node_repl_history
.next

.env.local
.env.local
.DS_Store
11 changes: 7 additions & 4 deletions app/admin/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { useRouter } from 'next/navigation'
import { Checkbox, Label } from 'flowbite-react';
import { useAuth } from "@/app/contexts/AuthContext";



Expand All @@ -22,6 +23,7 @@ const initialValues: FormData = {
};

export default function Create() {
const { token } = useAuth();
const router = useRouter();
const [formData, setFormData] = useState<FormData>(initialValues);
const [selectedDate, setSelectedDate] = useState(new Date());
Expand All @@ -40,6 +42,7 @@ export default function Create() {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/create`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(formDataWithDates),
Expand Down Expand Up @@ -136,19 +139,19 @@ export default function Create() {
const handleQuestionChange = (index: number, field: string, value: string) => {
const updatedQuestions = [...formData.questions];
const questionObj = updatedQuestions[index];

if (questionObj) {
// Ensure questionObj is defined
questionObj[field as keyof typeof questionObj] = value;

setFormData((prevData) => ({
...prevData,
questions: updatedQuestions,
}));
}
};



const renderInput = (
id: keyof FormData,
Expand Down
11 changes: 9 additions & 2 deletions app/admin/listing/[listingId]/[applicantId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ import ResponseCard from "@/components/admin/listing/ResponseCard";
import ApplicantInfoCard from "@/components/admin/listing/ApplicantInfoCard";
import ApplicantPDFViewer from "@/components/admin/listing/ApplicantPDFViewer";
import Loader from "@/components/Loader";
import { useAuth } from "@/app/contexts/AuthContext";

export default function ApplicantPage({ params }: { params: { applicantId: string } }) {
const { token } = useAuth();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [applicantData, setApplicantData] = useState<null | Applicant>(null);

useEffect(() => {
// Fetch listings data from your /listings API endpoint
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/applicant/${params.applicantId}`)
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/applicant/${params.applicantId}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data: Applicant) => {
setApplicantData(data);
Expand Down
14 changes: 11 additions & 3 deletions app/admin/listing/[listingId]/insights/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { Applicant } from "@/types/applicant";
import { PieChart, Pie, Tooltip, Label } from "recharts";
import { Table, Tabs } from 'flowbite-react';
import SummaryCard from "@/components/admin/listing/insights/SummaryCard";
import { useAuth } from "@/app/contexts/AuthContext";

import { FlowbiteTabTheme } from "flowbite-react";


export default function Insights({ params }: { params: { listingId: string } }) {
const { token } = useAuth();
const router = useRouter();
// dashboard : object containing data from backend (# applicants, average gpa, #1 major, avg gradYear, avg response length)
const [dashboard, setDashboard] = useState<Dashboard>({
Expand Down Expand Up @@ -41,10 +43,16 @@ export default function Insights({ params }: { params: { listingId: string } })

// matchingApplicants : list of applicants depending on which part of PieChart (if any) has been clicked
const [matchingApplicants, setMatchingApplicants] = useState<[] | Applicant[]>([]);

// Fetch insights data from your /listings API endpoint
useEffect(() => {
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/insights/listing/${params.listingId}`)
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/insights/listing/${params.listingId}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then(([dashboard, distribution]: [Dashboard, DistributionMetricsState]) => {
setDashboard(dashboard);
Expand Down Expand Up @@ -182,7 +190,7 @@ export default function Insights({ params }: { params: { listingId: string } })
"icon": "mr-2 h-5 w-5"
}
},
"tabpanel": "py-3"
"tabpanel": "py-3",
}

// if applicants data not yet received : produce loading screen
Expand Down
10 changes: 9 additions & 1 deletion app/admin/listing/[listingId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { Applicant } from "@/types/applicant";
import { useRouter } from "next/navigation";
import { Tabs, TabsRef, Table } from 'flowbite-react';
import { HiOutlineCollection, HiOutlineTable } from 'react-icons/hi';
import { useAuth } from "@/app/contexts/AuthContext";



export default function Listing({ params }: { params: { listingId: string } }) {
const router = useRouter();
const { token } = useAuth();
const [applicantData, setApplicantData] = useState<[] | [Applicant]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);

Expand All @@ -22,7 +24,13 @@ export default function Listing({ params }: { params: { listingId: string } }) {

useEffect(() => {
// Fetch listings data from your /listings API endpoint
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/applicants/${params.listingId}`)
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/applicants/${params.listingId}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data: [Applicant]) => {
setApplicantData(data)
Expand Down
12 changes: 10 additions & 2 deletions app/admin/members/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
"use client"
import React, { useState, useEffect } from "react";
import { Table, Progress } from 'flowbite-react';
import { Table } from 'flowbite-react';
import { Member } from "@/types/admin/members";
import { useAuth } from "@/app/contexts/AuthContext";

export default function Members() {

const [members, setMembers] = useState<Member[]>([]);

useEffect(() => {
const { token } = useAuth();
// Fetch listings data from your /listings API endpoint
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/members`)
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/members`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data: Member[]) => {
setMembers(data);
Expand Down
13 changes: 12 additions & 1 deletion app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import { useEffect, useState } from "react";
import ListingCard from "@/components/admin/ListingCard";
import Loader from "@/components/Loader";
import { useAuth } from "../contexts/AuthContext";



interface Listing {
listingId: string;
Expand All @@ -13,17 +16,25 @@ interface Listing {
}

export default function Admin() {
const { token } = useAuth();
const [isLoading, setIsLoading] = useState<boolean>(true);
const [listings, setListings] = useState<Listing[]>([]);

useEffect(() => {
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/listings`)
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/listings`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data: Listing[]) => {
setListings(data)
setIsLoading(false);
})
.catch((error) => console.error("Error fetching listings:", error));

}, []);

if (isLoading) {
Expand Down
10 changes: 9 additions & 1 deletion app/admin/settings/[listingId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation';
import { Listing } from "@/types/listing";
import { Button, Modal } from 'flowbite-react';
import CustomAlert from "@/components/admin/settings/CustomAlert";
import { useAuth } from "@/app/contexts/AuthContext";


interface FormData {
Expand All @@ -22,6 +23,7 @@ const initialValues: FormData = {
};

export default function ListingSettings({ params }: { params: { listingId: string } }) {
const { token } = useAuth();
const router = useRouter();
const [openModal, setOpenModal] = useState(false);
const [listingData, setListingData] = useState<Listing | null>(null);
Expand All @@ -37,7 +39,13 @@ export default function ListingSettings({ params }: { params: { listingId: strin

useEffect(() => {
// Fetch listing data from your /listings API endpoint
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/listings/${params.listingId}`)
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/listings/${params.listingId}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((data: Listing) => {
// Set the form data with the fetched listing data
Expand Down
10 changes: 10 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,17 @@ const authOptions: AuthOptions = {
}
return true
},
async jwt({ token, account, profile }) {
// Persist the OAuth access_token and or the user id to the token right after signin
// if (account) {
// token.accessToken = account.access_token
// token.id = profile.id
// }
token.test = "sdmfksmk"
return token
},
async session({ session, user, token }) {
session.token = token;
return session
},

Expand Down
46 changes: 46 additions & 0 deletions app/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { getSession } from 'next-auth/react';
import jwt from 'jsonwebtoken';

interface AuthContextProps {
token: string | null;
setToken: (token: string | null) => void;
isLoading: boolean;
}

const AuthContext = createContext<AuthContextProps | undefined>(undefined);

export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [token, setToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
getSession().then((session) => {
if (session) {
const signedToken = jwt.sign(session.token, `${process.env.NEXT_PUBLIC_JWT_SECRET}`, {
algorithm: 'HS256',
});
setToken(signedToken);
}
setIsLoading(false);
});
}, []);

const handleSetToken = (newToken: string | null) => {
setToken(newToken);
};

return (
<AuthContext.Provider value={{ token, setToken: handleSetToken, isLoading }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
8 changes: 6 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use client'
import './globals.css'
import { SessionProvider } from "next-auth/react";
import { AuthProvider } from './contexts/AuthContext';


export default function RootLayout({
children,
Expand All @@ -11,10 +13,12 @@ export default function RootLayout({
<html lang="en">
<body className="min-w-screen">
<SessionProvider>
{children}
<AuthProvider>
{children}
</AuthProvider>
</SessionProvider>
</body>
</html>
</html >

)
}
Loading

0 comments on commit e2685b4

Please sign in to comment.