work-timer/app/routes/login.tsx
2023-02-11 03:14:14 +01:00

147 lines
4.8 KiB
TypeScript

import type { ActionArgs, LoaderArgs, MetaFunction } from '@remix-run/node'
import { json, redirect } from '@remix-run/node'
import { Form, Link, useActionData, useSearchParams } from '@remix-run/react'
import * as React from 'react'
import { verifyLogin } from '~/models/user.server'
import { createUserSession, getUserId } from '~/session.server'
import { safeRedirect, validateEmail } from '~/utils'
export async function loader({ request }: LoaderArgs) {
const userId = await getUserId(request)
if (userId) return redirect('/')
return json({})
}
export async function action({ request }: ActionArgs) {
const formData = await request.formData()
const email = formData.get('email')
const password = formData.get('password')
const redirectTo = safeRedirect(formData.get('redirectTo'), '/')
const remember = formData.get('remember')
if (!validateEmail(email)) {
return json({ errors: { email: 'Email is invalid', password: null } }, { status: 400 })
}
if (typeof password !== 'string' || password.length === 0) {
return json({ errors: { password: 'Password is required', email: null } }, { status: 400 })
}
if (password.length < 8) {
return json({ errors: { password: 'Password is too short', email: null } }, { status: 400 })
}
const user = await verifyLogin(email, password)
if (!user) {
return json({ errors: { email: 'Invalid email or password', password: null } }, { status: 400 })
}
return createUserSession({
request,
userId: user.id,
remember: remember === 'on' ? true : false,
redirectTo
})
}
export const meta: MetaFunction = () => {
return {
title: 'Login'
}
}
export default function LoginPage() {
const [searchParams] = useSearchParams()
const redirectTo = searchParams.get('redirectTo') || '/notes'
const actionData = useActionData<typeof action>()
const emailRef = React.useRef<HTMLInputElement>(null)
const passwordRef = React.useRef<HTMLInputElement>(null)
React.useEffect(() => {
if (actionData?.errors?.email) {
emailRef.current?.focus()
} else if (actionData?.errors?.password) {
passwordRef.current?.focus()
}
}, [actionData])
return (
<div className="flex min-h-full flex-col justify-center">
<div className="mx-auto w-full max-w-md px-8">
<Form method="post" className="space-y-6" noValidate>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email address
</label>
<div className="mt-1">
<input
ref={emailRef}
id="email"
required
autoFocus={true}
name="email"
type="email"
autoComplete="email"
aria-invalid={actionData?.errors?.email ? true : undefined}
aria-describedby="email-error"
className="w-full rounded border border-gray-500 px-2 py-1 text-lg"
/>
{actionData?.errors?.email && (
<div className="pt-1 text-red-700" id="email-error">
{actionData.errors.email}
</div>
)}
</div>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<div className="mt-1">
<input
id="password"
ref={passwordRef}
name="password"
type="password"
autoComplete="current-password"
aria-invalid={actionData?.errors?.password ? true : undefined}
aria-describedby="password-error"
className="w-full rounded border border-gray-500 px-2 py-1 text-lg"
/>
{actionData?.errors?.password && (
<div className="pt-1 text-red-700" id="password-error">
{actionData.errors.password}
</div>
)}
</div>
</div>
<input type="hidden" name="redirectTo" value={redirectTo} />
<button
type="submit"
className="w-full rounded bg-blue-500 py-2 px-4 text-white hover:bg-blue-600 focus:bg-blue-400"
>
Log in
</button>
<div className="flex items-center justify-between">
<div className="flex items-center">
<input
id="remember"
name="remember"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="remember" className="ml-2 block text-sm text-gray-900">
Remember me
</label>
</div>
</div>
</Form>
</div>
</div>
)
}