2023-02-14 10:15:44 +01:00
|
|
|
import type { ActionArgs, LoaderArgs, MetaFunction } from '@remix-run/node';
|
|
|
|
|
import { json, redirect } from '@remix-run/node';
|
2023-02-21 19:06:00 +01:00
|
|
|
import {
|
|
|
|
|
Form,
|
|
|
|
|
Link,
|
|
|
|
|
useActionData,
|
|
|
|
|
useLoaderData,
|
|
|
|
|
useSearchParams
|
|
|
|
|
} from '@remix-run/react';
|
2023-02-14 10:15:44 +01:00
|
|
|
import * as React from 'react';
|
|
|
|
|
import {
|
|
|
|
|
TextInput,
|
|
|
|
|
Box,
|
|
|
|
|
Group,
|
|
|
|
|
Button,
|
2023-02-21 19:06:00 +01:00
|
|
|
PasswordInput,
|
|
|
|
|
Title
|
2023-02-14 10:15:44 +01:00
|
|
|
} from '@mantine/core';
|
|
|
|
|
import { AtSign, Lock } from 'react-feather';
|
|
|
|
|
import { verifyLogin } from '~/models/user.server';
|
|
|
|
|
import { createUserSession, getUserId } from '~/session.server';
|
|
|
|
|
import { safeRedirect, validateEmail } from '~/utils';
|
2023-02-21 19:06:00 +01:00
|
|
|
import { isSignupAllowed } from '~/config.server';
|
2023-02-11 03:14:14 +01:00
|
|
|
|
|
|
|
|
export async function loader({ request }: LoaderArgs) {
|
2023-02-14 10:15:44 +01:00
|
|
|
const userId = await getUserId(request);
|
|
|
|
|
if (userId) return redirect('/time-entries');
|
2023-02-21 19:06:00 +01:00
|
|
|
|
|
|
|
|
return json({
|
|
|
|
|
ALLOW_USER_SIGNUP: isSignupAllowed()
|
|
|
|
|
});
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function action({ request }: ActionArgs) {
|
2023-02-14 10:15:44 +01:00
|
|
|
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');
|
2023-02-11 03:14:14 +01:00
|
|
|
|
|
|
|
|
if (!validateEmail(email)) {
|
2023-02-14 10:15:44 +01:00
|
|
|
return json(
|
|
|
|
|
{ errors: { email: 'Email is invalid', password: null } },
|
|
|
|
|
{ status: 400 }
|
|
|
|
|
);
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof password !== 'string' || password.length === 0) {
|
2023-02-14 10:15:44 +01:00
|
|
|
return json(
|
|
|
|
|
{ errors: { password: 'Password is required', email: null } },
|
|
|
|
|
{ status: 400 }
|
|
|
|
|
);
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (password.length < 8) {
|
2023-02-14 10:15:44 +01:00
|
|
|
return json(
|
|
|
|
|
{ errors: { password: 'Password is too short', email: null } },
|
|
|
|
|
{ status: 400 }
|
|
|
|
|
);
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
2023-02-14 10:15:44 +01:00
|
|
|
const user = await verifyLogin(email, password);
|
2023-02-11 03:14:14 +01:00
|
|
|
|
|
|
|
|
if (!user) {
|
2023-02-14 10:15:44 +01:00
|
|
|
return json(
|
2023-02-21 19:06:00 +01:00
|
|
|
{
|
|
|
|
|
errors: {
|
|
|
|
|
email: 'Invalid email or password',
|
|
|
|
|
password: 'Invalid email or password'
|
|
|
|
|
}
|
|
|
|
|
},
|
2023-02-14 10:15:44 +01:00
|
|
|
{ status: 400 }
|
|
|
|
|
);
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return createUserSession({
|
|
|
|
|
request,
|
|
|
|
|
userId: user.id,
|
|
|
|
|
remember: remember === 'on' ? true : false,
|
|
|
|
|
redirectTo
|
2023-02-14 10:15:44 +01:00
|
|
|
});
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const meta: MetaFunction = () => {
|
|
|
|
|
return {
|
2023-02-14 10:15:44 +01:00
|
|
|
title: 'Login | WorkTimer',
|
|
|
|
|
description:
|
|
|
|
|
'WorkTimer is a time tracking app. Helps you track your time spent on projects.'
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-02-11 03:14:14 +01:00
|
|
|
|
|
|
|
|
export default function LoginPage() {
|
2023-02-14 10:15:44 +01:00
|
|
|
const [searchParams] = useSearchParams();
|
|
|
|
|
const redirectTo = searchParams.get('redirectTo') || '/time-entries';
|
|
|
|
|
const actionData = useActionData<typeof action>();
|
2023-02-21 19:06:00 +01:00
|
|
|
const loaderData = useLoaderData<typeof loader>();
|
2023-02-14 10:15:44 +01:00
|
|
|
const emailRef = React.useRef<HTMLInputElement>(null);
|
|
|
|
|
const passwordRef = React.useRef<HTMLInputElement>(null);
|
2023-02-11 03:14:14 +01:00
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
if (actionData?.errors?.email) {
|
2023-02-14 10:15:44 +01:00
|
|
|
emailRef.current?.focus();
|
2023-02-11 03:14:14 +01:00
|
|
|
} else if (actionData?.errors?.password) {
|
2023-02-14 10:15:44 +01:00
|
|
|
passwordRef.current?.focus();
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|
2023-02-14 10:15:44 +01:00
|
|
|
}, [actionData]);
|
2023-02-11 03:14:14 +01:00
|
|
|
|
|
|
|
|
return (
|
2023-02-14 10:15:44 +01:00
|
|
|
<Box sx={{ maxWidth: 300 }} mx="auto">
|
2023-02-21 19:06:00 +01:00
|
|
|
<Title order={2} my="lg">
|
2023-02-18 23:45:47 +01:00
|
|
|
Login
|
2023-02-21 19:06:00 +01:00
|
|
|
</Title>
|
2023-02-14 10:15:44 +01:00
|
|
|
<Form method="post" noValidate>
|
|
|
|
|
<TextInput
|
|
|
|
|
mb={12}
|
|
|
|
|
withAsterisk
|
|
|
|
|
label="Email address"
|
|
|
|
|
placeholder="your@email.com"
|
|
|
|
|
icon={<AtSign size={16} />}
|
|
|
|
|
id="email"
|
|
|
|
|
ref={emailRef}
|
|
|
|
|
required
|
|
|
|
|
autoFocus={true}
|
|
|
|
|
name="email"
|
|
|
|
|
type="email"
|
|
|
|
|
autoComplete="email"
|
|
|
|
|
aria-invalid={actionData?.errors?.email ? true : undefined}
|
|
|
|
|
error={actionData?.errors?.email}
|
|
|
|
|
errorProps={{ children: actionData?.errors?.email }}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<PasswordInput
|
|
|
|
|
mb={12}
|
|
|
|
|
withAsterisk
|
2023-02-23 01:19:48 +01:00
|
|
|
required
|
2023-02-14 10:15:44 +01:00
|
|
|
label="Password"
|
|
|
|
|
id="password"
|
|
|
|
|
ref={passwordRef}
|
|
|
|
|
placeholder="********"
|
|
|
|
|
icon={<Lock size={16} />}
|
|
|
|
|
name="password"
|
|
|
|
|
type="password"
|
|
|
|
|
autoComplete="current-password"
|
|
|
|
|
aria-invalid={actionData?.errors?.password ? true : undefined}
|
|
|
|
|
error={actionData?.errors?.password ? true : undefined}
|
|
|
|
|
errorProps={{ children: actionData?.errors?.password }}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<input type="hidden" name="redirectTo" value={redirectTo} />
|
|
|
|
|
|
2023-02-21 19:06:00 +01:00
|
|
|
<Group position="center" mt="xl">
|
2023-02-14 10:15:44 +01:00
|
|
|
<Button type="submit">Log In</Button>
|
|
|
|
|
</Group>
|
2023-02-21 19:06:00 +01:00
|
|
|
|
|
|
|
|
{!!loaderData?.ALLOW_USER_SIGNUP && (
|
|
|
|
|
<Box
|
|
|
|
|
mt="md"
|
|
|
|
|
sx={{
|
|
|
|
|
textAlign: 'center'
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
New user?{' '}
|
|
|
|
|
<Link
|
|
|
|
|
to={{
|
|
|
|
|
pathname: '/signup',
|
|
|
|
|
search: searchParams.toString()
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<strong>Sign up</strong>
|
|
|
|
|
</Link>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
2023-02-14 10:15:44 +01:00
|
|
|
</Form>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
2023-02-11 03:14:14 +01:00
|
|
|
}
|