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

203 lines
6.2 KiB
TypeScript
Raw Normal View History

2022-10-21 18:17:04 +00:00
import styles from './AuthModal.module.scss'
import { clsx } from 'clsx'
import { SocialProviders } from './SocialProviders'
import { ApiError } from '../../../utils/apiClient'
2022-10-31 16:40:55 +00:00
import { createSignal, Show } from 'solid-js'
2022-10-21 18:17:04 +00:00
import { email, setEmail } from './sharedLogic'
import { useRouter } from '../../../stores/router'
import type { AuthModalSearchParams } from './types'
2023-02-17 09:21:02 +00:00
import { hideModal } from '../../../stores/ui'
2022-11-14 10:02:08 +00:00
import { useSession } from '../../../context/session'
import { signSendLink } from '../../../stores/auth'
import { validateEmail } from '../../../utils/validateEmail'
import { generateModalTitleFromSource } from '../../../utils/custom-i18n'
2023-02-17 09:21:02 +00:00
2023-02-10 11:11:24 +00:00
import { useSnackbar } from '../../../context/snackbar'
2023-02-17 09:21:02 +00:00
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
2022-10-21 18:17:04 +00:00
type FormFields = {
email: string
password: string
}
type ValidationErrors = Partial<Record<keyof FormFields, string>>
export const LoginForm = () => {
2023-02-17 09:21:02 +00:00
const { t, lang } = useLocalize()
2022-10-21 18:17:04 +00:00
const [submitError, setSubmitError] = createSignal('')
const [isSubmitting, setIsSubmitting] = createSignal(false)
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 [showPassword, setShowPassword] = createSignal(false)
2022-10-21 18:17:04 +00:00
2023-02-10 11:11:24 +00:00
const {
actions: { showSnackbar }
} = useSnackbar()
const {
actions: { signIn }
2022-11-14 10:02:08 +00:00
} = useSession()
2022-10-21 18:17:04 +00:00
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
const [password, setPassword] = createSignal('')
const handleEmailInput = (newEmail: string) => {
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
setEmail(newEmail)
}
const handlePasswordInput = (newPassword: string) => {
setValidationErrors(({ password: _notNeeded, ...rest }) => rest)
setPassword(newPassword)
}
2022-11-02 07:25:36 +00:00
const handleSendLinkAgainClick = async (event: Event) => {
2022-11-01 22:25:18 +00:00
event.preventDefault()
setIsEmailNotConfirmed(false)
setSubmitError('')
setIsLinkSent(true)
2023-02-17 09:21:02 +00:00
const result = await signSendLink({ email: email(), lang: lang(), template: 'email_confirmation' })
2022-11-02 07:25:36 +00:00
if (result.error) setSubmitError(result.error)
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 = {}
if (!email()) {
newValidationErrors.email = t('Please enter email')
} else if (!validateEmail(email())) {
2022-10-21 18:17:04 +00:00
newValidationErrors.email = t('Invalid email')
}
if (!password()) {
newValidationErrors.password = t('Please enter password')
}
if (Object.keys(newValidationErrors).length > 0) {
setValidationErrors(newValidationErrors)
return
}
setIsSubmitting(true)
try {
await signIn({ email: email(), password: password() })
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) {
if (error instanceof ApiError) {
if (error.code === 'email_not_confirmed') {
setSubmitError(t('Please, confirm email'))
2022-10-25 16:25:42 +00:00
setIsEmailNotConfirmed(true)
2022-10-21 18:17:04 +00:00
return
}
if (error.code === 'user_not_found') {
setSubmitError(t('Something went wrong, check email and password'))
return
}
}
setSubmitError(error.message)
} finally {
setIsSubmitting(false)
}
}
return (
2023-05-18 20:02:19 +00:00
<form onSubmit={handleSubmit} class={styles.authForm}>
<div>
<h4>{generateModalTitleFromSource('login')}</h4>
2023-05-18 20:02:19 +00:00
<Show when={submitError()}>
<div class={styles.authInfo}>
<div class={styles.warn}>{submitError()}</div>
<Show when={isEmailNotConfirmed()}>
<a href="#" onClick={handleSendLinkAgainClick}>
{t('Send link again')}
</a>
</Show>
</div>
</Show>
<Show when={isLinkSent()}>
<div class={styles.authInfo}>{t('Link sent, check your email')}</div>
</Show>
<div class="pretty-form__item">
<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>
</div>
<Show when={validationErrors().email}>
<div class={styles.validationError}>{validationErrors().email}</div>
</Show>
<div class="pretty-form__item">
<input
id="password"
name="password"
autocomplete="password"
type={showPassword() ? 'text' : 'password'}
placeholder={t('Password')}
onInput={(event) => handlePasswordInput(event.currentTarget.value)}
/>
<label for="password">{t('Password')}</label>
<button
type="button"
class={styles.passwordToggle}
onClick={() => setShowPassword(!showPassword())}
>
<Icon class={styles.passwordToggleIcon} name={showPassword() ? 'eye-off' : 'eye'} />
</button>
2022-10-21 18:17:04 +00:00
</div>
2023-05-18 20:02:19 +00:00
<Show when={validationErrors().password}>
<div class={styles.validationError}>{validationErrors().password}</div>
</Show>
<div>
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
{isSubmitting() ? '...' : t('Enter')}
</button>
</div>
<div class={styles.authActions}>
<a
href="#"
onClick={(ev) => {
ev.preventDefault()
changeSearchParam('mode', 'forgot-password')
}}
>
{t('Forgot password?')}
</a>
</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={() => changeSearchParam('mode', 'register')}>
{t('I have no account yet')}
</span>
</div>
2022-10-21 18:17:04 +00:00
</div>
</form>
)
}