Skip to content

Commit

Permalink
feat(Frontend): Implemented mock Login/Logout
Browse files Browse the repository at this point in the history
  • Loading branch information
iandday committed Oct 20, 2024
1 parent b3ff8bc commit 555af90
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 146 deletions.
35 changes: 35 additions & 0 deletions frontend/src/components/hooks/useSecureStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState, useEffect } from "react";
import SecureStorage from "react-secure-storage";

const useSecureStorage = <T>(key: string, initialValue: string): [T, (value: T) => void] => {
const [value, setValue] = useState<T>(() => {
try {
const storedValue = SecureStorage.getItem(key)?.toString();
return storedValue ? JSON.parse(storedValue) : initialValue;
} catch (error) {
console.error("Error retrieving from secure storage:", error);
return initialValue;
}
});

useEffect(() => {
try {
if (value === null) {
SecureStorage.removeItem(key);
} else {
SecureStorage.setItem(key, JSON.stringify(value));
}
} catch (error) {
console.error("Error saving to secure storage:", error);
}
}, [key, value]);

const updateValue = (newData: T) => {
console.log("updating value", newData);
setValue(newData);
};

return [value, updateValue];
};

export default useSecureStorage;
31 changes: 28 additions & 3 deletions frontend/src/components/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Link } from 'react-router-dom'
import { IconChevronDown } from '@tabler/icons-react'
import { Link, useNavigate } from 'react-router-dom'
import {
IconChevronDown,
IconChevronsLeft,
IconLogout,
} from '@tabler/icons-react'
import { Button, buttonVariants } from './custom/button'
import {
Collapsible,
Expand All @@ -23,6 +27,8 @@ import {
import { cn } from '@/lib/utils'
import useCheckActiveNav from '@/hooks/use-check-active-nav'
import { SideLink } from '@/data/sidelinks'
import { AuthContext } from '../context/AuthContext'
import React from 'react'

interface NavProps extends React.HTMLAttributes<HTMLDivElement> {
isCollapsed: boolean
Expand All @@ -36,8 +42,11 @@ export default function Nav({
className,
closeNav,
}: NavProps) {
const { user, login, logout } = React.useContext(AuthContext)
const navigate = useNavigate()
const renderLink = ({ sub, ...rest }: SideLink) => {
const key = `${rest.title}-${rest.href}`

if (isCollapsed && sub)
return (
<NavLinkIconDropdown
Expand All @@ -58,6 +67,7 @@ export default function Nav({

return <NavLink {...rest} key={key} closeNav={closeNav} />
}

return (
<div
data-collapsed={isCollapsed}
Expand All @@ -68,7 +78,22 @@ export default function Nav({
>
<TooltipProvider delayDuration={0}>
<nav className='grid gap-1 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2'>
{links.map(renderLink)}
{links.map((l) => {
if (user?.token) {
if (!['Sign In', 'Sign Up', 'Forgot Password'].includes(l.title))
return renderLink(l)
} else {
if (['Sign In', 'Sign Up', 'Forgot Password'].includes(l.title))
return renderLink(l)
}
})}
{user?.token &&
renderLink({
title: 'Logout',
label: '',
href: '/sign-out',
icon: <IconLogout size={18} />,
})}
</nav>
</TooltipProvider>
</div>
Expand Down
44 changes: 9 additions & 35 deletions frontend/src/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { useEffect, useState } from 'react'
import { IconChevronsLeft, IconMenu2, IconX } from '@tabler/icons-react'
import {
IconChevronsLeft,
IconMenu2,
IconX,
IconShoppingCart,
} from '@tabler/icons-react'
import { Layout } from './custom/layout'
import { Button } from './custom/button'
import Nav from './nav'
Expand Down Expand Up @@ -47,44 +52,14 @@ export default function Sidebar({
className='z-50 flex justify-between px-4 py-3 shadow-sm md:px-4'
>
<div className={`flex items-center ${!isCollapsed ? 'gap-2' : ''}`}>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 256 256'
className={`transition-all ${isCollapsed ? 'h-6 w-6' : 'h-8 w-8'}`}
>
<rect width='256' height='256' fill='none'></rect>
<line
x1='208'
y1='128'
x2='128'
y2='208'
fill='none'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='16'
></line>
<line
x1='192'
y1='40'
x2='40'
y2='192'
fill='none'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='16'
></line>
<span className='sr-only'>Website Name</span>
</svg>
<IconShoppingCart stroke={1.5} className={``} />
<div
className={`flex flex-col justify-end truncate ${isCollapsed ? 'invisible w-0' : 'visible w-auto'}`}
>
<span className='font-medium'>Shadcn Admin</span>
<span className='text-xs'>Vite + ShadcnUI</span>
<span className='font-medium'>Don't Forget</span>
{/* <span className='text-xs'>It's Important</span> */}
</div>
</div>

{/* Toggle Button in mobile */}
<Button
variant='ghost'
Expand All @@ -107,7 +82,6 @@ export default function Sidebar({
isCollapsed={isCollapsed}
links={sidelinks}
/>

{/* Scrollbar width toggle button */}
<Button
onClick={() => setIsCollapsed((prev) => !prev)}
Expand Down
82 changes: 82 additions & 0 deletions frontend/src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createContext, ReactNode } from 'react'
import useSecureStorage from '../hooks/use-local-storage'

type Props = {
children?: ReactNode
}

interface AuthContextUser {
firstName: string
lastName: string
email: string
token: string
refreshToken: string
}

interface IAuthContext {
//authenticated: boolean;
user: AuthContextUser | null
login: ({
firstName,
lastName,
email,
token,
refreshToken,
}: AuthContextUser) => void
logout: () => void
}

const initialUser = {
firstName: '',
lastName: '',
email: '',
token: '',
refreshToken: '',
}

const initialValue = {
//authenticated: false,
user: initialUser,
login: ({
firstName,
lastName,
email,
token,
refreshToken,
}: AuthContextUser) => {},
logout: () => {},
}

const AuthContext = createContext<IAuthContext>(initialValue)

const AuthProvider = ({ children }: Props) => {
const [user, setUser] = useSecureStorage<AuthContextUser | null>('user', '')

const login = ({
firstName,
lastName,
email,
token,
refreshToken,
}: AuthContextUser) => {
setUser({
firstName: firstName,
lastName: lastName,
email: email,
token: token,
refreshToken: refreshToken,
})
}

const logout = () => {
setUser(null)
}

return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
)
}

export { AuthContext, AuthProvider }
12 changes: 8 additions & 4 deletions frontend/src/data/sidelinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import {
IconUserShield,
IconUsers,
IconLock,
IconLogin,
IconLogin2,
IconUserSquare,
IconKeyOff,
} from '@tabler/icons-react'

export interface NavLink {
Expand Down Expand Up @@ -127,21 +131,21 @@ export const sidelinks: SideLink[] = [
icon: <IconSettings size={18} />,
},
{
title: 'Sign In (email + password)',
title: 'Sign In',
label: '',
href: '/sign-in',
icon: <IconHexagonNumber1 size={18} />,
icon: <IconLogin2 size={18} />,
},
{
title: 'Sign Up',
label: '',
href: '/sign-up',
icon: <IconHexagonNumber3 size={18} />,
icon: <IconUserSquare size={18} />,
},
{
title: 'Forgot Password',
label: '',
href: '/forgot-password',
icon: <IconHexagonNumber4 size={18} />,
icon: <IconKeyOff size={18} />,
},
]
11 changes: 7 additions & 4 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { Toaster } from '@/components/ui/toaster'
import { ThemeProvider } from '@/components/theme-provider'
import router from '@/router'
import '@/index.css'
import { AuthProvider } from './context/AuthContext'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider defaultTheme='dark' storageKey='vite-ui-theme'>
<RouterProvider router={router} />
<Toaster />
</ThemeProvider>
<AuthProvider>
<ThemeProvider defaultTheme='dark' storageKey='vite-ui-theme'>
<RouterProvider router={router} />
<Toaster />
</ThemeProvider>
</AuthProvider>
</React.StrictMode>
)
42 changes: 14 additions & 28 deletions frontend/src/pages/auth/components/user-auth-form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HTMLAttributes, useState } from 'react'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router-dom'
import { Link, useNavigate } from 'react-router-dom'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { IconBrandFacebook, IconBrandGithub } from '@tabler/icons-react'
Expand All @@ -16,6 +16,8 @@ import { Input } from '@/components/ui/input'
import { Button } from '@/components/custom/button'
import { PasswordInput } from '@/components/custom/password-input'
import { cn } from '@/lib/utils'
import { AuthContext } from '../../../context/AuthContext'
import React from 'react'

interface UserAuthFormProps extends HTMLAttributes<HTMLDivElement> {}

Expand All @@ -36,6 +38,8 @@ const formSchema = z.object({

export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
const [isLoading, setIsLoading] = useState(false)
const { login } = React.useContext(AuthContext)
const navigate = useNavigate()

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
Expand All @@ -48,7 +52,15 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
function onSubmit(data: z.infer<typeof formSchema>) {
setIsLoading(true)
console.log(data)

login({
firstName: 'bob',
lastName: 'smith',
email: data.email,
token: 'token',
refreshToken: 'rtoken',
})
setIsLoading(false)
navigate('/')
setTimeout(() => {
setIsLoading(false)
}, 3000)
Expand Down Expand Up @@ -101,32 +113,6 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
<div className='absolute inset-0 flex items-center'>
<span className='w-full border-t' />
</div>
<div className='relative flex justify-center text-xs uppercase'>
<span className='bg-background px-2 text-muted-foreground'>
Or continue with
</span>
</div>
</div>

<div className='flex items-center gap-2'>
<Button
variant='outline'
className='w-full'
type='button'
loading={isLoading}
leftSection={<IconBrandGithub className='h-4 w-4' />}
>
GitHub
</Button>
<Button
variant='outline'
className='w-full'
type='button'
loading={isLoading}
leftSection={<IconBrandFacebook className='h-4 w-4' />}
>
Facebook
</Button>
</div>
</div>
</form>
Expand Down
Loading

0 comments on commit 555af90

Please sign in to comment.