parent
d113d9ca8a
commit
37c27cc09c
|
@ -65,6 +65,7 @@
|
|||
"Can write and edit text directly, and accept or reject suggestions from others": "Can write and edit text directly, and accept or reject suggestions from others",
|
||||
"Cancel": "Cancel",
|
||||
"Cancel changes": "Cancel changes",
|
||||
"Change password": "Change password",
|
||||
"Characters": "Знаков",
|
||||
"Chat Title": "Chat Title",
|
||||
"Choose a post type": "Choose a post type",
|
||||
|
@ -131,6 +132,7 @@
|
|||
"Email": "Mail",
|
||||
"Enter": "Enter",
|
||||
"Enter URL address": "Enter URL address",
|
||||
"Enter a new password": "Enter a new password",
|
||||
"Enter footnote text": "Enter footnote text",
|
||||
"Enter image description": "Enter image description",
|
||||
"Enter image title": "Enter image title",
|
||||
|
@ -253,6 +255,7 @@
|
|||
"NotificationNewReplyText2": "from",
|
||||
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
|
||||
"Notifications": "Notifications",
|
||||
"Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password": "Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password",
|
||||
"Or paste a link to an image": "Or paste a link to an image",
|
||||
"Ordered list": "Ordered list",
|
||||
"Our regular contributor": "Our regular contributor",
|
||||
|
@ -266,6 +269,7 @@
|
|||
"Password should be at least 8 characters": "Password should be at least 8 characters",
|
||||
"Password should contain at least one number": "Password should contain at least one number",
|
||||
"Password should contain at least one special character: !@#$%^&*": "Password should contain at least one special character: !@#$%^&*",
|
||||
"Password updated!": "Password updated!",
|
||||
"Passwords are not equal": "Passwords are not equal",
|
||||
"Paste Embed code": "Paste Embed code",
|
||||
"Personal": "Personal",
|
||||
|
@ -426,6 +430,7 @@
|
|||
"Write to us": "Write to us",
|
||||
"Write your colleagues name or email": "Write your colleague's name or email",
|
||||
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats",
|
||||
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
|
||||
"You were successfully authorized": "You were successfully authorized",
|
||||
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses",
|
||||
"You've confirmed email": "You've confirmed email",
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
"Can write and edit text directly, and accept or reject suggestions from others": "Может писать и редактировать текст напрямую, а также принимать или отклонять предложения других",
|
||||
"Cancel": "Отмена",
|
||||
"Cancel changes": "Отменить изменения",
|
||||
"Change password": "Сменить пароль",
|
||||
"Characters": "Знаков",
|
||||
"Chat Title": "Тема дискурса",
|
||||
"Choose a post type": "Выберите тип публикации",
|
||||
|
@ -137,6 +138,7 @@
|
|||
"Email": "Почта",
|
||||
"Enter": "Войти",
|
||||
"Enter URL address": "Введите адрес ссылки",
|
||||
"Enter a new password": "Введите новый пароль",
|
||||
"Enter footnote text": "Введите текст сноски",
|
||||
"Enter image description": "Введите описание изображения",
|
||||
"Enter image title": "Введите название изображения",
|
||||
|
@ -265,6 +267,7 @@
|
|||
"NotificationNewReplyText2": "от",
|
||||
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
||||
"Notifications": "Уведомления",
|
||||
"Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password": "Теперь можете ввести новый пароль, он должен содержать минимум 8 символов и не совпадать с предыдущим паролем",
|
||||
"Or paste a link to an image": "Или вставьте ссылку на изображение",
|
||||
"Ordered list": "Нумерованный список",
|
||||
"Our regular contributor": "Наш постоянный автор",
|
||||
|
@ -278,6 +281,7 @@
|
|||
"Password should be at least 8 characters": "Пароль должен быть не менее 8 символов",
|
||||
"Password should contain at least one number": "Пароль должен содержать хотя бы одну цифру",
|
||||
"Password should contain at least one special character: !@#$%^&*": "Пароль должен содержать хотя бы один спецсимвол: !@#$%^&*",
|
||||
"Password updated!": "Пароль обновлен!",
|
||||
"Passwords are not equal": "Пароли не совпадают",
|
||||
"Paste Embed code": "Вставьте embed код",
|
||||
"Personal": "Личные",
|
||||
|
@ -447,6 +451,7 @@
|
|||
"Write to us": "Напишите нам",
|
||||
"Write your colleagues name or email": "Напишите имя или e-mail коллеги",
|
||||
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac",
|
||||
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
|
||||
"You was successfully authorized": "Вы были успешно авторизованы",
|
||||
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах",
|
||||
"You've confirmed email": "Вы подтвердили почту",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
background: #fff;
|
||||
min-height: 550px;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
min-height: 600px;
|
||||
|
@ -106,6 +107,10 @@
|
|||
|
||||
margin-top: 1.6rem;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: #9fa1a7;
|
||||
|
@ -125,7 +130,7 @@
|
|||
.submitButton {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
margin-top: 32px;
|
||||
margin-top: 36px;
|
||||
padding: 1.6rem !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -183,10 +188,6 @@
|
|||
line-height: 16px;
|
||||
margin-top: 0.3em;
|
||||
|
||||
&.registerPassword {
|
||||
margin-bottom: -32px;
|
||||
}
|
||||
|
||||
/* Red/500 */
|
||||
color: #d00820;
|
||||
|
||||
|
@ -201,26 +202,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.passwordToggle {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.passwordToggleIcon {
|
||||
height: 1.6em;
|
||||
display: inline-block;
|
||||
margin-right: 0.2em;
|
||||
max-width: 1.4em;
|
||||
max-height: 1.4em;
|
||||
transition: filter 0.2s;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 26px;
|
||||
line-height: 32px;
|
||||
|
|
103
src/components/Nav/AuthModal/ChangePasswordForm.tsx
Normal file
103
src/components/Nav/AuthModal/ChangePasswordForm.tsx
Normal file
|
@ -0,0 +1,103 @@
|
|||
import type { AuthModalSearchParams } from './types'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, JSX, Show } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useRouter } from '../../../stores/router'
|
||||
import { hideModal } from '../../../stores/ui'
|
||||
|
||||
import { PasswordField } from './PasswordField'
|
||||
|
||||
import styles from './AuthModal.module.scss'
|
||||
|
||||
type FormFields = {
|
||||
password: string
|
||||
}
|
||||
|
||||
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
||||
|
||||
export const ChangePasswordForm = () => {
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
const { t } = useLocalize()
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||
const [newPassword, setNewPassword] = createSignal<string>()
|
||||
const [passwordError, setPasswordError] = createSignal<string>()
|
||||
const [isSuccess, setIsSuccess] = createSignal(false)
|
||||
|
||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||
|
||||
const handleSubmit = async (event: Event) => {
|
||||
event.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
// Fake change password logic
|
||||
console.log('!!! sent new password:', newPassword)
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
setIsSuccess(true)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handlePasswordInput = (value) => {
|
||||
setNewPassword(value)
|
||||
if (passwordError()) {
|
||||
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
||||
} else {
|
||||
setValidationErrors(({ password: _notNeeded, ...rest }) => rest)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={!isSuccess()}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
class={clsx(styles.authForm, styles.authFormForgetPassword)}
|
||||
ref={(el) => (authFormRef.current = el)}
|
||||
>
|
||||
<div>
|
||||
<h4>{t('Enter a new password')}</h4>
|
||||
<div class={styles.authSubtitle}>
|
||||
{t(
|
||||
'Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password',
|
||||
)}
|
||||
</div>
|
||||
|
||||
<PasswordField
|
||||
errorMessage={(err) => setPasswordError(err)}
|
||||
onInput={(value) => handlePasswordInput(value)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
|
||||
{isSubmitting() ? '...' : t('Change password')}
|
||||
</button>
|
||||
</div>
|
||||
<div class={styles.authControl}>
|
||||
<span
|
||||
class={styles.authLink}
|
||||
onClick={() =>
|
||||
changeSearchParams({
|
||||
mode: 'login',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
<Show when={isSuccess()}>
|
||||
<div class={styles.title}>{t('Password updated!')}</div>
|
||||
<div class={styles.text}>{t('You can now login using your new password')}</div>
|
||||
<div>
|
||||
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
|
||||
{t('Back to main page')}
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -11,9 +11,9 @@ import { useRouter } from '../../../stores/router'
|
|||
import { hideModal } from '../../../stores/ui'
|
||||
import { ApiError } from '../../../utils/apiClient'
|
||||
import { validateEmail } from '../../../utils/validateEmail'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
|
||||
import { AuthModalHeader } from './AuthModalHeader'
|
||||
import { PasswordField } from './PasswordField'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
import { SocialProviders } from './SocialProviders'
|
||||
|
||||
|
@ -35,7 +35,6 @@ export const LoginForm = () => {
|
|||
// TODO: better solution for interactive error messages
|
||||
const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false)
|
||||
const [isLinkSent, setIsLinkSent] = createSignal(false)
|
||||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
|
||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||
|
||||
|
@ -166,31 +165,7 @@ export const LoginForm = () => {
|
|||
</Show>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={clsx('pretty-form__item', {
|
||||
'pretty-form__item--error': validationErrors().password,
|
||||
})}
|
||||
>
|
||||
<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>
|
||||
<Show when={validationErrors().password}>
|
||||
<div class={styles.validationError}>{validationErrors().password}</div>
|
||||
</Show>
|
||||
</div>
|
||||
<PasswordField onInput={(value) => handlePasswordInput(value)} />
|
||||
|
||||
<div>
|
||||
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
|
||||
|
@ -208,6 +183,16 @@ export const LoginForm = () => {
|
|||
>
|
||||
{t('Forgot password?')}
|
||||
</span>
|
||||
<span
|
||||
class="link"
|
||||
onClick={() =>
|
||||
changeSearchParams({
|
||||
mode: 'change-password',
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('Change password')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
.PassportField {
|
||||
.passwordToggle {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.passwordToggleIcon {
|
||||
height: 1.6em;
|
||||
display: inline-block;
|
||||
margin-right: 0.2em;
|
||||
max-width: 1.4em;
|
||||
max-height: 1.4em;
|
||||
transition: filter 0.2s;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.validationError {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin-top: 0.3em;
|
||||
|
||||
&.registerPassword {
|
||||
margin-bottom: -32px;
|
||||
}
|
||||
|
||||
/* Red/500 */
|
||||
color: #d00820;
|
||||
|
||||
a {
|
||||
color: #d00820;
|
||||
border-color: #d00820;
|
||||
|
||||
&:hover {
|
||||
color: var(--default-color-invert);
|
||||
border-color: var(--background-color-invert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
src/components/Nav/AuthModal/PasswordField/PasswordField.tsx
Normal file
88
src/components/Nav/AuthModal/PasswordField/PasswordField.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createEffect, createSignal, JSX, on, Show } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../../context/localize'
|
||||
import { resetSortedArticles } from '../../../../stores/zine/articles'
|
||||
import { Icon } from '../../../_shared/Icon'
|
||||
|
||||
import styles from './PasswordField.module.scss'
|
||||
|
||||
type Props = {
|
||||
class?: string
|
||||
errorMessage?: (error: string) => void
|
||||
onInput: (value: string) => void
|
||||
}
|
||||
|
||||
export const PasswordField = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
const [error, setError] = createSignal<string>()
|
||||
|
||||
const validatePassword = (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
|
||||
}
|
||||
|
||||
const handleInputChange = (value) => {
|
||||
props.onInput(value)
|
||||
const errorValue = validatePassword(value)
|
||||
if (errorValue) {
|
||||
setError(errorValue)
|
||||
} else {
|
||||
setError()
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => error(),
|
||||
() => {
|
||||
props.errorMessage(error())
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.PassportField, props.class)}>
|
||||
<div
|
||||
class={clsx('pretty-form__item', {
|
||||
'pretty-form__item--error': error(),
|
||||
})}
|
||||
>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
type={showPassword() ? 'text' : 'password'}
|
||||
placeholder={t('Password')}
|
||||
onInput={(event) => handleInputChange(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>
|
||||
<Show when={error()}>
|
||||
<div class={clsx(styles.registerPassword, styles.validationError)}>{error()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
1
src/components/Nav/AuthModal/PasswordField/index.ts
Normal file
1
src/components/Nav/AuthModal/PasswordField/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { PasswordField } from './PasswordField'
|
|
@ -11,9 +11,9 @@ import { useRouter } from '../../../stores/router'
|
|||
import { hideModal } from '../../../stores/ui'
|
||||
import { ApiError } from '../../../utils/apiClient'
|
||||
import { validateEmail } from '../../../utils/validateEmail'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
|
||||
import { AuthModalHeader } from './AuthModalHeader'
|
||||
import { PasswordField } from './PasswordField'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
import { SocialProviders } from './SocialProviders'
|
||||
|
||||
|
@ -40,9 +40,9 @@ export const RegisterForm = () => {
|
|||
const [fullName, setFullName] = createSignal('')
|
||||
const [password, setPassword] = createSignal('')
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
const [isSuccess, setIsSuccess] = createSignal(false)
|
||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||
const [passwordError, setPasswordError] = createSignal<string>()
|
||||
|
||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||
|
||||
|
@ -52,37 +52,15 @@ export const RegisterForm = () => {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const handlePasswordInput = (newPassword: string) => {
|
||||
setPassword(newPassword)
|
||||
}
|
||||
|
||||
const handleNameInput = (newPasswordCopy: string) => {
|
||||
setFullName(newPasswordCopy)
|
||||
const handleNameInput = (newName: string) => {
|
||||
setFullName(newName)
|
||||
}
|
||||
|
||||
const handleSubmit = async (event: Event) => {
|
||||
event.preventDefault()
|
||||
|
||||
const passwordError = isValidPassword(password())
|
||||
if (passwordError) {
|
||||
setValidationErrors((errors) => ({ ...errors, password: passwordError }))
|
||||
if (passwordError()) {
|
||||
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
||||
} else {
|
||||
setValidationErrors(({ password: _notNeeded, ...rest }) => rest)
|
||||
}
|
||||
|
@ -198,48 +176,17 @@ export const RegisterForm = () => {
|
|||
<Show when={emailChecks()[email()]}>
|
||||
<div class={styles.validationError}>
|
||||
{t("This email is already taken. If it's you")},{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
changeSearchParams({
|
||||
mode: 'login',
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span class="link" onClick={() => changeSearchParams({ mode: 'login' })}>
|
||||
{t('enter')}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={clsx('pretty-form__item', {
|
||||
'pretty-form__item--error': validationErrors().password,
|
||||
})}
|
||||
>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
autocomplete="current-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>
|
||||
<Show when={validationErrors().password}>
|
||||
<div class={clsx(styles.registerPassword, styles.validationError)}>
|
||||
{validationErrors().password}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<PasswordField
|
||||
errorMessage={(err) => setPasswordError(err)}
|
||||
onInput={(value) => setPassword(value)}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
|
||||
|
|
|
@ -9,6 +9,7 @@ import { useRouter } from '../../../stores/router'
|
|||
import { hideModal } from '../../../stores/ui'
|
||||
import { isMobile } from '../../../utils/media-query'
|
||||
|
||||
import { ChangePasswordForm } from './ChangePasswordForm'
|
||||
import { EmailConfirm } from './EmailConfirm'
|
||||
import { ForgotPasswordForm } from './ForgotPasswordForm'
|
||||
import { LoginForm } from './LoginForm'
|
||||
|
@ -21,10 +22,11 @@ const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
|
|||
register: RegisterForm,
|
||||
'forgot-password': ForgotPasswordForm,
|
||||
'confirm-email': EmailConfirm,
|
||||
'change-password': ChangePasswordForm,
|
||||
}
|
||||
|
||||
export const AuthModal = () => {
|
||||
let rootRef: HTMLDivElement
|
||||
const rootRef: { current: HTMLDivElement } = { current: null }
|
||||
const { t } = useLocalize()
|
||||
const { searchParams } = useRouter<AuthModalSearchParams>()
|
||||
|
||||
|
@ -36,17 +38,17 @@ export const AuthModal = () => {
|
|||
|
||||
createEffect((oldMode) => {
|
||||
if (oldMode !== mode() && !isMobile()) {
|
||||
rootRef?.querySelector('input')?.focus()
|
||||
rootRef.current?.querySelector('input')?.focus()
|
||||
}
|
||||
}, null)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={rootRef}
|
||||
ref={(el) => (rootRef.current = el)}
|
||||
class={clsx(styles.view, {
|
||||
row: !source,
|
||||
[styles.signUp]: mode() === 'register' || mode() === 'confirm-email',
|
||||
})}
|
||||
classList={{ [styles.signUp]: mode() === 'register' || mode() === 'confirm-email' }}
|
||||
>
|
||||
<Show when={!source}>
|
||||
<div class={clsx('col-md-12 d-none d-md-flex', styles.authImage)}>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export type AuthModalMode = 'login' | 'register' | 'confirm-email' | 'forgot-password'
|
||||
export type AuthModalMode = 'login' | 'register' | 'confirm-email' | 'forgot-password' | 'change-password'
|
||||
export type AuthModalSource =
|
||||
| 'discussions'
|
||||
| 'vote'
|
||||
|
|
Loading…
Reference in New Issue
Block a user