Skip to content

Commit

Permalink
Merge pull request #82 from CS3219-AY2425S1/shishir/fix-fe-authentica…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
shishirbychapur authored Oct 5, 2024
2 parents cd35fb9 + 7c165b1 commit ae45630
Show file tree
Hide file tree
Showing 24 changed files with 552 additions and 291 deletions.
20 changes: 8 additions & 12 deletions frontend/components/account/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ import { InputField, OptionsField } from '../customs/custom-input'
import validateInput, { initialFormValues } from '@/util/input-validation'

import CustomDialogWithButton from '../customs/custom-dialog'
import { toast } from 'sonner'
import { useMemo, useState } from 'react'
import { updateProfile } from '@/services/user-service-api'
import { Proficiency } from '@repo/user-types'
import React from 'react'
import { toast } from 'sonner'
import { updateProfile } from '@/services/user-service-api'
import { useSession } from 'next-auth/react'
import { useState } from 'react'

function Profile() {
const defaultUsername: string = useMemo(() => {
if (typeof window !== 'undefined') {
return sessionStorage.getItem('username') ?? ''
}
return ''
}, [])
const { data: session, update } = useSession()
const defaultUsername = session?.user.username ?? ''
const [formValues, setFormValues] = useState({ ...initialFormValues, username: defaultUsername })
const [formErrors, setFormErrors] = useState({ ...initialFormValues, proficiency: '' })
const [isDialogOpen, toggleDialogOpen] = useState(false)
Expand All @@ -34,18 +31,17 @@ function Profile() {
toggleDialogOpen(false)
const userData = { username: formValues.username, proficiency: formValues.proficiency }
try {
const response = await updateProfile(sessionStorage.getItem('id') ?? '', userData)
const response = await updateProfile(session?.user.id ?? '', userData)
if (response) {
const proficiency = Object.values(Proficiency).includes(response.proficiency as Proficiency)
? response.proficiency
: Proficiency.BEGINNER

sessionStorage.setItem('username', response.username ?? '')
setFormValues({
...formValues,
username: response.username ?? '',
proficiency: proficiency,
})
update({ ...session, user: { ...session?.user, username: response.username, role: response.role } })
toast.success('Profile has been updated successfully.')
}
} catch (error) {
Expand Down
30 changes: 11 additions & 19 deletions frontend/components/account/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { deleteAccount, updatePasswordRequest } from '@/services/user-service-api'
import { tokenState, userState } from '@/atoms/auth'
import { useMemo, useState } from 'react'
import validateInput, { initialFormValues } from '@/util/input-validation'

import CustomDialogWithButton from '../customs/custom-dialog'
import { InputField } from '../customs/custom-input'
import React from 'react'
import { toast } from 'sonner'
import usePasswordToggle from '../../hooks/UsePasswordToggle'
import { useMemo, useState } from 'react'
import { deleteAccount, updatePasswordRequest } from '@/services/user-service-api'
import React from 'react'
import { useSetRecoilState } from 'recoil'
import { tokenState, userState } from '@/atoms/auth'
import { useRouter } from 'next/router'
import { useSession } from 'next-auth/react'
import { useSetRecoilState } from 'recoil'

function Setting() {
const route = useRouter()
const { data: session, update } = useSession()
const setIsAuth = useSetRecoilState(userState)
const setIsValid = useSetRecoilState(tokenState)
const defaultEmail: string = useMemo(() => {
if (typeof window !== 'undefined') {
return sessionStorage.getItem('email') ?? ''
}
return ''
}, [])
const userId: string = useMemo(() => {
if (typeof window !== 'undefined') {
return sessionStorage.getItem('id') ?? ''
}
return ''
}, [])
const defaultEmail = session?.user.email ?? ''
const userId = session?.user.id ?? ''
const [passwordInputType, passwordToggleIcon] = usePasswordToggle()
const [confirmPasswordInputType, confirmPasswordToggleIcon] = usePasswordToggle()

Expand All @@ -44,9 +36,10 @@ function Setting() {
// Submit handler
const handleFormSubmit = async () => {
try {
await updatePasswordRequest({ password: formValues.password }, userId)
const response = await updatePasswordRequest({ password: formValues.password }, userId)
setIsFormSubmit(true)
toggleUpdateDialogOpen(false)
update({ ...session, user: response })
toast.success('Profile has been updated successfully.')
setFormValues({ ...initialFormValues, email: defaultEmail })
} catch (error) {
Expand Down Expand Up @@ -84,7 +77,6 @@ function Setting() {
setIsAuth(false)
setIsValid(false)
toggleDeleteDialogOpen(false)
sessionStorage.clear()
route.push('/auth')
toast.success('Successfully Delete Account')
}
Expand Down
42 changes: 13 additions & 29 deletions frontend/components/auth/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
'use client'

import { tokenState, userState } from '@/atoms/auth'
import validateInput, { initialFormValues } from '@/util/input-validation'

import { Button } from '../ui/button'
import { InputField } from '../customs/custom-input'
import { PasswordReset } from './PasswordReset'
import React from 'react'
import { loginRequest } from '@/services/user-service-api'
import { signIn } from 'next-auth/react'
import { toast } from 'sonner'
import usePasswordToggle from '../../hooks/UsePasswordToggle'
import { useRouter } from 'next/router'
import { useSetRecoilState } from 'recoil'
import { useState } from 'react'

export default function Login() {
const [formValues, setFormValues] = useState({ ...initialFormValues })
const [formErrors, setFormErrors] = useState({ ...initialFormValues, proficiency: '' })
const [passwordInputType, passwordToggleIcon] = usePasswordToggle()
const setIsAuth = useSetRecoilState(userState)
const setIsValid = useSetRecoilState(tokenState)

const router = useRouter()

const handleFormChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const { id, value } = e.target
setFormValues({ ...formValues, [id]: value })
}

const router = useRouter()

const onLogin = async () => {
const isTest = {
email: true,
Expand All @@ -43,28 +38,17 @@ export default function Login() {
setFormErrors(errors)

if (isValid) {
const requestBody = {
usernameOrEmail: formValues.email,
const result = await signIn('credentials', {
redirect: false,
username: formValues.email,
password: formValues.loginPassword,
}
try {
const res = await loginRequest(requestBody)
if (res) {
sessionStorage.setItem('id', res.id)
sessionStorage.setItem('username', res.username)
sessionStorage.setItem('email', res.email)
sessionStorage.setItem('TTL', new Date().toString())
sessionStorage.setItem('isAuth', 'true')
sessionStorage.setItem('role', res.role)
setIsAuth(true)
setIsValid(true)
router.push('/')
toast.success('Logged in successfully')
}
} catch (error) {
if (error instanceof Error) {
toast.error(error.message)
}
})
if (result?.error) {
toast.error('Login failed. Please try again')
return
} else {
toast.success('Logged in successfully')
router.push('/')
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions frontend/components/customs/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'

export default function Loading() {
return (
<div className="flex items-center justify-center">
<svg
style={{ marginTop: '30vh' }}
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 24 24"
fill="none"
stroke="rgb(147 51 234)"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={'animate-spin'}
>
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
</svg>
</div>
)
}
42 changes: 4 additions & 38 deletions frontend/components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,20 @@
'use client'

import { userState, tokenState } from '@/atoms/auth'
import { NavBar } from '@/components/layout/navbar'
import { inter } from '@/styles/fonts'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useRecoilState } from 'recoil'
import React from 'react'
import { inter } from '@/styles/fonts'
import { useSession } from 'next-auth/react'

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const router = useRouter()
const { pathname } = router
const [isAuth, setIsAuth] = useRecoilState(userState)
const [isValidToken, setIsValidToken] = useRecoilState(tokenState)

const isValid = () => {
const ttl = new Date(sessionStorage.getItem('TTL') ?? '').getTime()
const currentTime = new Date().getTime()
// Hard Coded Time Out, to change into env later
if (currentTime - ttl <= 3600000) {
return true
} else {
sessionStorage.setItem('isAuth', 'false')
setIsAuth(false)
setIsValidToken(false)
return false
}
}

useEffect(() => {
setIsAuth(Boolean(sessionStorage.getItem('isAuth')))
if (isValid()) {
setIsValidToken(true)
if (pathname === '/auth') {
router.push('/')
}
} else {
setIsValidToken(false)
setIsAuth(false)
router.push('/auth')
}
}, [setIsAuth, setIsValidToken])
const { data: session } = useSession()

return (
<>
{isAuth && isValidToken ? (
{session ? (
<>
<NavBar />
<div className={`${inter.className} mx-10 my-6`}>{children}</div>
Expand Down
21 changes: 7 additions & 14 deletions frontend/components/layout/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ import {
import Image from 'next/image'
import Link from 'next/link'
import { LogOutIcon } from 'lucide-react'
import { useSetRecoilState } from 'recoil'
import { tokenState, userState } from '@/atoms/auth'
import { signOut } from 'next-auth/react'

export function NavBar() {
const setIsAuth = useSetRecoilState(userState)
const setIsValid = useSetRecoilState(tokenState)
return (
<div className="flex justify-between border-b-[1px]">
<Link href="/" legacyBehavior passHref>
Expand Down Expand Up @@ -50,18 +47,14 @@ export function NavBar() {
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
<Link
href="/auth"
onClick={() => {
setIsAuth(false)
setIsValid(false)
sessionStorage.clear()
<div
className={navigationMenuTriggerStyle()}
onClick={async () => {
await signOut({ callbackUrl: '/auth' })
}}
>
<div className={navigationMenuTriggerStyle()}>
<LogOutIcon />
</div>
</Link>
<LogOutIcon />
</div>
</div>
)
}
7 changes: 5 additions & 2 deletions frontend/components/ui/label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ interface CustomLabelProps {
title: string
textColor: string
bgColor: string
margin?: string
}

export default function CustomLabel({ title, textColor, bgColor }: CustomLabelProps) {
export default function CustomLabel({ title, textColor, bgColor, margin }: CustomLabelProps) {
return (
<span className={`${textColor} ${bgColor} px-3 py-1 rounded-xl text-center margin-auto capitalize w-fit`}>
<span
className={`${textColor} ${bgColor} m-${margin} px-3 py-1 rounded-xl text-center margin-auto capitalize w-fit`}
>
{title}
</span>
)
Expand Down
26 changes: 26 additions & 0 deletions frontend/hooks/UseProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// hooks/useProtectedRoute.ts

import { useEffect, useState } from 'react'

import { useRouter } from 'next/router'
import { useSession } from 'next-auth/react'

const useProtectedRoute = () => {
const { data: session, status } = useSession()
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)

useEffect(() => {
if (status === 'loading') return

if (!session) {
router.push('/auth')
} else {
setIsLoading(false)
}
}, [session, status, router])

return { session, loading: isLoading }
}

export default useProtectedRoute
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"lucide-react": "^0.441.0",
"moment": "^2.30.1",
"next": "14.2.11",
"next-auth": "^4.24.8",
"react": "^18",
"react-ace": "^12.0.0",
"react-dom": "^18",
Expand Down
19 changes: 11 additions & 8 deletions frontend/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import '@/styles/globals.css'

import { AppProps } from 'next/app'
import Layout from '@/components/layout/layout'
import CustomToaster from '@/components/customs/custom-toaster'
import Layout from '@/components/layout/layout'
import { RecoilRoot } from 'recoil'
import { SessionProvider } from 'next-auth/react'

export default function App({ Component, pageProps }: AppProps) {
export default function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<RecoilRoot>
<Layout>
<Component {...pageProps} />
<CustomToaster />
</Layout>
</RecoilRoot>
<SessionProvider session={session} refetchInterval={60}>
<RecoilRoot>
<Layout>
<Component {...pageProps} />
<CustomToaster />
</Layout>
</RecoilRoot>
</SessionProvider>
)
}
4 changes: 4 additions & 0 deletions frontend/pages/account/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import AccountSettings from '@/components/account/AccountSetting'
import Loading from '@/components/customs/loading'
import useProtectedRoute from '@/hooks/UseProtectedRoute'

export default function Account() {
const { loading } = useProtectedRoute()
if (loading) return <Loading />
return <AccountSettings />
}
Loading

0 comments on commit ae45630

Please sign in to comment.