webapp/src/components/AuthModal/LoginForm.tsx
2024-07-21 05:17:42 +03:00

208 lines
6.0 KiB
TypeScript

import { clsx } from 'clsx'
import { JSX, Show, createSignal } from 'solid-js'
import { useLocalize } from '~/context/localize'
import { useSession } from '~/context/session'
import { useSnackbar, useUI } from '~/context/ui'
import { validateEmail } from '~/utils/validate'
import { AuthModalHeader } from './AuthModalHeader'
import { PasswordField } from './PasswordField'
import { SocialProviders } from './SocialProviders'
import { email, setEmail } from './sharedLogic'
import { useSearchParams } from '@solidjs/router'
import styles from './AuthModal.module.scss'
type FormFields = {
email: string
password: string
}
type ValidationErrors = Partial<Record<keyof FormFields, string>>
export const LoginForm = () => {
const { hideModal } = useUI()
const [, setSearchParams] = useSearchParams()
const { t } = useLocalize()
const [submitError, setSubmitError] = createSignal<string | JSX.Element>()
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [password, setPassword] = createSignal('')
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
// FIXME: use signal or remove
const [_isLinkSent, setIsLinkSent] = createSignal(false)
let authFormRef: HTMLFormElement
const { showSnackbar } = useSnackbar()
const { signIn, authError } = useSession()
const handleEmailInput = (newEmail: string) => {
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
setEmail(newEmail.toLowerCase())
}
const handlePasswordInput = (newPassword: string) => {
setValidationErrors(({ password: _notNeeded, ...rest }) => rest)
setPassword(newPassword)
}
const handleSendLinkAgainClick = (event: Event) => {
event.preventDefault()
setIsLinkSent(true)
setSubmitError()
setSearchParams({ mode: 'send-confirm-email' })
}
const preSendValidate = async (value: string, type: 'email' | 'password'): Promise<boolean> => {
if (type === 'email') {
if (value === '' || !validateEmail(value)) {
setValidationErrors((prev) => ({
...prev,
email: t('Invalid email')
}))
return false
}
} else if (type === 'password') {
if (value === '') {
setValidationErrors((prev) => ({
...prev,
password: t('Please enter password')
}))
return false
}
}
return true
}
const handleSubmit = async (event: Event) => {
event.preventDefault()
await preSendValidate(email(), 'email')
await preSendValidate(password(), 'password')
setIsLinkSent(false)
setSubmitError()
if (Object.keys(validationErrors()).length > 0) {
authFormRef
.querySelector<HTMLInputElement>(`input[name="${Object.keys(validationErrors())[0]}"]`)
?.focus()
return
}
setIsSubmitting(true)
try {
const success = await signIn({ email: email(), password: password() })
if (!success) {
switch (authError()) {
case 'user has not signed up email & password':
case 'bad user credentials': {
setValidationErrors((prev) => ({
...prev,
password: t('Something went wrong, check email and password')
}))
break
}
case 'user not found': {
setValidationErrors((prev) => ({ ...prev, email: t('User was not found') }))
break
}
case 'email not verified': {
setValidationErrors((prev) => ({ ...prev, email: t('This email is not verified') }))
break
}
default:
setSubmitError(
<div class={styles.info}>
{t('Error', authError())}
{'. '}
<span class={'link'} onClick={handleSendLinkAgainClick}>
{t('Send link again')}
</span>
</div>
)
}
}
hideModal()
showSnackbar({ body: t('Welcome!') })
} catch (error) {
console.error(error)
setSubmitError(authError())
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef = el)}>
<div>
<AuthModalHeader modalType="login" />
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email
})}
>
<input
id="email"
name="email"
autocomplete="email"
type="email"
value={email()}
placeholder={t('Email')}
onInput={(event) => handleEmailInput(event.currentTarget.value)}
/>
<label for="email">{t('Email')}</label>
<Show when={validationErrors().email}>
<div class={styles.validationError}>{validationErrors().email}</div>
</Show>
</div>
<PasswordField
variant={'login'}
setError={validationErrors().password}
onBlur={(value) => handlePasswordInput(value)}
/>
<Show when={submitError()}>
<div class={clsx('form-message--error', styles.validationError)}>{submitError()}</div>
</Show>
<div>
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
{isSubmitting() ? '...' : t('Enter')}
</button>
</div>
<div class={styles.authActions}>
<span
class="link"
onClick={() =>
setSearchParams({
mode: 'send-reset-link'
})
}
>
{t('Forgot password?')}
</span>
</div>
</div>
<div>
<SocialProviders />
<div class={styles.authControl}>
<span
class={styles.authLink}
onClick={() =>
setSearchParams({
mode: 'register'
})
}
>
{t('I have no account yet')}
</span>
</div>
</div>
</form>
)
}