From 539cf51db40cbe2b3d706e5903b3b176572899bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 11:01:03 +0700 Subject: [PATCH 1/8] fix(api): update next-auth demo config --- src/pages/api/auth/[...nextauth].ts | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/pages/api/auth/[...nextauth].ts b/src/pages/api/auth/[...nextauth].ts index f3f9ab9..7133eb0 100644 --- a/src/pages/api/auth/[...nextauth].ts +++ b/src/pages/api/auth/[...nextauth].ts @@ -1,12 +1,13 @@ -import { sha1 } from 'hash-wasm'; +import { bcryptVerify } from 'hash-wasm'; import NextAuth from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; -function getOneUser() { +function getOneUser(email: string) { const user = { - email: 'tienvinh@gmail.com', + id: '1', + email, name: 'Dương Tiến Vinh', - password: '7110eda4d09e062aa5e4a390b0a572ac0d2c0220', // 1234 + password: '$2a$10$vOJLhI.jeFrIB0imYb8OMu5yyhGxHyjsElagxR2ESMX5nKsHW0rxO', // password is 'password' }; return Promise.resolve({ ...user, password: user.password }); @@ -28,20 +29,30 @@ export default NextAuth({ }, }, async authorize(credentials) { + if (!credentials) return null; + // Inputs from login form const { email, password } = credentials; - const hashPassword = await sha1(password); - const user = await getOneUser(); - if (user && user.password === hashPassword) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return user as any; + + const user = await getOneUser(email); + + if (user) { + const isVerified = await bcryptVerify({ + password, + hash: user.password, + }); + + if (!isVerified) return null; + + return user; } + return null; }, }), ], pages: { - signIn: '/auth/login', + signIn: '/auth/sign-in', }, events: { signIn(message) { From 97171d2183d0dae959d8b52c3f919c2165044530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:40:48 +0700 Subject: [PATCH 2/8] refactor(web): update sign in & sign up page --- package.json | 1 + pnpm-lock.yaml | 11 +++ src/components/forms/SignInForm.tsx | 86 +++++++++++++++++ src/components/forms/SignUpForm.tsx | 114 ++++++++++++++++++++++ src/pages/auth/login.tsx | 141 ---------------------------- src/pages/auth/register.tsx | 47 ---------- src/pages/auth/sign-in.tsx | 33 +++++++ src/pages/auth/sign-up.tsx | 33 +++++++ 8 files changed, 278 insertions(+), 188 deletions(-) create mode 100644 src/components/forms/SignInForm.tsx create mode 100644 src/components/forms/SignUpForm.tsx delete mode 100644 src/pages/auth/login.tsx delete mode 100644 src/pages/auth/register.tsx create mode 100644 src/pages/auth/sign-in.tsx create mode 100644 src/pages/auth/sign-up.tsx diff --git a/package.json b/package.json index ee682ac..fe5f79b 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "prepare": "husky install" }, "dependencies": { + "@hookform/resolvers": "3.3.3", "@iconify/react": "4.1.1", "@mantine/core": "7.1.5", "@mantine/hooks": "7.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2ec8ba..fc00f7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@hookform/resolvers': + specifier: 3.3.3 + version: 3.3.3(react-hook-form@7.47.0) '@iconify/react': specifier: 4.1.1 version: 4.1.1(react@18.2.0) @@ -605,6 +608,14 @@ packages: resolution: {integrity: sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==} dev: false + /@hookform/resolvers@3.3.3(react-hook-form@7.47.0): + resolution: {integrity: sha512-bOMxKkSD3zWcS11TKoUQ8O0ZqKslFohvUsPKSrdCHiuEuMjRo/u3cq9YRJD/+xtNGYup++XD2LkjhegP5XENiw==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.47.0(react@18.2.0) + dev: false + /@humanwhocodes/config-array@0.11.11: resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} diff --git a/src/components/forms/SignInForm.tsx b/src/components/forms/SignInForm.tsx new file mode 100644 index 0000000..747546e --- /dev/null +++ b/src/components/forms/SignInForm.tsx @@ -0,0 +1,86 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { + Button, + Group, + PasswordInput, + Space, + Stack, + TextInput, +} from '@mantine/core'; +import { useRouter } from 'next/navigation'; +import { signIn } from 'next-auth/react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +export const signInSchema = z.object({ + email: z.string().email(), + password: z.string().min(8), +}); + +export type TSignInForm = z.infer; + +const SignInForm = () => { + const router = useRouter(); + + const { + setError, + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + email: '', + password: '', + }, + resolver: zodResolver(signInSchema), + }); + + const onSubmit = async (formData: TSignInForm) => { + const res = await signIn('credentials', { + email: formData.email, + password: formData.password, + redirect: false, + }); + + if (res?.status === 401) { + setError('email', { + type: 'manual', + message: 'Invalid credentials', + }); + + setError('password', { + type: 'manual', + message: 'Invalid credentials', + }); + } else if (res?.status === 200) { + router.push('/'); + } + }; + + return ( +
+ + + + + + + + + + +
+ ); +}; + +export { SignInForm }; diff --git a/src/components/forms/SignUpForm.tsx b/src/components/forms/SignUpForm.tsx new file mode 100644 index 0000000..526bf9d --- /dev/null +++ b/src/components/forms/SignUpForm.tsx @@ -0,0 +1,114 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { + Button, + Group, + PasswordInput, + Space, + Stack, + TextInput, +} from '@mantine/core'; +import { useMutation } from '@tanstack/react-query'; +import axios, { AxiosError } from 'axios'; +import { useRouter } from 'next/navigation'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +export const signUpSchema = z + .object({ + email: z.string().email(), + name: z.string().nullish(), + password: z.string().min(8), + confirmPassword: z.string(), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ['confirmPassword'], + }); + +export type TSignUpForm = z.infer; + +const SignUpForm = () => { + const router = useRouter(); + + const { + setError, + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + name: '', + email: '', + password: '', + confirmPassword: '', + }, + resolver: zodResolver(signUpSchema), + }); + + const { mutate: signUp } = useMutation({ + mutationKey: ['users', 'signUp'], + mutationFn: async (formData: TSignUpForm) => { + const { data } = await axios.post('/api/users', { + name: formData.name, + email: formData.email, + password: formData.password, + }); + + return data; + }, + onSuccess: () => { + router.push('/auth/sign-in'); + }, + onError: (error) => { + if (error instanceof AxiosError) { + if (error.response?.status === 409) { + setError('email', { + message: 'Email already exists', + }); + } + } + }, + }); + + const onSubmit = (formData: TSignUpForm) => { + signUp(formData); + }; + + return ( +
+ + + + + + + + + + + + +
+ ); +}; + +export { SignUpForm }; diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx deleted file mode 100644 index a0486c8..0000000 --- a/src/pages/auth/login.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { Icon } from '@iconify/react'; -import { - Anchor, - Button, - Code, - Container, - Group, - LoadingOverlay, - Modal, - Paper, - PasswordInput, - Text, - TextInput, - ThemeIcon, - Title, -} from '@mantine/core'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { signIn, useSession } from 'next-auth/react'; -import { useEffect, useState } from 'react'; - -import { useForm } from 'react-hook-form'; - -const Login = () => { - const router = useRouter(); - const { register, handleSubmit } = useForm(); - const [modalVisible, setModalVisible] = useState(false); - const [overlayVisible, setOverlayVisible] = useState(false); - const [formSubmitted, setFormSubmitted] = useState(false); - const [error, setError] = useState(''); - const [user, setUser] = useState(null); - const { data: session } = useSession(); - - useEffect(() => { - if (session) { - setUser(session.user); - } - }, [session]); - - useEffect(() => { - if (formSubmitted) { - setModalVisible(true); - setOverlayVisible(false); - setFormSubmitted(false); - } - }, [formSubmitted]); - - const onSubmit = async (form) => { - const { email, password } = form; - setOverlayVisible(true); - // Sign in using next-auth function api - const { error: signInError } = await signIn('credentials', { - email, - password, - redirect: false, - }); - if (signInError) { - setError(signInError); - } - setFormSubmitted(true); - }; - - return ( - - Welcome back! - - Do not have an account yet?{' '} - - Create account - - - - username: tienvinh@gmail.com,{' '} - password: 1234 - - - -
- - - - - - -
- - { - setModalVisible(false); - if (!error && user) { - router.push('/'); - } - }} - opened={modalVisible} - withCloseButton={false} - > - {error ? ( - - - - - Wrong credentials - - ) : ( - user && ( - - - - - You are logged in - - ) - )} - -
- ); -}; - -export default Login; diff --git a/src/pages/auth/register.tsx b/src/pages/auth/register.tsx deleted file mode 100644 index 57c5322..0000000 --- a/src/pages/auth/register.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { - Anchor, - Button, - Container, - Paper, - PasswordInput, - Text, - TextInput, - Title, -} from '@mantine/core'; - -import Link from 'next/link'; - -const Register = () => { - return ( - - Welcome to our website! - - Already have an account?{' '} - - Login - - - - - - - - - - - ); -}; - -export default Register; diff --git a/src/pages/auth/sign-in.tsx b/src/pages/auth/sign-in.tsx new file mode 100644 index 0000000..0c9a340 --- /dev/null +++ b/src/pages/auth/sign-in.tsx @@ -0,0 +1,33 @@ +import { Card, Center, Stack, Text } from '@mantine/core'; +import Link from 'next/link'; +import { SignInForm } from '@/components/forms/SignInForm'; + +const SignIn = () => { + return ( + <> +
+ + + + Sign in + + + + + + Do not have an account?{' '} + + Sign up + + + + +
+ + ); +}; + +export default SignIn; diff --git a/src/pages/auth/sign-up.tsx b/src/pages/auth/sign-up.tsx new file mode 100644 index 0000000..d4d1f3b --- /dev/null +++ b/src/pages/auth/sign-up.tsx @@ -0,0 +1,33 @@ +import { Card, Center, Stack, Text } from '@mantine/core'; +import Link from 'next/link'; +import { SignUpForm } from '@/components/forms/SignUpForm'; + +const SignUp = () => { + return ( + <> +
+ + + + Sign up + + + + + + Already have an account?{' '} + + Sign in + + + + +
+ + ); +}; + +export default SignUp; From f0896f355f12afaefffdd1016657cbb4c4baf728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:45:25 +0700 Subject: [PATCH 3/8] chore: update tsconfig --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 67d2e1a..4907fb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "moduleResolution": "Bundler", "module": "ESNext", "noEmit": true, + "outDir": "dist", /* If your code runs in the DOM: */ "lib": ["DOM", "DOM.Iterable", "ESNext"], /* React */ From cb88cda97277e38ca28e2f7c485f780baeab2f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:47:22 +0700 Subject: [PATCH 4/8] refactor: move demo components to modules folder --- src/components/{ui => elements}/Feature.tsx | 0 src/components/{ => modules}/ButtonDemo.tsx | 2 +- src/components/{ => modules}/ComboboxDemo.tsx | 2 +- src/components/{ => modules}/DataDisplayDemo.tsx | 2 +- src/components/{ => modules}/FeedbackDemo.tsx | 2 +- src/components/{ => modules}/InputDemo.tsx | 2 +- src/components/{ => modules}/NavigationDemo.tsx | 2 +- src/components/{ => modules}/OverlayDemo.tsx | 2 +- src/components/{ => modules}/TypographyDemo.tsx | 2 +- src/pages/index.tsx | 16 ++++++++-------- 10 files changed, 16 insertions(+), 16 deletions(-) rename src/components/{ui => elements}/Feature.tsx (100%) rename src/components/{ => modules}/ButtonDemo.tsx (98%) rename src/components/{ => modules}/ComboboxDemo.tsx (98%) rename src/components/{ => modules}/DataDisplayDemo.tsx (99%) rename src/components/{ => modules}/FeedbackDemo.tsx (98%) rename src/components/{ => modules}/InputDemo.tsx (99%) rename src/components/{ => modules}/NavigationDemo.tsx (99%) rename src/components/{ => modules}/OverlayDemo.tsx (96%) rename src/components/{ => modules}/TypographyDemo.tsx (96%) diff --git a/src/components/ui/Feature.tsx b/src/components/elements/Feature.tsx similarity index 100% rename from src/components/ui/Feature.tsx rename to src/components/elements/Feature.tsx diff --git a/src/components/ButtonDemo.tsx b/src/components/modules/ButtonDemo.tsx similarity index 98% rename from src/components/ButtonDemo.tsx rename to src/components/modules/ButtonDemo.tsx index a210da4..175e24e 100644 --- a/src/components/ButtonDemo.tsx +++ b/src/components/modules/ButtonDemo.tsx @@ -1,7 +1,7 @@ import { Icon } from '@iconify/react'; import { ActionIcon, Button } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const ButtonDemo = memo(function ButtonDemo({ diff --git a/src/components/ComboboxDemo.tsx b/src/components/modules/ComboboxDemo.tsx similarity index 98% rename from src/components/ComboboxDemo.tsx rename to src/components/modules/ComboboxDemo.tsx index e73c86f..7bcca1f 100644 --- a/src/components/ComboboxDemo.tsx +++ b/src/components/modules/ComboboxDemo.tsx @@ -6,7 +6,7 @@ import { TagsInput, } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const ComboboxDemo = memo(function ComboboxDemo({ diff --git a/src/components/DataDisplayDemo.tsx b/src/components/modules/DataDisplayDemo.tsx similarity index 99% rename from src/components/DataDisplayDemo.tsx rename to src/components/modules/DataDisplayDemo.tsx index a24dfb2..1f2bc2f 100644 --- a/src/components/DataDisplayDemo.tsx +++ b/src/components/modules/DataDisplayDemo.tsx @@ -8,7 +8,7 @@ import { ThemeIcon, } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const DataDisplayDemo = memo(function DataDisplayDemo({ diff --git a/src/components/FeedbackDemo.tsx b/src/components/modules/FeedbackDemo.tsx similarity index 98% rename from src/components/FeedbackDemo.tsx rename to src/components/modules/FeedbackDemo.tsx index e961634..d5ce773 100644 --- a/src/components/FeedbackDemo.tsx +++ b/src/components/modules/FeedbackDemo.tsx @@ -1,6 +1,6 @@ import { Alert, Notification, Progress, Skeleton } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const FeedbackDemo = memo(function FeedbackDemo({ diff --git a/src/components/InputDemo.tsx b/src/components/modules/InputDemo.tsx similarity index 99% rename from src/components/InputDemo.tsx rename to src/components/modules/InputDemo.tsx index b681b3d..78e2d7d 100644 --- a/src/components/InputDemo.tsx +++ b/src/components/modules/InputDemo.tsx @@ -18,7 +18,7 @@ import { Textarea, } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const InputDemo = memo(function InputDemo({ diff --git a/src/components/NavigationDemo.tsx b/src/components/modules/NavigationDemo.tsx similarity index 99% rename from src/components/NavigationDemo.tsx rename to src/components/modules/NavigationDemo.tsx index 966c722..f59f904 100644 --- a/src/components/NavigationDemo.tsx +++ b/src/components/modules/NavigationDemo.tsx @@ -1,6 +1,6 @@ import { NavLink, Pagination, Stepper, Tabs } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const NavigationDemo = memo(function NavigationDemo({ diff --git a/src/components/OverlayDemo.tsx b/src/components/modules/OverlayDemo.tsx similarity index 96% rename from src/components/OverlayDemo.tsx rename to src/components/modules/OverlayDemo.tsx index 518360c..fc02ecb 100644 --- a/src/components/OverlayDemo.tsx +++ b/src/components/modules/OverlayDemo.tsx @@ -1,6 +1,6 @@ import { Button, Tooltip } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const OverlayDemo = memo(function OverlayDemo({ diff --git a/src/components/TypographyDemo.tsx b/src/components/modules/TypographyDemo.tsx similarity index 96% rename from src/components/TypographyDemo.tsx rename to src/components/modules/TypographyDemo.tsx index f1532af..91e21ff 100644 --- a/src/components/TypographyDemo.tsx +++ b/src/components/modules/TypographyDemo.tsx @@ -1,7 +1,7 @@ import { Icon } from '@iconify/react'; import { Blockquote, Code, Highlight, Stack, Title } from '@mantine/core'; import { memo } from 'react'; -import { Feature } from '@/components/ui/Feature'; +import { Feature } from '@/components/elements/Feature'; import { type ControlledDemoProps } from '@/pages'; const TypographyDemo = memo(function TypographyDemo({ diff --git a/src/pages/index.tsx b/src/pages/index.tsx index c1ac6ee..55095c5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -13,14 +13,14 @@ import { } from '@mantine/core'; import Head from 'next/head'; import { useDeferredValue, useState } from 'react'; -import { ButtonDemo } from '@/components/ButtonDemo'; -import { ComboboxDemo } from '@/components/ComboboxDemo'; -import { DataDisplayDemo } from '@/components/DataDisplayDemo'; -import { FeedbackDemo } from '@/components/FeedbackDemo'; -import { InputDemo } from '@/components/InputDemo'; -import { NavigationDemo } from '@/components/NavigationDemo'; -import { OverlayDemo } from '@/components/OverlayDemo'; -import { TypographyDemo } from '@/components/TypographyDemo'; +import { ButtonDemo } from '@/components/modules/ButtonDemo'; +import { ComboboxDemo } from '@/components/modules/ComboboxDemo'; +import { DataDisplayDemo } from '@/components/modules/DataDisplayDemo'; +import { FeedbackDemo } from '@/components/modules/FeedbackDemo'; +import { InputDemo } from '@/components/modules/InputDemo'; +import { NavigationDemo } from '@/components/modules/NavigationDemo'; +import { OverlayDemo } from '@/components/modules/OverlayDemo'; +import { TypographyDemo } from '@/components/modules/TypographyDemo'; export type ControlledDemoProps = { color: string; From 3af9dc4f5d09079366f92388c0a4dfed26aa1989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:44:16 +0700 Subject: [PATCH 5/8] chore: add appshell UI --- src/components/layouts/AppShell.tsx | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/components/layouts/AppShell.tsx diff --git a/src/components/layouts/AppShell.tsx b/src/components/layouts/AppShell.tsx new file mode 100644 index 0000000..c8f34bd --- /dev/null +++ b/src/components/layouts/AppShell.tsx @@ -0,0 +1,59 @@ +import { Icon } from '@iconify/react/dist/iconify.js'; +import { + ActionIcon, + Anchor, + Group, + AppShell as MantineAppShell, + Text, + useMantineColorScheme, +} from '@mantine/core'; + +const AppShell = ({ children }: { children?: React.ReactNode }) => { + const { colorScheme } = useMantineColorScheme(); + const dark = colorScheme === 'dark'; + + return ( + + + + + + + + + + + + + {children} + + + + + + Made with{' '} + {' '} + by{' '} + Tien Vinh + + + + + ); +}; + +export { AppShell }; From d8cf9a5607428a6222fe5b613d96d751a22b5b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:50:56 +0700 Subject: [PATCH 6/8] fix: update homepage UI --- src/pages/index.tsx | 264 +++++++++++++++++++++++--------------------- 1 file changed, 138 insertions(+), 126 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 55095c5..38b6580 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -13,6 +13,7 @@ import { } from '@mantine/core'; import Head from 'next/head'; import { useDeferredValue, useState } from 'react'; +import { AppShell } from '@/components/layouts/AppShell'; import { ButtonDemo } from '@/components/modules/ButtonDemo'; import { ComboboxDemo } from '@/components/modules/ComboboxDemo'; import { DataDisplayDemo } from '@/components/modules/DataDisplayDemo'; @@ -73,140 +74,147 @@ const HomePage = () => { + /> - - + - - setLabel(e.target.value)} - /> - setDescription(e.target.value)} - /> - setError(e.target.value)} - /> - setPlaceholder(e.target.value)} - /> - - - - setWithAsterisk(e.currentTarget.checked)} + + + setLabel(e.target.value)} /> - setDisabled(e.currentTarget.checked)} + setDescription(e.target.value)} /> - setLoading(e.currentTarget.checked)} + setError(e.target.value)} /> - - - - Size: {size} - - - setSize( - { - 0: 'xs', - 25: 'sm', - 50: 'md', - 75: 'lg', - 100: 'xl', - }[e] as MantineSize, - ) - } - step={25} - /> - - - - Radius: {radius} - - - setRadius( - { - 0: 'xs', - 25: 'sm', - 50: 'md', - 75: 'lg', - 100: 'xl', - }[e] as MantineSize, - ) - } - step={25} + setPlaceholder(e.target.value)} /> - - Color: {color} - - {Object.keys(themes.colors).map((colorKey, index) => { - return ( - setColor(colorKey)} - radius="md" - withShadow={false} - > - {color === colorKey && ( - - )} - - ); - })} - + + + setWithAsterisk(e.currentTarget.checked)} + /> + setDisabled(e.currentTarget.checked)} + /> + setLoading(e.currentTarget.checked)} + /> + + + + Size: {size} + + + setSize( + { + 0: 'xs', + 25: 'sm', + 50: 'md', + 75: 'lg', + 100: 'xl', + }[e] as MantineSize, + ) + } + step={25} + /> + + + + Radius: {radius} + + + setRadius( + { + 0: 'xs', + 25: 'sm', + 50: 'md', + 75: 'lg', + 100: 'xl', + }[e] as MantineSize, + ) + } + step={25} + /> + + + Color: {color} + + {Object.keys(themes.colors).map((colorKey, index) => { + return ( + setColor(colorKey)} + radius="md" + withShadow={false} + > + {color === colorKey && ( + + )} + + ); + })} + + - - + + { ); }; +HomePage.getLayout = (page: React.ReactNode) => { + return {page}; +}; + export default HomePage; From 5cd9d40e5774d0b8b3a6620929fa0409dac4a4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:53:07 +0700 Subject: [PATCH 7/8] fix: update class merge utils --- src/lib/utils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ec79801..9ad0df4 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,6 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" - +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } From faa293c3c715383a35977f6ab364586ee9e2f0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C6=B0=C6=A1ng=20Ti=E1=BA=BFn=20Vinh?= <64480713+DuckyMomo20012@users.noreply.github.com> Date: Thu, 28 Dec 2023 20:56:40 +0700 Subject: [PATCH 8/8] fix: update authguard component --- package.json | 1 + pnpm-lock.yaml | 48 +++++++++++++++++++++ src/context/AuthGuard.tsx | 90 +++++++++++++++++---------------------- 3 files changed, 89 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index fe5f79b..d4b65ef 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@iconify/react": "4.1.1", "@mantine/core": "7.1.5", "@mantine/hooks": "7.1.5", + "@mantine/notifications": "7.1.5", "@reduxjs/toolkit": "1.9.7", "@tanstack/react-query": "5.0.0", "@tanstack/react-query-devtools": "5.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc00f7c..0a322fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: '@mantine/hooks': specifier: 7.1.5 version: 7.1.5(react@18.2.0) + '@mantine/notifications': + specifier: 7.1.5 + version: 7.1.5(@mantine/core@7.1.5)(@mantine/hooks@7.1.5)(react-dom@18.2.0)(react@18.2.0) '@reduxjs/toolkit': specifier: 1.9.7 version: 1.9.7(react-redux@8.1.3)(react@18.2.0) @@ -716,6 +719,30 @@ packages: react: 18.2.0 dev: false + /@mantine/notifications@7.1.5(@mantine/core@7.1.5)(@mantine/hooks@7.1.5)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/WRxNNgPvRr4munHjCTZaMVjSIpz8ydheccpPGrqOgAN/zfPNWYYcv7kaqXdlb+ag9ZMFsixQB97svvhCRxPCA==} + peerDependencies: + '@mantine/core': 7.1.5 + '@mantine/hooks': 7.1.5 + react: ^18.2.0 + react-dom: ^18.2.0 + dependencies: + '@mantine/core': 7.1.5(@mantine/hooks@7.1.5)(@types/react@18.2.29)(react-dom@18.2.0)(react@18.2.0) + '@mantine/hooks': 7.1.5(react@18.2.0) + '@mantine/store': 7.1.5(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + + /@mantine/store@7.1.5(react@18.2.0): + resolution: {integrity: sha512-iPAt8auWyUs5TyUr31MziCILlLCYCfw6fSqPvLxOwUYpSf9BvtCAoE9JmrRrVi2q5+xO0KPSyK+OHWyBwsAqcQ==} + peerDependencies: + react: ^18.2.0 + dependencies: + react: 18.2.0 + dev: false + /@next/env@13.5.6: resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} dev: false @@ -1792,6 +1819,13 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.21.0 + csstype: 3.1.1 + dev: false + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -4121,6 +4155,20 @@ packages: - '@types/react' dev: false + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.21.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} diff --git a/src/context/AuthGuard.tsx b/src/context/AuthGuard.tsx index 31c5cf4..2128ddf 100644 --- a/src/context/AuthGuard.tsx +++ b/src/context/AuthGuard.tsx @@ -1,67 +1,57 @@ -import { Icon } from '@iconify/react'; -import { - Center, - Group, - Loader, - Modal, - Stack, - Text, - ThemeIcon, -} from '@mantine/core'; -import { useRouter } from 'next/router'; +import { notifications } from '@mantine/notifications'; +import { useRouter } from 'next/navigation'; import { useSession } from 'next-auth/react'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; const AuthGuard = ({ children }: { children?: React.ReactNode }) => { - const [opened, setOpened] = useState(false); const router = useRouter(); const { status } = useSession(); - // Set this to true will redirect directly - // required: true, - // onUnauthenticated() { - // router.push('/auth/login'); - // }, + // eslint-disable-next-line consistent-return useEffect(() => { - if (status !== 'loading') { - setOpened(true); + if (status === 'loading') { + const id = setTimeout(() => { + notifications.show({ + title: 'Loading user...', + message: 'Please wait while we are loading your user data', + color: 'cyan', + loading: true, + }); + }, 0); + + return () => { + clearTimeout(id); + }; + } + + if (status === 'unauthenticated') { + // NOTE: A little trick to make to notification show up after the page is + // loaded, because if we don't do this, the notification will not show up + const id = setTimeout(() => { + notifications.show({ + title: 'Not authenticated', + message: 'You are not authenticated, redirecting to sign in page...', + color: 'red', + autoClose: 5000, + }); + }, 0); + + router.push('/auth/sign-in'); + + return () => { + clearTimeout(id); + }; } - }, [status]); + }, [status, router]); if (status === 'loading') { - return ( -
- - Checking user... -
- -
-
-
- ); + return null; } if (status === 'unauthenticated') { - return ( - <> - { - setOpened(false); - router.push('/auth/login'); - }} - opened={opened} - withCloseButton={false} - > - - - - - You are not logged in - - - - ); + return null; } + if (status === 'authenticated') { return <>{children}; }