Skip to content

Commit

Permalink
login + signup mockup page (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
cungminh2710 authored May 2, 2024
1 parent 053ea4d commit 705f3ce
Show file tree
Hide file tree
Showing 13 changed files with 479 additions and 22 deletions.
5 changes: 4 additions & 1 deletion apps/coderum-dev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@
"page:build": "npx @cloudflare/next-on-pages"
},
"dependencies": {
"@repo/ui": "workspace:*",
"@hookform/resolvers": "3.3.4",
"@libsql/client": "0.6.0",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@repo/ui": "workspace:*",
"@t3-oss/env-nextjs": "0.10.1",
"@tanstack/react-query": "5.32.1",
"@trpc/client": "next",
"@trpc/react-query": "next",
"@trpc/server": "next",
"drizzle-orm": "0.30.10",
"lucia": "3.2.0",
"lucide-react": "0.376.0",
"next": "14.2.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "7.51.3",
"server-only": "0.0.1",
"superjson": "2.2.1",
"zod": "3.23.5"
Expand Down
126 changes: 126 additions & 0 deletions apps/coderum-dev/src/app/auth/login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
'use client';

import ValidationMessage from '@/components/ValidationMessage';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@repo/ui/src/button';
import { FormControl, FormField, FormItem, Form } from '@repo/ui/src/form';
import { Input } from '@repo/ui/src/input';
import { Label } from '@repo/ui/src/label';
import Link from 'next/link';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { GithubIcon } from 'lucide-react';

type LoginFormInputs = {
email: string;
password: string;
};

const formSchema: z.ZodType<LoginFormInputs> = z.object({
email: z
.string({
required_error: 'Email is required',
invalid_type_error: 'Email must be a string',
})
.email({ message: 'Please enter an email' }),
password: z
.string({
required_error: 'Password is required',
invalid_type_error: 'Password must be a string',
})
.min(1, { message: 'Please enter your password' }),
});

export const LoginForm = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
password: '',
},
});

const {
formState: { errors },
} = form;

return (
<div className='mx-auto grid w-80 gap-6 lg:w-96'>
<div className='grid gap-2 text-center'>
<h1 className='text-3xl font-bold'>Login</h1>
<p className='text-muted-foreground text-balance'>Enter your email below to login to your account</p>
</div>
<Form {...form}>
<form className='grid gap-4'>
<div className='grid gap-2'>
<Label htmlFor='email'>Email</Label>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem className='w-full'>
<FormControl>
<Input
{...field}
placeholder='[email protected]'
required
/>
</FormControl>
</FormItem>
)}
/>
<ValidationMessage errorMessage={errors.email?.message} />
</div>
<div className='grid gap-2'>
<div className='flex items-center'>
<Label htmlFor='password'>Password</Label>
<Link
href='/auth/forgot-password'
className='ml-auto inline-block text-sm underline'
>
Forgot your password?
</Link>
</div>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem className='w-full'>
<FormControl>
<Input
{...field}
required
/>
</FormControl>
</FormItem>
)}
/>
<ValidationMessage errorMessage={errors.password?.message} />
</div>
<Button
type='submit'
className='w-full'
>
Login
</Button>
<Button
variant='outline'
className='w-full space-x-1'
>
<GithubIcon className='h-4 w-4' />
<span>Login with GitHub</span>
</Button>
</form>
</Form>
<div className='mt-4 space-x-1 text-center text-sm'>
<span>Don&apos;t have an account?</span>
<Link
href='/auth/signup'
className='underline'
>
Sign up
</Link>
</div>
</div>
);
};
25 changes: 25 additions & 0 deletions apps/coderum-dev/src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Metadata } from 'next';
import { LoginForm } from './LoginForm';

export const metadata: Metadata = {
title: 'Log In - Coderum',
};

export default function LoginPage() {
return (
<div className='min-h-screen w-full lg:grid lg:grid-cols-2'>
<div className='flex items-center justify-center py-12'>
<LoginForm />
</div>
<div className='bg-muted hidden lg:block'>
<img
src='/placeholder.svg'
alt='Image'
width='1920'
height='1080'
className='h-full w-full object-cover dark:brightness-[0.2] dark:grayscale'
/>
</div>
</div>
);
}
29 changes: 29 additions & 0 deletions apps/coderum-dev/src/app/auth/signup/PasswordCheckItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const PasswordCheckItem = (props: { isPassed: boolean; message: string }) => {
if (props.isPassed) {
return (
<li className='space-x-2 text-green-700'>
<svg
xmlns='http://www.w3.org/2000/svg'
width={12}
height={12}
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth={3}
strokeLinecap='round'
strokeLinejoin='round'
className='-ml-1 mr-2 inline-block'
>
<polyline points='20 6 9 17 4 12' />
</svg>
{props.message}
</li>
);
}
return (
<li className='text-muted-foreground flex items-center'>
<div className='bg-muted-foreground mr-3 inline-block h-1 w-1 rounded-full'></div>
{props.message}
</li>
);
};
185 changes: 185 additions & 0 deletions apps/coderum-dev/src/app/auth/signup/SignUpForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@repo/ui/src/button';
import { Form, FormField, FormItem, FormControl } from '@repo/ui/src/form';
import { Input } from '@repo/ui/src/input';
import { Label } from '@repo/ui/src/label';
import Link from 'next/link';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { PasswordCheckItem } from './PasswordCheckItem';
import ValidationMessage from '@/components/ValidationMessage';
import { GithubIcon } from 'lucide-react';

type RegisterFormInputs = {
username: string;
email: string;
password: string;
};

const passwordMixedUpperLowerCaseValidator = (password: string) => /^(?=.*[a-z])(?=.*[A-Z]).+$/.test(password);
const passwordHasNumberValidator = (password: string) => /.*\d.*/.test(password);

const formSchema: z.ZodType<RegisterFormInputs> = z.object({
username: z
.string({
required_error: 'User name is required',
invalid_type_error: 'User name must be a string',
})
.min(8, { message: 'User name must have at least 8 characters' }),
email: z
.string({
required_error: 'Email is required',
invalid_type_error: 'Email must be a string',
})
.email({ message: 'Please enter an email' }),
password: z
.string({
required_error: 'Password is required',
invalid_type_error: 'Password must be a string',
})
.min(8, { message: 'Password must have at least 8 characters' })
.refine(
(value) => passwordMixedUpperLowerCaseValidator(value),
'Password should be the mix of uppercase & lowercase letters',
)
.refine((value) => passwordHasNumberValidator(value), 'Password should contain at least 1 number'),
});

export const SignUpForm = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
email: '',
password: '',
},
});

const {
formState: { errors },
} = form;

return (
<Form {...form}>
<form className='flex items-center justify-center py-12'>
<div className='mx-auto grid w-80 gap-6 lg:w-96'>
<div className='grid gap-2 text-center'>
<h1 className='text-3xl font-bold'>Sign Up</h1>
<p className='text-muted-foreground text-balance'>Create your account and start using Coderum for free</p>
</div>
<div className='grid gap-4'>
<Label htmlFor='username'>Username</Label>
<div
dir='ltr'
className='focus-within:ring-ring group relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2'
>
<div className='bg-muted h-9 rounded-l-md border border-r-0 px-3'>
<div className='flex min-h-[2.25rem] flex-col justify-center text-sm leading-7'>
<span className='flex whitespace-nowrap'>
<span className='text-muted-foreground inline-flex items-center rounded-none text-sm'>
coderum.dev/@
</span>
</span>
</div>
</div>
<FormField
control={form.control}
name='username'
render={({ field }) => (
<FormItem className='w-full'>
<FormControl>
<Input
placeholder='username'
className='focus:ring-ring my-0 block h-9 w-full rounded-md rounded-l-none border border-l-0 ring-0 focus:outline-none focus:ring-2'
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<ValidationMessage errorMessage={errors.username?.message} />
</div>
<div className='grid gap-2'>
<Label htmlFor='email'>Email</Label>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem className='w-full'>
<FormControl>
<Input
{...field}
placeholder='[email protected]'
required
/>
</FormControl>
</FormItem>
)}
/>
<ValidationMessage errorMessage={errors.email?.message} />
</div>
<div className='grid gap-2'>
<Label htmlFor='password'>Password</Label>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem className='w-full'>
<FormControl>
<Input
{...field}
required
/>
</FormControl>
</FormItem>
)}
/>
<ValidationMessage errorMessage={errors.password?.message} />

<div className='text-muted-foreground mt-2 flex items-center text-sm'>
<ul className='ml-4 list-none'>
<PasswordCheckItem
isPassed={passwordMixedUpperLowerCaseValidator(form.watch('password'))}
message={'Mix of uppercase & lowercase letters'}
/>
<PasswordCheckItem
isPassed={form.watch('password').length >= 8}
message={'Minimum 8 characters long'}
/>
<PasswordCheckItem
isPassed={passwordHasNumberValidator(form.watch('password'))}
message={'Contain at least 1 number'}
/>
</ul>
</div>
</div>
<Button
type='submit'
className='w-full'
>
Sign Up
</Button>
<Button
variant='outline'
className='w-full space-x-1'
>
<GithubIcon className='h-4 w-4' />
<span>Login with GitHub</span>
</Button>
</div>
<div className='mt-4 space-x-1 text-center text-sm'>
<span>Already have an account?</span>
<Link
href='/auth/login'
className='underline'
>
Login
</Link>
</div>
</div>
</form>
</Form>
);
};
Loading

0 comments on commit 705f3ce

Please sign in to comment.