export interface LoginActionData {
username: string;
password: string;
}
export const action = async ({ request }: ActionArgs) => {
const formData = await request.formData();
const { username, password } = Object.fromEntries(formData);
// const { username, password } = fields;
const redirectTo = '/todos';
invariant(typeof username === 'string', `${username} must be a string`);
invariant(typeof password === 'string', `${password} must be a string`);
// check input type is string
validateStringInputType({ username, password });
// create field error message
const fieldErrors: LoginActionData = {
username: validateUsername(username),
password: validatePassword(password),
};
// check field has any error
const arrayedObj = Object.values(fieldErrors);
if (arrayedObj.some(Boolean)) {
return badRequest({ fieldErrors, ...{ username, password } });
}
const user = await login({ username, password });
if (!user) {
return badRequest({
fields: { username, password },
formError: `Username/Password combination is incorrect`,
});
}
return createUserSession(user.id, redirectTo);
};
export default function LoginRoute() {
const actionData = useActionData() as AuthBadRequestResponse;
const { classes } = authStyles();
const { wrapper, label, input, errorInput, errorMessage, button } = classes;
const { setActionData } = useAuthUX();
useEffect(() => {
if (!actionData) return;
setActionData(actionData);
}, [setActionData, actionData]);
return (
<Form method="post">
<Box className={wrapper}>
<Space h={20} />
<Input.Label htmlFor="username-input" mt={4} className={label}>
<Input
id="username-input"
name="username"
type="text"
defaultValue={actionData?.fields?.username}
aria-label="username"
aria-invalid={
Boolean(actionData?.fieldErrors?.username) ||
Boolean(actionData?.formError)
}
aria-errormessage={
(actionData?.fieldErrors?.username && 'username-error') ||
(actionData?.formError && 'User does not exist.')
}
placeholder="username"
className={input}
//@ts-ignore
styles={{
input:
(actionData?.fieldErrors?.username || actionData?.formError) &&
errorInput,
}}
/>
<Input.Error className={errorMessage}>
{actionData?.fieldErrors?.username || actionData?.formError}{' '}
</Input.Error>
</Input.Label>
<Input.Label htmlFor="password-input" mt={4} className={label}>
<Input
id="password-input"
name="password"
type="password"
defaultValue={actionData?.fields?.password}
aria-label="password"
aria-invalid={
Boolean(actionData?.fieldErrors?.password) ||
Boolean(actionData?.formError)
}
aria-errormessage={
(actionData?.fieldErrors?.password && 'password-error') ||
(actionData?.formError && 'User does not exist.')
}
placeholder="password"
className={input}
//@ts-ignore
styles={{
input:
(actionData?.fieldErrors?.password || actionData?.formError) &&
errorInput,
}}
/>
<Input.Error className={errorMessage}>
{actionData?.fieldErrors?.password || actionData?.formError}{' '}
</Input.Error>
</Input.Label>
<Button type="submit" variant="gradient" className={button} mt={8}>
Log in
</Button>
<Space h={60} />
</Box>
</Form>
);
}