webapp/src/components/Nav/AuthModal/RegisterForm.tsx

268 lines
8.6 KiB
TypeScript
Raw Normal View History

2022-10-31 16:40:55 +00:00
import { Show, createSignal } from 'solid-js'
2022-10-25 16:25:42 +00:00
import type { JSX } from 'solid-js'
import styles from './AuthModal.module.scss'
import { clsx } from 'clsx'
import { SocialProviders } from './SocialProviders'
import { ApiError } from '../../../utils/apiClient'
import { email, setEmail } from './sharedLogic'
import { useRouter } from '../../../stores/router'
import type { AuthModalSearchParams } from './types'
import { hideModal } from '../../../stores/ui'
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
2022-11-14 10:02:08 +00:00
import { register } from '../../../stores/auth'
2023-02-17 09:21:02 +00:00
import { useLocalize } from '../../../context/localize'
import { validateEmail } from '../../../utils/validateEmail'
import { AuthModalHeader } from './AuthModalHeader'
2023-08-21 11:11:18 +00:00
import { Icon } from '../../_shared/Icon'
2022-10-25 16:25:42 +00:00
type FormFields = {
fullName: string
2022-10-25 16:25:42 +00:00
email: string
password: string
}
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
const handleEmailInput = (newEmail: string) => {
setEmail(newEmail)
}
2022-10-25 16:25:42 +00:00
export const RegisterForm = () => {
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
const { emailChecks } = useEmailChecks()
2022-10-25 16:25:42 +00:00
const [submitError, setSubmitError] = createSignal('')
const [fullName, setFullName] = createSignal('')
2022-10-25 16:25:42 +00:00
const [password, setPassword] = createSignal('')
const [isSubmitting, setIsSubmitting] = createSignal(false)
2023-08-21 11:11:18 +00:00
const [showPassword, setShowPassword] = createSignal(false)
2022-10-25 16:25:42 +00:00
const [isSuccess, setIsSuccess] = createSignal(false)
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
const authFormRef: { current: HTMLFormElement } = { current: null }
2022-10-25 16:25:42 +00:00
const handleEmailBlur = () => {
if (validateEmail(email())) {
2022-10-25 16:25:42 +00:00
checkEmail(email())
}
}
function isValidPassword(passwordToCheck) {
const minLength = 8
const hasNumber = /\d/
const hasSpecial = /[!#$%&*@^]/
if (passwordToCheck.length < minLength) {
return t('Password should be at least 8 characters')
}
if (!hasNumber.test(passwordToCheck)) {
return t('Password should contain at least one number')
}
if (!hasSpecial.test(passwordToCheck)) {
return t('Password should contain at least one special character: !@#$%^&*')
}
return null
}
2022-10-25 16:25:42 +00:00
const handlePasswordInput = (newPassword: string) => {
setPassword(newPassword)
}
const handleNameInput = (newPasswordCopy: string) => {
setFullName(newPasswordCopy)
2022-10-25 16:25:42 +00:00
}
const handleSubmit = async (event: Event) => {
event.preventDefault()
2023-08-21 11:11:18 +00:00
const passwordError = isValidPassword(password())
if (passwordError) {
setValidationErrors((errors) => ({ ...errors, password: passwordError }))
} else {
setValidationErrors(({ password: _notNeeded, ...rest }) => rest)
}
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
setValidationErrors(({ fullName: _notNeeded, ...rest }) => rest)
2022-10-25 16:25:42 +00:00
setSubmitError('')
const newValidationErrors: ValidationErrors = {}
const cleanName = fullName().trim()
const cleanEmail = email().trim()
if (!cleanName) {
newValidationErrors.fullName = t('Please enter a name to sign your comments and publication')
2022-10-25 16:25:42 +00:00
}
if (!cleanEmail) {
2022-10-25 16:25:42 +00:00
newValidationErrors.email = t('Please enter email')
} else if (!validateEmail(email())) {
2022-10-25 16:25:42 +00:00
newValidationErrors.email = t('Invalid email')
}
if (!password()) {
newValidationErrors.password = t('Please enter password')
}
setValidationErrors(newValidationErrors)
const emailCheckResult = await checkEmail(cleanEmail)
2022-10-25 16:25:42 +00:00
const isValid = Object.keys(newValidationErrors).length === 0 && !emailCheckResult
if (!isValid) {
authFormRef.current
.querySelector<HTMLInputElement>(`input[name="${Object.keys(newValidationErrors)[0]}"]`)
.focus()
2022-10-25 16:25:42 +00:00
return
}
setIsSubmitting(true)
try {
await register({
name: cleanName,
email: cleanEmail,
2022-10-25 16:25:42 +00:00
password: password()
})
setIsSuccess(true)
} catch (error) {
if (error instanceof ApiError && error.code === 'user_already_exists') {
return
}
setSubmitError(error.message)
} finally {
setIsSubmitting(false)
}
}
return (
<>
<Show when={!isSuccess()}>
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef.current = el)}>
2023-05-18 20:02:19 +00:00
<div>
<AuthModalHeader modalType="register" />
2023-05-18 20:02:19 +00:00
<Show when={submitError()}>
<div class={styles.authInfo}>
<ul>
<li class={styles.warn}>{submitError()}</li>
</ul>
</div>
</Show>
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().fullName
})}
>
2023-05-18 20:02:19 +00:00
<input
name="fullName"
type="text"
placeholder={t('Full name')}
autocomplete=""
onInput={(event) => handleNameInput(event.currentTarget.value)}
/>
<label for="name">{t('Full name')}</label>
2023-08-30 21:30:15 +00:00
<Show when={validationErrors().fullName}>
<div class={styles.validationError}>{validationErrors().fullName}</div>
</Show>
2022-10-25 16:25:42 +00:00
</div>
2023-08-30 21:30:15 +00:00
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email
})}
>
2023-05-18 20:02:19 +00:00
<input
id="email"
name="email"
autocomplete="email"
type="email"
value={email()}
placeholder={t('Email')}
onInput={(event) => handleEmailInput(event.currentTarget.value)}
onBlur={handleEmailBlur}
/>
<label for="email">{t('Email')}</label>
2023-08-30 21:30:15 +00:00
<Show when={validationErrors().email}>
<div class={styles.validationError}>{validationErrors().email}</div>
</Show>
2023-09-04 21:50:48 +00:00
<Show when={emailChecks()[email()]}>
<div class={styles.validationError}>
{t("This email is already taken. If it's you")},{' '}
<a
href="#"
onClick={(event) => {
event.preventDefault()
changeSearchParam('mode', 'login')
}}
>
{t('enter')}
</a>
</div>
</Show>
2022-10-25 16:25:42 +00:00
</div>
2023-08-30 21:30:15 +00:00
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().password
})}
>
2023-05-18 20:02:19 +00:00
<input
id="password"
name="password"
autocomplete="current-password"
2023-08-21 11:11:18 +00:00
type={showPassword() ? 'text' : 'password'}
2023-05-18 20:02:19 +00:00
placeholder={t('Password')}
onInput={(event) => handlePasswordInput(event.currentTarget.value)}
/>
<label for="password">{t('Password')}</label>
2023-08-21 11:11:18 +00:00
<button
type="button"
class={styles.passwordToggle}
onClick={() => setShowPassword(!showPassword())}
>
<Icon class={styles.passwordToggleIcon} name={showPassword() ? 'eye-off' : 'eye'} />
</button>
2023-08-30 21:30:15 +00:00
<Show when={validationErrors().password}>
<div class={clsx(styles.registerPassword, styles.validationError)}>
{validationErrors().password}
</div>
</Show>
2023-05-18 20:02:19 +00:00
</div>
<div>
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
{isSubmitting() ? '...' : t('Join')}
</button>
</div>
2022-10-25 16:25:42 +00:00
</div>
2023-05-18 20:02:19 +00:00
<div>
<SocialProviders />
2022-10-25 16:25:42 +00:00
2023-05-18 20:02:19 +00:00
<div class={styles.authControl}>
<span class={styles.authLink} onClick={() => changeSearchParam('mode', 'login')}>
{t('I have an account')}
</span>
</div>
2022-10-25 16:25:42 +00:00
</div>
</form>
</Show>
<Show when={isSuccess()}>
<div class={styles.title}>{t('Almost done! Check your email.')}</div>
<div class={styles.text}>{t("We've sent you a message with a link to enter our website.")}</div>
<div>
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
{t('Back to main page')}
</button>
</div>
</Show>
</>
)
}