Skip to content

Commit

Permalink
sign up updates
Browse files Browse the repository at this point in the history
  • Loading branch information
sspenst committed May 23, 2024
1 parent 8b91e09 commit 857d039
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 394 deletions.
2 changes: 1 addition & 1 deletion components/forms/forgotPasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function ForgotPasswordForm() {
<FormTemplate title='Reset your password'>
<form className='flex flex-col gap-6' onSubmit={onSubmit}>
<div>
<label className='block mb-2' htmlFor='email'>Email</label>
<label className='block text-sm font-medium mb-2' htmlFor='email'>Email</label>
<input required onChange={e => setEmail(e.target.value)} value={email} className='w-full' id='email' type='email' placeholder='Email' />
</div>
<button className='bg-blue-500 hover:bg-blue-600 text-white w-full font-medium py-2 px-3 rounded' disabled={isSent} type='submit'>Continue</button>
Expand Down
4 changes: 2 additions & 2 deletions components/forms/loginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ export default function LoginForm() {
<FormTemplate title='Log in with your Thinky.gg account'>
<form className='flex flex-col gap-6' onSubmit={onSubmit}>
<div>
<label className='block mb-2' htmlFor='username'>Username or Email</label>
<label className='block text-sm font-medium mb-2' htmlFor='username'>Username or Email</label>
<input onChange={e => setName(e.target.value)} className='w-full' id='username' type='text' placeholder='Username or Email' />
</div>
<div>
<div className='flex justify-between gap-2 flex-wrap mb-2'>
<label htmlFor='password'>Password</label>
<label className='text-sm font-medium' htmlFor='password'>Password</label>
<Link
className='font-medium text-sm text-blue-500 hover:text-blue-400'
href='/forgot-password'
Expand Down
4 changes: 2 additions & 2 deletions components/forms/resetPasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ export default function ResetPasswordForm({ token, userId }: ResetPasswordFormPr
<FormTemplate title='Reset your password'>
<form className='flex flex-col gap-6' onSubmit={onSubmit}>
<div>
<label className='block mb-2' htmlFor='password'>
<label className='block text-sm font-medium mb-2' htmlFor='password'>
Password
</label>
<input onChange={e => setPassword(e.target.value)} className='w-full' id='password' type='password' placeholder='Password' />
</div>
<div>
<label className='block mb-2' htmlFor='password2'>
<label className='block text-sm font-medium mb-2' htmlFor='password2'>
Re-enter password
</label>
<input onChange={e => setPassword2(e.target.value)} className='w-full' id='password2' type='password' placeholder='Password' />
Expand Down
196 changes: 123 additions & 73 deletions components/forms/signupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,22 @@ import { useRouter } from 'next/router';
import React, { useContext, useRef, useState } from 'react';
import ReCAPTCHA from 'react-google-recaptcha';
import toast from 'react-hot-toast';
import StepWizard, { StepWizardChildProps, StepWizardProps } from 'react-step-wizard';
import { useSWRConfig } from 'swr';
import { debounce } from 'throttle-debounce';
import { AppContext } from '../../contexts/appContext';
import LoadingSpinner from '../page/loadingSpinner';
import FormTemplate from './formTemplate';

interface SignupFormProps {
recaptchaPublicKey?: string;
}

export default function SignupForm({ recaptchaPublicKey }: SignupFormProps) {
export default function SignupForm() {
const { cache } = useSWRConfig();
const [email, setEmail] = useState<string>('');
const { mutateUser, setShouldAttemptAuth } = useContext(AppContext);
const [password, setPassword] = useState<string>('');
const recaptchaRef = useRef<ReCAPTCHA>(null);
const [recaptchaToken, setRecaptchaToken] = useState<string>('');
const router = useRouter();
const [showRecaptcha, setShowRecaptcha] = useState<boolean>(false);
const [username, setUsername] = useState<string>('');

function onRecaptchaChange(value: string | null) {
if (value) {
setRecaptchaToken(value);
}
}

function onSubmit(event: React.FormEvent) {
event.preventDefault();

Expand All @@ -38,26 +29,6 @@ export default function SignupForm({ recaptchaPublicKey }: SignupFormProps) {
return;
}

if (username.length < 3 || username.length > 50) {
toast.dismiss();
toast.error('Username must be between 3 and 50 characters');

return;
}

if (username.match(/[^-a-zA-Z0-9_]/)) {
toast.dismiss();
toast.error('Username can only contain letters, numbers, underscores, and hyphens');

return;
}

if (!showRecaptcha && recaptchaPublicKey) {
setShowRecaptcha(true);

return;
}

toast.dismiss();
toast.loading('Registering...');

Expand All @@ -71,7 +42,6 @@ export default function SignupForm({ recaptchaPublicKey }: SignupFormProps) {
name: username,
password: password,
tutorialCompletedAt: parseInt(tutorialCompletedAt),
recaptchaToken: recaptchaToken,
utm_source: utm_source
}),
credentials: 'include',
Expand Down Expand Up @@ -115,51 +85,131 @@ export default function SignupForm({ recaptchaPublicKey }: SignupFormProps) {
});
}

const [isValidUsername, setIsValidUsername] = useState<boolean>(false);
const [wizard, setWizard] = useState<StepWizardProps>();
const [usernameExists, setUsernameExists] = useState<boolean>(false);
const [isExistsLoading, setIsExistsLoading] = useState<boolean>(false);

const checkUsername = async (username: string) => {
const res = await fetch(`/api/user/exists?name=${username}`);
const resObj = await res.json();

setIsExistsLoading(false);
setUsernameExists(resObj.exists);
};

// debounce the checkUsername function
const debouncedCheckUsername = useRef(
debounce(500, checkUsername)
).current;

// check if username is valid
const handleUsernameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newUserName = e.target.value;

setUsername(newUserName);

let valid = true;

if (newUserName.length < 3 || newUserName.length > 50 || newUserName.match(/[^-a-zA-Z0-9_]/)) {
valid = false;
}

setIsValidUsername(valid);

if (!valid) {
setIsExistsLoading(false);

return;
}

setIsExistsLoading(true);
debouncedCheckUsername(newUserName);
};

return (
<FormTemplate>
<form className='flex flex-col gap-4' onSubmit={onSubmit}>
<div>
<label className='block text-sm font-bold mb-2 ' htmlFor='username'>
Username
</label>
<input required onChange={e => setUsername(e.target.value)} className='shadow appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline' id='username' type='text' placeholder='Username' />
</div>
<div>
<label className='block text-sm font-bold mb-2' htmlFor='email'>
Email
</label>
<input required onChange={e => setEmail(e.target.value)} value={email} className='shadow appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline' id='email' type='email' placeholder='Email' />
</div>
<div>
<label className='block text-sm font-bold mb-2' htmlFor='password'>
Password
</label>
<input onChange={e => setPassword(e.target.value)} className='shadow appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline' id='password' type='password' placeholder='Password' />
</div>
{recaptchaPublicKey && showRecaptcha && (
<div className='w-full pt-2'>
<ReCAPTCHA
size='normal'
onChange={onRecaptchaChange}
ref={recaptchaRef}
sitekey={recaptchaPublicKey ?? ''}
/>
<FormTemplate title='Create your Thinky.gg account'>
<form className='flex flex-col gap-6' onSubmit={onSubmit}>
<StepWizard className='w-full' instance={setWizard}>
<div className='flex flex-col gap-6'>
<div>
<div className='flex justify-between gap-2 flex-wrap mb-2'>
<label className='text-sm font-medium' htmlFor='username'>Username</label>
{username.length >= 3 &&
<div className='flex items-center text-sm gap-2'>
{isExistsLoading ? <LoadingSpinner size={20} /> :
isValidUsername && !usernameExists ?
<div className='text-sm' style={{
color: 'var(--color-complete)',
}}>
Username is available
</div>
:
<div className='text-red-500 text-sm'>
{!isValidUsername ? 'Username is not valid' : 'Username is not available'}
</div>
}
</div>
}
</div>
<input required onChange={e => handleUsernameChange(e)} className='w-full' id='username' type='text' placeholder='Username' />
</div>
<button
className='bg-blue-500 enabled:hover:bg-blue-600 text-white w-full font-medium py-2 px-3 rounded disabled:opacity-50'
disabled={isExistsLoading || !isValidUsername || usernameExists}
onClick={() => (wizard as StepWizardChildProps)?.nextStep()}
>
Continue
</button>
</div>
)}
<div className='flex items-center justify-between gap-1'>
<input type='checkbox' id='terms_agree_checkbox' required />
<label htmlFor='terms_agree_checkbox' className='text-xs p-1'>
I agree to the <a className='underline' href='https://docs.google.com/document/d/e/2PACX-1vR4E-RcuIpXSrRtR3T3y9begevVF_yq7idcWWx1A-I9w_VRcHhPTkW1A7DeUx2pGOcyuKifEad3Qokn/pub' rel='noreferrer' target='_blank'>terms of service</a> and reviewed the <a className='underline' href='https://docs.google.com/document/d/e/2PACX-1vSNgV3NVKlsgSOEsnUltswQgE8atWe1WCLUY5fQUVjEdu_JZcVlRkZcpbTOewwe3oBNa4l7IJlOnUIB/pub' rel='noreferrer' target='_blank'>privacy policy</a>.
</label>
</div>
<div className='flex flex-wrap gap-y-4 items-center justify-between'>
<input className='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline cursor-pointer' type='submit' value='Sign Up' />
<div className='flex flex-col gap-6'>
<div className='flex gap-2'>
<span>
Welcome, <span className='font-bold'>{username}</span>
</span>
<button onClick={() => (wizard as StepWizardChildProps)?.previousStep()}>
<svg xmlns='http://www.w3.org/2000/svg' className='w-5 h-5 gray hover-color' viewBox='1 1 22 22' strokeWidth='1.5' stroke='currentColor' fill='none' strokeLinecap='round' strokeLinejoin='round'>
<path stroke='none' d='M0 0h24v24H0z' fill='none' />
<path d='M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4' />
<path d='M13.5 6.5l4 4' />
</svg>
</button>
</div>
<div>
<label className='block text-sm font-medium mb-2' htmlFor='email'>Email</label>
<input required onChange={e => setEmail(e.target.value)} value={email} className='w-full' id='email' type='email' placeholder='Email' />
</div>
<div>
<label className='block text-sm font-medium mb-2' htmlFor='password'>Password</label>
<input required onChange={e => setPassword(e.target.value)} className='w-full' id='password' type='password' placeholder='Password' />
</div>
<div className='flex gap-3'>
<input type='checkbox' id='terms_agree_checkbox' required />
<label htmlFor='terms_agree_checkbox' className='text-xs'>
I agree to the <a className='underline' href='https://docs.google.com/document/d/e/2PACX-1vR4E-RcuIpXSrRtR3T3y9begevVF_yq7idcWWx1A-I9w_VRcHhPTkW1A7DeUx2pGOcyuKifEad3Qokn/pub' rel='noreferrer' target='_blank'>terms of service</a> and reviewed the <a className='underline' href='https://docs.google.com/document/d/e/2PACX-1vSNgV3NVKlsgSOEsnUltswQgE8atWe1WCLUY5fQUVjEdu_JZcVlRkZcpbTOewwe3oBNa4l7IJlOnUIB/pub' rel='noreferrer' target='_blank'>privacy policy</a>.
</label>
</div>
<button className='bg-blue-500 hover:bg-blue-600 text-white w-full font-medium py-2 px-3 rounded' type='submit'>Sign up</button>
</div>
</StepWizard>
<div className='flex flex-col gap-4 items-center'>
<Link
className='inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-400'
className='font-medium text-sm text-blue-500 hover:text-blue-400'
href='/play-as-guest'
>
Play as Guest
Play as guest
</Link>
<div className='text-center text-sm'>
<span>
{'Already have an account? '}
</span>
<Link
className='font-medium text-sm text-blue-500 hover:text-blue-400'
href='/login'
>
Log in
</Link>
</div>
</div>
</form>
</FormTemplate>
Expand Down
Loading

0 comments on commit 857d039

Please sign in to comment.