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

203 lines
6.2 KiB
TypeScript
Raw Normal View History

import type { AuthModalSearchParams } from './types'
2022-10-21 18:17:04 +00:00
import { clsx } from 'clsx'
2024-02-04 11:25:21 +00:00
import { Show, createSignal } from 'solid-js'
import { useLocalize } from '../../../context/localize'
2022-11-14 10:02:08 +00:00
import { useSession } from '../../../context/session'
import { useSnackbar } from '../../../context/snackbar'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/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'
2024-02-06 14:34:27 +00:00
import { capitalize } from '../../../utils/capitalize'
import styles from './AuthModal.module.scss'
2022-10-21 18:17:04 +00:00
type FormFields = {
email: string
password: string
}
type ValidationErrors = Partial<Record<keyof FormFields, string>>
export const LoginForm = () => {
2024-02-04 17:40:15 +00:00
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
2023-11-28 18:04:51 +00:00
const { t } = useLocalize()
2022-10-21 18:17:04 +00:00
const [submitError, setSubmitError] = createSignal('')
const [isSubmitting, setIsSubmitting] = createSignal(false)
2024-02-04 17:40:15 +00:00
const [password, setPassword] = createSignal('')
2022-10-21 18:17:04 +00:00
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
2022-10-25 16:25:42 +00:00
// TODO: better solution for interactive error messages
const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false)
const [isLinkSent, setIsLinkSent] = createSignal(false)
const authFormRef: { current: HTMLFormElement } = { current: null }
2024-02-04 17:40:15 +00:00
const { showSnackbar } = useSnackbar()
const { signIn } = useSession()
2022-10-21 18:17:04 +00:00
const handleEmailInput = (newEmail: string) => {
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
2024-01-13 14:22:04 +00:00
setEmail(newEmail.toLowerCase())
2022-10-21 18:17:04 +00:00
}
const handlePasswordInput = (newPassword: string) => {
setValidationErrors(({ password: _notNeeded, ...rest }) => rest)
setPassword(newPassword)
}
2024-02-05 15:04:23 +00:00
const handleSendLinkAgainClick = (event: Event) => {
2022-11-01 22:25:18 +00:00
event.preventDefault()
setIsLinkSent(true)
2022-11-01 22:25:18 +00:00
setIsEmailNotConfirmed(false)
setSubmitError('')
2023-12-25 05:31:26 +00:00
changeSearchParams({ mode: 'forgot-password' })
2024-01-27 06:21:48 +00:00
// NOTE: temporary solution, needs logic in authorizer
2023-12-24 08:16:41 +00:00
/* FIXME:
2024-02-04 17:40:15 +00:00
const { authorizer } = useSession()
2023-12-24 08:16:41 +00:00
const result = await authorizer().verifyEmail({ token })
if (!result) setSubmitError('cant sign send link')
*/
2022-11-01 22:25:18 +00:00
}
2022-10-21 18:17:04 +00:00
const handleSubmit = async (event: Event) => {
event.preventDefault()
2022-10-25 16:25:42 +00:00
setIsLinkSent(false)
2022-11-14 01:17:12 +00:00
setIsEmailNotConfirmed(false)
2022-10-21 18:17:04 +00:00
setSubmitError('')
const newValidationErrors: ValidationErrors = {}
2024-02-04 17:40:15 +00:00
const validateAndSetError = (field, message) => {
if (!field()) {
newValidationErrors[field.name] = t(message)
}
2022-10-21 18:17:04 +00:00
}
2024-02-04 17:40:15 +00:00
validateAndSetError(email, 'Please enter email')
validateAndSetError(() => validateEmail(email()), 'Invalid email')
validateAndSetError(password, 'Please enter password')
2022-10-21 18:17:04 +00:00
if (Object.keys(newValidationErrors).length > 0) {
setValidationErrors(newValidationErrors)
authFormRef.current
.querySelector<HTMLInputElement>(`input[name="${Object.keys(newValidationErrors)[0]}"]`)
2024-02-06 14:34:27 +00:00
?.focus()
2022-10-21 18:17:04 +00:00
return
}
setIsSubmitting(true)
try {
2024-02-01 20:34:53 +00:00
const { errors } = await signIn({ email: email(), password: password() })
if (errors?.length > 0) {
if (errors.some((error) => error.message.includes('bad user credentials'))) {
setValidationErrors((prev) => ({
...prev,
password: t('Something went wrong, check email and password'),
}))
} else {
setSubmitError(t('Error'))
}
return
}
2022-10-25 16:25:42 +00:00
hideModal()
2023-02-10 11:11:24 +00:00
showSnackbar({ body: t('Welcome!') })
2022-10-21 18:17:04 +00:00
} catch (error) {
2023-12-24 08:16:41 +00:00
console.error(error)
2022-10-21 18:17:04 +00:00
setSubmitError(error.message)
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef.current = el)}>
2023-05-18 20:02:19 +00:00
<div>
<AuthModalHeader modalType="login" />
2023-05-18 20:02:19 +00:00
<Show when={submitError()}>
<div class={styles.authInfo}>
<div class={styles.warn}>{submitError()}</div>
<Show when={isEmailNotConfirmed()}>
2024-01-27 06:21:48 +00:00
<span class={'link'} onClick={handleSendLinkAgainClick}>
2023-05-18 20:02:19 +00:00
{t('Send link again')}
2024-01-27 06:21:48 +00:00
</span>
2023-05-18 20:02:19 +00:00
</Show>
</div>
</Show>
<Show when={isLinkSent()}>
<div class={styles.authInfo}>{t('Link sent, check your email')}</div>
</Show>
<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)}
/>
<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-05-18 20:02:19 +00:00
</div>
2024-01-27 06:21:48 +00:00
<PasswordField variant={'login'} onInput={(value) => handlePasswordInput(value)} />
2024-02-01 20:34:53 +00:00
<Show when={validationErrors().password}>
<div class={styles.validationError} style={{ position: 'static', 'font-size': '1.4rem' }}>
{validationErrors().password}
</div>
</Show>
2023-05-18 20:02:19 +00:00
<div>
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
{isSubmitting() ? '...' : t('Enter')}
</button>
</div>
<div class={styles.authActions}>
<span
class="link"
onClick={() =>
changeSearchParams({
mode: 'forgot-password',
})
}
2023-05-18 20:02:19 +00:00
>
2024-02-06 14:34:27 +00:00
{t('Set the new password')}
</span>
2023-05-18 20:02:19 +00:00
</div>
2022-10-21 18:17:04 +00:00
</div>
2023-05-18 20:02:19 +00:00
<div>
<SocialProviders />
2022-10-21 18:17:04 +00:00
2023-05-18 20:02:19 +00:00
<div class={styles.authControl}>
<span
class={styles.authLink}
onClick={() =>
changeSearchParams({
mode: 'register',
})
}
>
2023-05-18 20:02:19 +00:00
{t('I have no account yet')}
</span>
</div>
2022-10-21 18:17:04 +00:00
</div>
</form>
)
}