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

277 lines
8.6 KiB
TypeScript
Raw Normal View History

2024-06-24 17:50:27 +00:00
import { clsx } from 'clsx'
2022-10-25 16:25:42 +00:00
import type { JSX } from 'solid-js'
2024-02-08 17:30:27 +00:00
import { Show, createMemo, createSignal } from 'solid-js'
2024-06-24 17:50:27 +00:00
import { useSearchParams } from '@solidjs/router'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
2024-06-24 17:50:27 +00:00
import { useUI } from '~/context/ui'
import { validateEmail } from '~/utils/validateEmail'
import { AuthModalHeader } from './AuthModalHeader'
import { PasswordField } from './PasswordField'
import { SocialProviders } from './SocialProviders'
2024-02-04 11:25:21 +00:00
import { email, setEmail } from './sharedLogic'
import styles from './AuthModal.module.scss'
2024-02-08 15:37:17 +00:00
type EmailStatus = 'not verified' | 'verified' | 'registered' | ''
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>>
export const RegisterForm = () => {
2024-06-24 17:50:27 +00:00
const [, changeSearchParams] = useSearchParams()
const { hideModal } = useUI()
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
2024-02-06 14:34:27 +00:00
const { signUp, isRegistered, resendVerifyEmail } = useSession()
2024-05-01 14:41:59 +00:00
// FIXME: use submit error data or remove signal
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)
const [isSuccess, setIsSuccess] = createSignal(false)
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
const [passwordError, setPasswordError] = createSignal<string>()
2024-02-06 14:34:27 +00:00
const [emailStatus, setEmailStatus] = createSignal<string>('')
2022-10-25 16:25:42 +00:00
2024-06-24 17:50:27 +00:00
let authFormRef: HTMLFormElement
const handleNameInput = (newName: string) => {
setFullName(newName)
2022-10-25 16:25:42 +00:00
}
const handleSubmit = async (event: Event) => {
event.preventDefault()
if (passwordError()) {
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
2023-08-21 11:11:18 +00:00
} 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)
2024-02-06 14:34:27 +00:00
const isValid = createMemo(() => Object.keys(newValidationErrors).length === 0)
2024-06-24 17:50:27 +00:00
if (!isValid() && authFormRef) {
authFormRef
.querySelector<HTMLInputElement>(`input[name="${Object.keys(newValidationErrors)[0]}"]`)
2024-06-24 17:50:27 +00:00
?.focus()
2022-10-25 16:25:42 +00:00
return
}
setIsSubmitting(true)
try {
2023-12-24 08:16:41 +00:00
const opts = {
2023-11-28 18:04:51 +00:00
given_name: cleanName,
email: cleanEmail,
password: password(),
2023-11-28 18:04:51 +00:00
confirm_password: password(),
2024-06-26 08:22:05 +00:00
redirect_uri: window?.location?.origin || ''
2023-12-24 08:16:41 +00:00
}
2024-06-24 17:50:27 +00:00
const success = await signUp(opts)
setIsSuccess(success)
2024-02-08 15:37:17 +00:00
} catch (error) {
console.error(error)
} finally {
setIsSubmitting(false)
}
}
2024-06-24 17:50:27 +00:00
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const handleResendLink = async (_ev: any) => {
const success: boolean = await resendVerifyEmail({
2024-02-08 18:36:05 +00:00
email: email(),
2024-06-26 08:22:05 +00:00
identifier: 'basic_signup'
2024-02-08 18:36:05 +00:00
})
2024-06-24 17:50:27 +00:00
setIsSuccess(success)
2024-02-08 18:36:05 +00:00
}
2024-02-08 15:37:17 +00:00
const handleCheckEmailStatus = (status: EmailStatus | string) => {
switch (status) {
2024-05-01 14:33:37 +00:00
case 'not verified': {
2024-02-01 20:34:53 +00:00
setValidationErrors((prev) => ({
...prev,
email: (
<>
2024-02-08 15:37:17 +00:00
{t('This email is not verified')},{' '}
2024-02-08 18:36:05 +00:00
<span class="link" onClick={handleResendLink}>
2024-02-08 15:37:17 +00:00
{t('resend confirmation link')}
2024-02-01 20:34:53 +00:00
</span>
</>
2024-06-26 08:22:05 +00:00
)
2024-02-01 20:34:53 +00:00
}))
2024-02-08 15:37:17 +00:00
break
2024-05-01 14:33:37 +00:00
}
case 'verified': {
setValidationErrors((_prev) => ({
2024-02-15 17:49:12 +00:00
email: (
<>
{t('This email is registered')}. {t('try')}
{', '}
2024-02-15 18:09:26 +00:00
<span class="link" onClick={() => changeSearchParams({ mode: 'login' })}>
{t('enter')}
</span>
2024-02-15 17:49:12 +00:00
</>
2024-06-26 08:22:05 +00:00
)
2024-02-08 15:37:17 +00:00
}))
break
2024-05-01 14:33:37 +00:00
}
case 'registered': {
2024-02-15 17:49:12 +00:00
setValidationErrors((prev) => ({
...prev,
2024-02-08 15:37:17 +00:00
email: (
<>
2024-02-17 13:25:25 +00:00
{t('This email is registered')}
{'. '}
2024-02-08 15:37:17 +00:00
<span class="link" onClick={() => changeSearchParams({ mode: 'send-reset-link' })}>
2024-02-17 13:25:25 +00:00
{t('Set the new password')}
2024-02-08 15:37:17 +00:00
</span>
</>
2024-06-26 08:22:05 +00:00
)
2024-02-08 15:37:17 +00:00
}))
break
2024-05-01 14:33:37 +00:00
}
default: {
2024-02-08 17:30:27 +00:00
console.info('[RegisterForm] email is not registered')
2024-02-08 15:37:17 +00:00
break
2024-05-01 14:33:37 +00:00
}
2024-02-08 15:37:17 +00:00
}
}
const handleEmailBlur = async () => {
if (validateEmail(email())) {
const checkResult = await isRegistered(email())
2024-02-08 17:30:27 +00:00
setEmailStatus(checkResult)
2024-02-08 15:37:17 +00:00
handleCheckEmailStatus(checkResult)
2022-10-25 16:25:42 +00:00
}
}
const handleEmailInput = (newEmail: string) => {
setEmailStatus('')
setValidationErrors({})
setEmail(newEmail.toLowerCase())
}
2022-10-25 16:25:42 +00:00
return (
<>
<Show when={!isSuccess()}>
2024-06-24 17:50:27 +00:00
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef = el)}>
2023-05-18 20:02:19 +00:00
<div>
<AuthModalHeader modalType="register" />
<div
class={clsx('pretty-form__item', {
2024-06-26 08:22:05 +00:00
'pretty-form__item--error': validationErrors().fullName
})}
>
2023-05-18 20:02:19 +00:00
<input
name="fullName"
type="text"
2024-02-08 17:42:07 +00:00
disabled={Boolean(emailStatus())}
2023-05-18 20:02:19 +00:00
placeholder={t('Full name')}
2024-02-08 15:37:17 +00:00
autocomplete="one-time-code"
2024-02-17 13:25:25 +00:00
onChange={(event) => handleNameInput(event.currentTarget.value)}
2023-05-18 20:02:19 +00:00
/>
<label for="name">{t('Full name')}</label>
2024-02-08 17:42:07 +00:00
<Show when={validationErrors().fullName && !emailStatus()}>
2023-08-30 21:30:15 +00:00
<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', {
2024-06-26 08:22:05 +00:00
'pretty-form__item--error': validationErrors().email && !emailStatus()
})}
>
2023-05-18 20:02:19 +00:00
<input
id="email"
name="email"
2024-02-08 15:37:17 +00:00
autocomplete="one-time-code"
2023-05-18 20:02:19 +00:00
type="email"
placeholder={t('Email')}
onInput={(event) => handleEmailInput(event.currentTarget.value)}
onBlur={handleEmailBlur}
/>
<label for="email">{t('Email')}</label>
<Show when={validationErrors().email || emailStatus()}>
<div class={clsx(styles.validationError, { info: Boolean(emailStatus()) })}>
{validationErrors().email}
</div>
</Show>
2022-10-25 16:25:42 +00:00
</div>
2023-08-30 21:30:15 +00:00
2024-02-15 18:09:26 +00:00
<PasswordField
disableAutocomplete={true}
disabled={Boolean(emailStatus())}
2024-02-17 13:25:25 +00:00
errorMessage={(err) => !emailStatus() && setPasswordError(err)}
onInput={(value) => setPassword(emailStatus() ? '' : value)}
2024-02-15 18:09:26 +00:00
/>
2023-05-18 20:02:19 +00:00
<div>
2024-02-15 18:09:26 +00:00
<button
class={clsx('button', styles.submitButton)}
disabled={isSubmitting() || Boolean(emailStatus())}
type="submit"
2024-02-15 17:49:12 +00:00
>
2024-02-15 18:09:26 +00:00
{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={() =>
changeSearchParams({
2024-06-26 08:22:05 +00:00
mode: 'login'
})
}
>
2023-05-18 20:02:19 +00:00
{t('I have an account')}
</span>
</div>
2022-10-25 16:25:42 +00:00
</div>
</form>
</Show>
<Show when={isSuccess()}>
<div style={{ 'justify-content': 'center' }}>
<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>
2022-10-25 16:25:42 +00:00
</div>
</Show>
</>
)
}