Skip to content

Commit

Permalink
Added service worker for PWA support. Added axios, redux and other ut…
Browse files Browse the repository at this point in the history
…ils. Added modal and forms. Added auth for user not logged in.
  • Loading branch information
lenn0n13 committed Jul 17, 2024
1 parent 39e2091 commit 0737694
Show file tree
Hide file tree
Showing 38 changed files with 9,405 additions and 1,378 deletions.
25 changes: 25 additions & 0 deletions app/(pages)/dashboard/logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client'
import React from 'react'

import { useCookie } from "@hooks/all"
import { useRouter } from 'next/navigation'
import { ArrowLeftStartOnRectangleIcon } from '@heroicons/react/24/solid'
const Logout = () => {
const { removeCookie } = useCookie()
const router = useRouter()
const handleLogout = () => {
removeCookie({
name: 'user_token',
domain: window.location.hostname
})
router.push('/')
}
return (
<div onClick={handleLogout} className="flex flex-row gap-2 mb-0 hover:text-slate-400 text-red-700 dark:text-red-500" role="button">
<ArrowLeftStartOnRectangleIcon className="h-6 w-6" />
<div className="">Logout</div>
</div>
)
}

export default Logout
11 changes: 5 additions & 6 deletions app/(pages)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react'
import Card from '../../components/Card/Card'
import { HomeModernIcon, DocumentTextIcon, ArrowLeftStartOnRectangleIcon, CreditCardIcon, UserGroupIcon, UsersIcon, BuildingOffice2Icon } from '@heroicons/react/24/solid'
import { HomeModernIcon, CreditCardIcon, UserGroupIcon, UsersIcon, BuildingOffice2Icon } from '@heroicons/react/24/solid'
import Logout from './logout'
const Dashboard = () => {

return (
<div className="">
<div className="h-[100vh]">
Expand All @@ -11,7 +13,7 @@ const Dashboard = () => {
<Card>
<div className="font-bold text-xl">Admin Panel</div>
<div className="border-b border-slate-400 mb-5 mt-3 opacity-30"></div>
<div className="flex flex-row gap-2 mb-4 hover:text-slate-400 text-green-700 dark:text-green-500" role="button">
<div className="flex flex-row gap-2 mb-4 hover:text-slate-400 text-green-600 dark:text-green-500" role="button">
<HomeModernIcon className="h-6 w-6" />
<div className="font-bold">Dashboard</div>
</div>
Expand All @@ -32,10 +34,7 @@ const Dashboard = () => {
<div className="">Branch</div>
</div>
<div className="border-b border-slate-400 mb-5 mt-3 opacity-30"></div>
<div className="flex flex-row gap-2 mb-0 hover:text-slate-400 text-red-700 dark:text-red-500" role="button">
<ArrowLeftStartOnRectangleIcon className="h-6 w-6" />
<div className="">Logout</div>
</div>
<Logout/>
</Card>
</div>
<div className="col-span-9 max-h-[80vh] overflow-hidden py-5">
Expand Down
118 changes: 81 additions & 37 deletions app/(pages)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,106 @@
'use client'
import { useEffect, useState } from "react";
import Image from "next/image"
import { useState } from "react";
import { useRouter } from 'next/navigation'
import { useAxios, useCookie, useDispatch } from "@hooks/all";
import { openModal, closeModal } from "@/app/store/reducers/modal";

import Input from "@/app/components/Forms/Input";
import Button from "@/app/components/Forms/Button";
import NRLogo from "@/public/nr.png"
import Link from "@/app/components/Navs/Link";
import Card from "@/app/components/Card/Card";
export default function Login() {
const [date, setDate] = useState<Date>()
import Form from "@/app/components/Forms/Form";
import ErrorModal from "@/app/components/Modal/ErrorModal";

const displayDate = () => {
return date?.toLocaleString();
}
export default function Login() {
const router = useRouter()
const dispatch = useDispatch()
const { post, isLoading } = useAxios()
const { setCookie } = useCookie()
const [password, setPassword] = useState<string | number>()

const getCurrentYear = () => {
const date = new Date()
return date?.getFullYear()
}
useEffect(() => {
setInterval(() => { setDate(new Date) }, 1000)
}, [])

const handleLogin = async () => {
if (password) {
// Validate password
const response = await post({
url: '/login',
requiresAuth: false,
payload: { password }
})

if (response.result) {
// Set token to browser's cookies
setCookie({
name: 'user_token',
value: response.access,
days: 7
})
// Redirect to dashboard
router.push('/dashboard', { scroll: true })
} else {
dispatch(openModal({ id: 'error-modal' }))
}

}
}

return (
<div className="flex items-center justify-center h-[100vh]">

<ErrorModal
message="Incorrect password, please try again."
>
<Button
buttonStyle="solid"
type="button"
className="!bg-gray-300 !border-none !text-black"
onClick={() => { dispatch(closeModal()) }}>Close</Button>
</ErrorModal>

<Card>
<div className="flex flex-col m-auto w-100 items-center xl:w-[300px] rounded-xl ">
<div className="">
<Image className="object-cover w-[100px] h-[100px] opacity-90" src={NRLogo} alt="" />
</div>
<div className="title font-bold text-2xl dark:text-white">NR Login </div>
{date ? <div className="text-[13px] fadeIn">{displayDate() || "Hi, Welcome!"}</div> :
<div>Welcome!</div>
}
<div className="text-sm">Welcome</div>
<Form onSubmit={handleLogin} className=" w-full">
<Input
autoFocus
value={password}
onChange={(password: string | number) => { setPassword(password) }}
type="password"
wrapperClassName="mt-5"
placeholder="Enter Admin Password"
/>

<Input
wrapperClassName="mt-5"
placeholder="Enter Admin Password"
/>

<div className="mt-2 w-full">
<Button buttonStyle="solid">
<div className="flex items-center justify-center gap-2">
LOGIN
<svg className="flex-shrink-0 size-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
</div>
</Button>
</div>
<div className="mt-2">
<Button buttonStyle="solid" type="submit" isLoading={isLoading}>
<div className="flex items-center justify-center gap-2">
LOGIN
<svg className="flex-shrink-0 size-4"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
</div>
</Button>
</div>
</Form>

<Link>
<div className="mt-5 font-bold text-[14px]" >
Expand Down
30 changes: 30 additions & 0 deletions app/components/Auth/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'
import { useLayoutEffect, useState } from "react"
import { useCookie } from "@hooks/all"
import { redirect } from 'next/navigation'

type AuthProps = {
children: React.ReactNode
}

const Auth = ({ children }: AuthProps) => {
const [hasLoaded, setHasLoaded] = useState<boolean>(false)

useLayoutEffect(() => {
const { getCookie } = useCookie();
const listOfPathnames = ["/login", "/"];

if (getCookie('user_token') && listOfPathnames.indexOf(window.location.pathname) != -1) {
redirect('/dashboard')
} else if (getCookie('user_token') == '' && listOfPathnames.indexOf(window.location.pathname) == -1) {
redirect('/')
}

setHasLoaded(true)

}, [])

return hasLoaded ? children : <div className="h-screen w-screen"/>
}

export default Auth
25 changes: 25 additions & 0 deletions app/components/Modal/ErrorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import Modal from './Modal'
import XIcon from "@/public/x.png";
import Image from "next/image"

type ErrorModalProps = {
children?: React.ReactNode,
message?: string
}

const ErrorModal = ({ children, message }: ErrorModalProps) => {

return (
<Modal id="error-modal" hideHeader={true}>
{message && <div className='flex items-center justify-center flex-col text-black dark:text-white'>
<Image className="object-cover w-[100px] h-[100px] opacity-90" src={XIcon} alt="" />
<div className="font-bold mt-4">Error</div>
<div className="mb-4 text-[14px]">{message}</div>
</div>}
{children}
</Modal>
)
}

export default ErrorModal
69 changes: 69 additions & 0 deletions app/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use client'
import { createPortal } from 'react-dom';
import { useSelector, useDispatch } from "@/app/hooks/all"
import { closeModal } from "@/app/store/reducers/modal";

type ModalProps = {
id: string,
title?: string,
size?: 'sm' | 'md' | 'lg' | 'xl',
children: React.ReactNode,
hideHeader?: boolean
}

const Modal = ({
id,
title,
size = 'sm',
hideHeader,
children
}: ModalProps) => {

const dispatch = useDispatch()
const openModal = useSelector((state) => state.modal.isOpen)
const modalId = useSelector((state) => state.modal.modalId)

const handleCloseModal = () => {
dispatch(closeModal())
}

const modalSize = {
sm: 'sm:max-w-sm sm:w-full m-3 sm:mx-auto',
md: 'md:max-w-md md:w-full m-3 md:mx-auto',
lg: 'lg:max-w-lg lg:w-full m-3 lg:mx-auto',
xl: 'xl:max-w-4xl xl:w-full m-3 xl:mx-auto',
}

const show = (): boolean => {
return openModal && modalId === id
}

return createPortal(<div className="relative">
<div id="hs-vertically-centered-modal" className={`hs-overlay size-full fixed top-0 start-0 ${show() ? 'z-[80]' : 'z-0 duration-500 ease-out transition-all'} overflow-x-hidden overflow-y-auto pointer-events-none`}>
<div className={`${show() ? ' mt-7 opacity-100 duration-500' : ' opacity-0 mt-0 duration-500'} p-5 ease-out transition-all ${modalSize[size]} min-h-[calc(100%-3.5rem)] flex items-center`}>
<div className=" bg-opacity-90 w-full flex flex-col bg-white border shadow-sm rounded-xl pointer-events-auto dark:bg-neutral-800 dark:border-neutral-700 dark:shadow-neutral-700/70">
<div className={`justify-between items-center py-3 px-4 border-b dark:border-neutral-700 ${hideHeader ? 'hidden' : 'flex'}`}>
<h3 className="font-bold text-gray-800 dark:text-white">
{title}
</h3>
<button type="button" onClick={handleCloseModal} className="flex justify-center items-center size-7 text-sm font-semibold rounded-full border border-transparent text-gray-800 hover:bg-gray-100 disabled:opacity-50 disabled:pointer-events-none dark:text-white dark:hover:bg-neutral-700">
<span className="sr-only">Close</span>
<svg className="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
<div className="p-4 overflow-y-auto">
{children}
</div>
</div>
</div>

</div>
<div className={`transition duration fixed ${show() ? '' : 'hidden'} w-screen h-screen z-[79] inset-0 bg-gray-900 bg-opacity-60 dark:bg-opacity-80 dark:bg-neutral-900 `}></div>
</div>
, document.getElementById('root-modal') as Element)
}

export default Modal
11 changes: 10 additions & 1 deletion app/components/PageWrapper/PageWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
'use client'
import React from 'react'
import ToggleTheme from "@/app/components/ToggleTheme/ToggleTheme";
import Auth from "@/app/components/Auth/Auth";

const page = ({ children }: { children: React.ReactNode }) => {
return (
<div className='relative h-[100vh] overflow-y-scroll'>
<div className="bg-land dark:bg-dark-land bg-cover bg-fixed text-slate-800">
{children}
<Auth>
{children}
</Auth>
<div className="absolute bottom-0 w-full">
<div className="flex items-center text-center text-[12px] justify-center text-white font-bold pb-2">
Powered By NextJS, TypeScript, Tailwind and Redux.
</div>
</div>
</div>
<ToggleTheme />
</div>
Expand Down
15 changes: 12 additions & 3 deletions app/components/forms/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ type Props = {
type?: "submit" | "button"
className?: string,
buttonStyle?: "solid" | "outline",
onClick?: () => void
onClick?: () => void,
isLoading?: boolean
}

function Button({
children,
type,
buttonStyle,
className = "",
onClick
onClick,
isLoading = false
}: Props) {

const buttonStyleList = {
Expand All @@ -31,9 +33,16 @@ function Button({
return (
<button
onClick={onClick}
disabled={isLoading}
type={type}
className={className + " " + buttonStyleList?.[buttonStyle || "solid"]}>
{children}
{isLoading ? <div className='flex items-center justify-center'>
<svg aria-hidden="true" className="inline w-8 h-5 text-gray-200 animate-spin dark:text-gray-600 fill-green-500" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor" />
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill" />
</svg>
<span className="sr-only">Loading...</span></div> : children}

</button>
)
}
Expand Down
Loading

0 comments on commit 0737694

Please sign in to comment.