-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
208 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import type { Submission } from '@conform-to/react'; | ||
import { useForm, parse, validateConstraint } from '@conform-to/react'; | ||
import type { ActionFunctionArgs } from 'react-router-dom'; | ||
import { useFetcher, json, redirect } from 'react-router-dom'; | ||
|
||
interface Login { | ||
email: string; | ||
password: string; | ||
remember: string; | ||
} | ||
|
||
async function isAuthenticated(email: string, password: string) { | ||
return new Promise((resolve) => { | ||
resolve(email === '[email protected]' && password === '12345'); | ||
}); | ||
} | ||
|
||
export async function action({ request }: ActionFunctionArgs) { | ||
const formData = await request.formData(); | ||
const submission = parse(formData); | ||
|
||
if ( | ||
!(await isAuthenticated( | ||
submission.payload.email, | ||
submission.payload.password, | ||
)) | ||
) { | ||
return json({ | ||
...submission, | ||
// '' denote the root which is treated as form error | ||
error: { '': 'Invalid credential' }, | ||
}); | ||
} | ||
|
||
return redirect('/'); | ||
} | ||
|
||
export function Component() { | ||
const fetcher = useFetcher<Submission>(); | ||
const [form, { email, password }] = useForm<Login>({ | ||
lastSubmission: fetcher.data, | ||
shouldRevalidate: 'onBlur', | ||
onValidate(context) { | ||
return validateConstraint(context); | ||
}, | ||
}); | ||
|
||
return ( | ||
<fetcher.Form method="post" {...form.props}> | ||
<div className="form-error">{form.error}</div> | ||
<label> | ||
<div>Email</div> | ||
<input | ||
className={email.error ? 'error' : ''} | ||
name="email" | ||
type="email" | ||
required | ||
pattern="[^@]+@[^@]+\.[^@]+" | ||
/> | ||
{email.error === 'required' ? ( | ||
<div>Email is required</div> | ||
) : email.error === 'type' || email.error === 'pattern' ? ( | ||
<div>Email is invalid</div> | ||
) : null} | ||
</label> | ||
<label> | ||
<div>Password</div> | ||
<input | ||
className={password.error ? 'error' : ''} | ||
name="password" | ||
type="password" | ||
required | ||
/> | ||
{password.error === 'required' ? <div>Password is required</div> : null} | ||
</label> | ||
<label> | ||
<div> | ||
<span>Remember me</span> | ||
<input name="remember" type="checkbox" value="yes" /> | ||
</div> | ||
</label> | ||
<hr /> | ||
<button>Login</button> | ||
</fetcher.Form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { conform, parse, useForm } from '@conform-to/react'; | ||
import type { ActionArgs } from '@remix-run/node'; | ||
import { json } from '@remix-run/node'; | ||
import { useFetcher } from '@remix-run/react'; | ||
|
||
interface SignupForm { | ||
email: string; | ||
password: string; | ||
confirmPassword: string; | ||
} | ||
|
||
function parseFormData(formData: FormData) { | ||
return parse<SignupForm>(formData, { | ||
resolve({ email, password, confirmPassword }) { | ||
const error: Record<string, string> = {}; | ||
|
||
if (!email) { | ||
error.email = 'Email is required'; | ||
} else if (!email.includes('@')) { | ||
error.email = 'Email is invalid'; | ||
} | ||
|
||
if (!password) { | ||
error.password = 'Password is required'; | ||
} | ||
|
||
if (!confirmPassword) { | ||
error.confirmPassword = 'Confirm password is required'; | ||
} else if (confirmPassword !== password) { | ||
error.confirmPassword = 'Password does not match'; | ||
} | ||
|
||
if (error.email || error.password || error.confirmPassword) { | ||
return { error }; | ||
} | ||
|
||
// Return the value only if no error | ||
return { | ||
value: { | ||
email, | ||
password, | ||
confirmPassword, | ||
}, | ||
}; | ||
}, | ||
}); | ||
} | ||
|
||
export async function action({ request }: ActionArgs) { | ||
const formData = await request.formData(); | ||
const submission = parseFormData(formData); | ||
|
||
/** | ||
* Signup only when the user click on the submit button and no error found | ||
*/ | ||
if (!submission.value || submission.intent !== 'submit') { | ||
// Always sends the submission state back to client until the user is signed up | ||
return json({ | ||
...submission, | ||
payload: { | ||
// Never send the password back to client | ||
email: submission.payload.email, | ||
}, | ||
}); | ||
} | ||
|
||
throw new Error('Not implemented'); | ||
} | ||
|
||
export default function Signup() { | ||
const fetcher = useFetcher<typeof action>(); | ||
// Last submission returned by the server | ||
const lastSubmission = fetcher.data; | ||
const [form, { email, password, confirmPassword }] = useForm({ | ||
// Sync the result of last submission | ||
lastSubmission, | ||
|
||
// Reuse the validation logic on the client | ||
onValidate({ formData }) { | ||
return parseFormData(formData); | ||
}, | ||
}); | ||
|
||
return ( | ||
<fetcher.Form method="post" {...form.props}> | ||
<div className="form-error">{form.error}</div> | ||
<div> | ||
<label>Email</label> | ||
<input | ||
className={email.error ? 'error' : ''} | ||
{...conform.input(email)} | ||
/> | ||
<div>{email.error}</div> | ||
</div> | ||
<div> | ||
<label>Password</label> | ||
<input | ||
className={password.error ? 'error' : ''} | ||
{...conform.input(password, { type: 'password' })} | ||
/> | ||
<div>{password.error}</div> | ||
</div> | ||
<div> | ||
<label>Confirm Password</label> | ||
<input | ||
className={confirmPassword.error ? 'error' : ''} | ||
{...conform.input(confirmPassword, { type: 'password' })} | ||
/> | ||
<div>{confirmPassword.error}</div> | ||
</div> | ||
<hr /> | ||
<button type="submit">Signup</button> | ||
</fetcher.Form> | ||
); | ||
} |