Skip to content

Commit

Permalink
Merge pull request #117 from DuckyMomo20012/dev
Browse files Browse the repository at this point in the history
fix: update UI & configs
  • Loading branch information
DuckyMomo20012 authored Dec 28, 2023
2 parents 4f4657f + faa293c commit 84cf207
Show file tree
Hide file tree
Showing 23 changed files with 606 additions and 394 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
"prepare": "husky install"
},
"dependencies": {
"@hookform/resolvers": "3.3.3",
"@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",
Expand Down
59 changes: 59 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
86 changes: 86 additions & 0 deletions src/components/forms/SignInForm.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof signInSchema>;

const SignInForm = () => {
const router = useRouter();

const {
setError,
register,
handleSubmit,
formState: { errors },
} = useForm<TSignInForm>({
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 (
<form className="h-full w-full" onSubmit={handleSubmit(onSubmit)}>
<Stack>
<TextInput
error={errors.email?.message}
label="Email"
withAsterisk
{...register('email')}
/>
<PasswordInput
error={errors.password?.message}
label="Password"
withAsterisk
{...register('password')}
/>

<Space h="md" />

<Group className="w-full" justify="center">
<Button type="submit">Sign in</Button>
</Group>
</Stack>
</form>
);
};

export { SignInForm };
114 changes: 114 additions & 0 deletions src/components/forms/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof signUpSchema>;

const SignUpForm = () => {
const router = useRouter();

const {
setError,
register,
handleSubmit,
formState: { errors },
} = useForm<TSignUpForm>({
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 (
<form className="h-full w-full" onSubmit={handleSubmit(onSubmit)}>
<Stack>
<TextInput
error={errors.email?.message}
label="Email"
withAsterisk
{...register('email')}
/>
<TextInput
error={errors.name?.message}
label="Name"
{...register('name')}
/>
<PasswordInput
error={errors.password?.message}
label="Password"
withAsterisk
{...register('password')}
/>
<PasswordInput
error={errors.confirmPassword?.message}
label="Confirm password"
withAsterisk
{...register('confirmPassword')}
/>

<Space h="md" />

<Group className="w-full" justify="center">
<Button type="submit">Sign up</Button>
</Group>
</Stack>
</form>
);
};

export { SignUpForm };
59 changes: 59 additions & 0 deletions src/components/layouts/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<MantineAppShell footer={{ height: 20 }} header={{ height: 60 }}>
<MantineAppShell.Header>
<Group className="h-full w-full items-center px-4" justify="flex-end">
<Anchor
data-test-id="github-link"
href="https://github.com/DuckyMomo20012/nextjs-template"
target="_blank"
>
<ActionIcon
aria-label="GitHub link"
role="link"
size="lg"
variant="outline"
>
<Icon icon="ant-design:github-filled" width={24} />
</ActionIcon>
</Anchor>
</Group>
</MantineAppShell.Header>

<MantineAppShell.Main className="flex flex-col">
{children}
</MantineAppShell.Main>

<MantineAppShell.Footer>
<Group className="h-full w-full items-center" justify="center">
<Text className="w-full text-center" size="sm">
Made with{' '}
<Icon
icon={`fluent-emoji-flat:${
dark ? 'teacup-without-handle' : 'sparkling-heart'
}`}
inline={true}
/>{' '}
by{' '}
<Anchor href="https://github.com/DuckyMomo20012">Tien Vinh</Anchor>
</Text>
</Group>
</MantineAppShell.Footer>
</MantineAppShell>
);
};

export { AppShell };
Original file line number Diff line number Diff line change
@@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading

1 comment on commit 84cf207

@vercel
Copy link

@vercel vercel bot commented on 84cf207 Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.