Merge branch 'dev' into hotfix/posting-author
This commit is contained in:
commit
bf9f0d9c7b
|
@ -18,6 +18,7 @@
|
||||||
"Add signature": "Add signature",
|
"Add signature": "Add signature",
|
||||||
"Add subtitle": "Add subtitle",
|
"Add subtitle": "Add subtitle",
|
||||||
"Add url": "Add url",
|
"Add url": "Add url",
|
||||||
|
"try": "попробуйте",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
"Address on Discours": "Address on Discours",
|
"Address on Discours": "Address on Discours",
|
||||||
"Album name": "Название aльбома",
|
"Album name": "Название aльбома",
|
||||||
|
@ -144,7 +145,6 @@
|
||||||
"Enter your new password": "Enter your new password",
|
"Enter your new password": "Enter your new password",
|
||||||
"Enter": "Enter",
|
"Enter": "Enter",
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"Please give us your email address": "Please provide us your email address to get the password reset link",
|
|
||||||
"Experience": "Experience",
|
"Experience": "Experience",
|
||||||
"FAQ": "Tips and suggestions",
|
"FAQ": "Tips and suggestions",
|
||||||
"Favorite topics": "Favorite topics",
|
"Favorite topics": "Favorite topics",
|
||||||
|
@ -254,7 +254,6 @@
|
||||||
"Nothing here yet": "There's nothing here yet",
|
"Nothing here yet": "There's nothing here yet",
|
||||||
"Nothing is here": "There is nothing here",
|
"Nothing is here": "There is nothing here",
|
||||||
"Notifications": "Notifications",
|
"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",
|
"Or paste a link to an image": "Or paste a link to an image",
|
||||||
"Ordered list": "Ordered list",
|
"Ordered list": "Ordered list",
|
||||||
"Our regular contributor": "Our regular contributor",
|
"Our regular contributor": "Our regular contributor",
|
||||||
|
@ -323,7 +322,7 @@
|
||||||
"Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Samizdat exists thanks to the help of wonderful people from all over the world. Thank you!",
|
"Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Samizdat exists thanks to the help of wonderful people from all over the world. Thank you!",
|
||||||
"Send link again": "Send link again",
|
"Send link again": "Send link again",
|
||||||
"Send": "Send",
|
"Send": "Send",
|
||||||
"Set the new password": "Set the new password",
|
"Forgot password?": "Forgot password?",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"Share publication": "Share publication",
|
"Share publication": "Share publication",
|
||||||
"Share": "Share",
|
"Share": "Share",
|
||||||
|
@ -526,5 +525,7 @@
|
||||||
"view": "view",
|
"view": "view",
|
||||||
"viewsWithCount": "{count} {count, plural, one {view} other {views}}",
|
"viewsWithCount": "{count} {count, plural, one {view} other {views}}",
|
||||||
"yesterday": "yesterday",
|
"yesterday": "yesterday",
|
||||||
"Failed to delete comment": "Failed to delete comment"
|
"Failed to delete comment": "Failed to delete comment",
|
||||||
|
"It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password",
|
||||||
|
"Restore password": "Restore password"
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,6 @@
|
||||||
"Enter": "Войти",
|
"Enter": "Войти",
|
||||||
"This content is not published yet": "Содержимое ещё не опубликовано",
|
"This content is not published yet": "Содержимое ещё не опубликовано",
|
||||||
"Error": "Ошибка",
|
"Error": "Ошибка",
|
||||||
"Please give us your email address": "Пожалуйста, укажите свою почту, чтобы получить ссылку для сброса пароля",
|
|
||||||
"Experience": "Личный опыт",
|
"Experience": "Личный опыт",
|
||||||
"FAQ": "Советы и предложения",
|
"FAQ": "Советы и предложения",
|
||||||
"Favorite topics": "Избранные темы",
|
"Favorite topics": "Избранные темы",
|
||||||
|
@ -267,7 +266,6 @@
|
||||||
"Nothing here yet": "Здесь пока ничего нет",
|
"Nothing here yet": "Здесь пока ничего нет",
|
||||||
"Nothing is here": "Здесь ничего нет",
|
"Nothing is here": "Здесь ничего нет",
|
||||||
"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": "Теперь можете ввести новый пароль, он должен содержать минимум 8 символов и не совпадать с предыдущим паролем",
|
|
||||||
"Or paste a link to an image": "Или вставьте ссылку на изображение",
|
"Or paste a link to an image": "Или вставьте ссылку на изображение",
|
||||||
"Ordered list": "Нумерованный список",
|
"Ordered list": "Нумерованный список",
|
||||||
"Our regular contributor": "Наш постоянный автор",
|
"Our regular contributor": "Наш постоянный автор",
|
||||||
|
@ -288,7 +286,7 @@
|
||||||
"Pin": "Закрепить",
|
"Pin": "Закрепить",
|
||||||
"Platform Guide": "Гид по дискурсу",
|
"Platform Guide": "Гид по дискурсу",
|
||||||
"Please check your email address": "Пожалуйста, проверьте введенный адрес почты",
|
"Please check your email address": "Пожалуйста, проверьте введенный адрес почты",
|
||||||
"Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте ваш адрес почты, мы отправили ссылку для сброса пароля",
|
"Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте свою почту, мы отправили вам письмо со ссылкой для сброса пароля",
|
||||||
"Please confirm your email to finish": "Подтвердите почту и действие совершится",
|
"Please confirm your email to finish": "Подтвердите почту и действие совершится",
|
||||||
"Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте",
|
"Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте",
|
||||||
"Please enter email": "Пожалуйста, введите почту",
|
"Please enter email": "Пожалуйста, введите почту",
|
||||||
|
@ -329,7 +327,7 @@
|
||||||
"Reports": "Репортажи",
|
"Reports": "Репортажи",
|
||||||
"Required": "Поле обязательно для заполнения",
|
"Required": "Поле обязательно для заполнения",
|
||||||
"Resend code": "Выслать подтверждение",
|
"Resend code": "Выслать подтверждение",
|
||||||
"Set the new password": "Задать новый пароль",
|
"Forgot password?": "Забыли пароль?",
|
||||||
"Rules of the journal Discours": "Правила журнала Дискурс",
|
"Rules of the journal Discours": "Правила журнала Дискурс",
|
||||||
"Save draft": "Сохранить черновик",
|
"Save draft": "Сохранить черновик",
|
||||||
"Save settings": "Сохранить настройки",
|
"Save settings": "Сохранить настройки",
|
||||||
|
@ -404,6 +402,7 @@
|
||||||
"This email is": "Этот email",
|
"This email is": "Этот email",
|
||||||
"This email is not verified": "Этот email не подтвержден",
|
"This email is not verified": "Этот email не подтвержден",
|
||||||
"This email is verified": "Этот email подтвержден",
|
"This email is verified": "Этот email подтвержден",
|
||||||
|
"try": "попробуйте",
|
||||||
"This email is registered": "Этот email уже зарегистрирован",
|
"This email is registered": "Этот email уже зарегистрирован",
|
||||||
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.",
|
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.",
|
||||||
"This month": "За месяц",
|
"This month": "За месяц",
|
||||||
|
@ -553,5 +552,7 @@
|
||||||
"view": "просмотр",
|
"view": "просмотр",
|
||||||
"viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}",
|
"viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}",
|
||||||
"yesterday": "вчера",
|
"yesterday": "вчера",
|
||||||
"Failed to delete comment": "Не удалось удалить комментарий"
|
"Failed to delete comment": "Не удалось удалить комментарий",
|
||||||
|
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
||||||
|
"Restore password": "Восстановить пароль"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
|
|
||||||
// align-self: center;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.view {
|
.view {
|
||||||
background: #fff;
|
background: var(--background-color);
|
||||||
min-height: 550px;
|
min-height: 550px;
|
||||||
position: relative;
|
position: relative;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -154,17 +154,6 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.authInfo {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: smaller;
|
|
||||||
margin-top: -2em;
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
.warn {
|
|
||||||
color: #a00;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.authForm {
|
.authForm {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -221,3 +210,7 @@
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
margin-bottom: 52px;
|
margin-bottom: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submitError {
|
||||||
|
margin: -1rem 0 -2rem;
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const ChangePasswordForm = () => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
if (newPassword()) {
|
if (newPassword()) {
|
||||||
await changePassword(newPassword(), searchParams()?.token)
|
changePassword(newPassword(), searchParams()?.token)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
setIsSuccess(true)
|
setIsSuccess(true)
|
||||||
|
@ -60,11 +60,6 @@ export const ChangePasswordForm = () => {
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h4>{t('Enter a new password')}</h4>
|
<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>
|
|
||||||
<Show when={validationErrors()}>
|
<Show when={validationErrors()}>
|
||||||
<div>{validationErrors().password}</div>
|
<div>{validationErrors().password}</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
.title {
|
|
||||||
font-size: 26px;
|
|
||||||
line-height: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #141414;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 24px;
|
|
||||||
margin-bottom: 52px;
|
|
||||||
}
|
|
|
@ -17,19 +17,20 @@ export const EmailConfirm = () => {
|
||||||
const [emailConfirmed, setEmailConfirmed] = createSignal(false)
|
const [emailConfirmed, setEmailConfirmed] = createSignal(false)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const e = session()?.user?.email
|
const email = session()?.user?.email
|
||||||
const v = session()?.user?.email_verified
|
const isVerified = session()?.user?.email_verified
|
||||||
if (e) {
|
|
||||||
setEmail(e.toLowerCase())
|
if (email) {
|
||||||
if (v) setEmailConfirmed(v)
|
setEmail(email.toLowerCase())
|
||||||
|
if (isVerified) setEmailConfirmed(isVerified)
|
||||||
if (authError()) {
|
if (authError()) {
|
||||||
changeSearchParams({}, true)
|
changeSearchParams({}, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
if (authError()) {
|
||||||
if (authError()) console.debug('[AuthModal.EmailConfirm] auth error:', authError())
|
console.debug('[AuthModal.EmailConfirm] auth error:', authError())
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, createSignal } from 'solid-js'
|
import { JSX, Show, createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -27,12 +27,11 @@ type ValidationErrors = Partial<Record<keyof FormFields, string>>
|
||||||
export const LoginForm = () => {
|
export const LoginForm = () => {
|
||||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [submitError, setSubmitError] = createSignal('')
|
const [submitError, setSubmitError] = createSignal<string | JSX.Element>()
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||||
const [password, setPassword] = createSignal('')
|
const [password, setPassword] = createSignal('')
|
||||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||||
// TODO: better solution for interactive error messages
|
|
||||||
const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false)
|
|
||||||
const [isLinkSent, setIsLinkSent] = createSignal(false)
|
const [isLinkSent, setIsLinkSent] = createSignal(false)
|
||||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
|
@ -52,43 +51,43 @@ export const LoginForm = () => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
setIsLinkSent(true)
|
setIsLinkSent(true)
|
||||||
setIsEmailNotConfirmed(false)
|
setSubmitError()
|
||||||
setSubmitError('')
|
changeSearchParams({ mode: 'send-confirm-email' })
|
||||||
changeSearchParams({ mode: 'send-reset-link' })
|
|
||||||
// NOTE: temporary solution, needs logic in authorizer
|
|
||||||
/* FIXME:
|
|
||||||
const { authorizer } = useSession()
|
|
||||||
const result = await authorizer().verifyEmail({ token })
|
|
||||||
if (!result) setSubmitError('cant sign send link')
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
await preSendValidate(email(), 'email')
|
||||||
|
await preSendValidate(password(), 'password')
|
||||||
|
|
||||||
setIsLinkSent(false)
|
setIsLinkSent(false)
|
||||||
setIsEmailNotConfirmed(false)
|
setSubmitError()
|
||||||
setSubmitError('')
|
|
||||||
|
|
||||||
const newValidationErrors: ValidationErrors = {}
|
|
||||||
|
|
||||||
const validateAndSetError = (field, message) => {
|
|
||||||
if (!field()) {
|
|
||||||
newValidationErrors[field.name] = t(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validateAndSetError(email, 'Please enter email')
|
|
||||||
validateAndSetError(() => validateEmail(email()), 'Invalid email')
|
|
||||||
validateAndSetError(password, 'Please enter password')
|
|
||||||
|
|
||||||
if (Object.keys(newValidationErrors).length > 0) {
|
|
||||||
setValidationErrors(newValidationErrors)
|
|
||||||
|
|
||||||
|
if (Object.keys(validationErrors()).length > 0) {
|
||||||
authFormRef.current
|
authFormRef.current
|
||||||
.querySelector<HTMLInputElement>(`input[name="${Object.keys(newValidationErrors)[0]}"]`)
|
.querySelector<HTMLInputElement>(`input[name="${Object.keys(validationErrors())[0]}"]`)
|
||||||
?.focus()
|
?.focus()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,14 +95,27 @@ export const LoginForm = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { errors } = await signIn({ email: email(), password: password() })
|
const { errors } = await signIn({ email: email(), password: password() })
|
||||||
|
console.error('[signIn errors]', errors)
|
||||||
if (errors?.length > 0) {
|
if (errors?.length > 0) {
|
||||||
if (errors.some((error) => error.message.includes('bad user credentials'))) {
|
if (errors.some((error) => error.message.includes('bad user credentials'))) {
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
password: t('Something went wrong, check email and password'),
|
password: t('Something went wrong, check email and password'),
|
||||||
}))
|
}))
|
||||||
|
} else if (errors.some((error) => error.message.includes('user not found'))) {
|
||||||
|
setSubmitError('Пользователь не найден')
|
||||||
|
} else if (errors.some((error) => error.message.includes('email not verified'))) {
|
||||||
|
setSubmitError(
|
||||||
|
<div class={styles.info}>
|
||||||
|
{t('This email is not verified')}
|
||||||
|
{'. '}
|
||||||
|
<span class={'link'} onClick={handleSendLinkAgainClick}>
|
||||||
|
{t('Send link again')}
|
||||||
|
</span>
|
||||||
|
</div>,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
setSubmitError(t('Error'))
|
setSubmitError(t('Error', errors[0].message))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -121,19 +133,6 @@ export const LoginForm = () => {
|
||||||
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef.current = el)}>
|
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef.current = el)}>
|
||||||
<div>
|
<div>
|
||||||
<AuthModalHeader modalType="login" />
|
<AuthModalHeader modalType="login" />
|
||||||
<Show when={submitError()}>
|
|
||||||
<div class={styles.authInfo}>
|
|
||||||
<div class={styles.warn}>{submitError()}</div>
|
|
||||||
<Show when={isEmailNotConfirmed()}>
|
|
||||||
<span class={'link'} onClick={handleSendLinkAgainClick}>
|
|
||||||
{t('Send link again')}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={isLinkSent()}>
|
|
||||||
<div class={styles.authInfo}>{t('Link sent, check your email')}</div>
|
|
||||||
</Show>
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email,
|
'pretty-form__item--error': validationErrors().email,
|
||||||
|
@ -154,11 +153,14 @@ export const LoginForm = () => {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PasswordField variant={'login'} onInput={(value) => handlePasswordInput(value)} />
|
<PasswordField
|
||||||
<Show when={validationErrors().password}>
|
variant={'login'}
|
||||||
<div class={styles.validationError} style={{ position: 'static', 'font-size': '1.4rem' }}>
|
setError={validationErrors().password}
|
||||||
{validationErrors().password}
|
onInput={(value) => handlePasswordInput(value)}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
|
<Show when={submitError()}>
|
||||||
|
<div class={clsx('form-message--error', styles.submitError)}>{submitError()}</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -175,7 +177,7 @@ export const LoginForm = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t('Set the new password')}
|
{t('Forgot password?')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,11 +31,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Red/500 */
|
/* Red/500 */
|
||||||
color: #d00820;
|
color: orange;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #d00820;
|
color: orange;
|
||||||
border-color: #d00820;
|
border-color: orange;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--default-color-invert);
|
color: var(--default-color-invert);
|
||||||
|
|
|
@ -11,21 +11,23 @@ type Props = {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
errorMessage?: (error: string) => void
|
errorMessage?: (error: string) => void
|
||||||
|
setError?: string
|
||||||
onInput: (value: string) => void
|
onInput: (value: string) => void
|
||||||
|
onBlur?: (value: string) => void
|
||||||
variant?: 'login' | 'registration'
|
variant?: 'login' | 'registration'
|
||||||
disableAutocomplete?: boolean
|
disableAutocomplete?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minLength = 8
|
||||||
|
const hasNumber = /\d/
|
||||||
|
const hasSpecial = /[!#$%&*@^]/
|
||||||
|
|
||||||
export const PasswordField = (props: Props) => {
|
export const PasswordField = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [showPassword, setShowPassword] = createSignal(false)
|
const [showPassword, setShowPassword] = createSignal(false)
|
||||||
const [error, setError] = createSignal<string>()
|
const [error, setError] = createSignal<string>()
|
||||||
|
|
||||||
const validatePassword = (passwordToCheck) => {
|
const validatePassword = (passwordToCheck) => {
|
||||||
const minLength = 8
|
|
||||||
const hasNumber = /\d/
|
|
||||||
const hasSpecial = /[!#$%&*@^]/
|
|
||||||
|
|
||||||
if (passwordToCheck.length < minLength) {
|
if (passwordToCheck.length < minLength) {
|
||||||
return t('Password should be at least 8 characters')
|
return t('Password should be at least 8 characters')
|
||||||
}
|
}
|
||||||
|
@ -35,11 +37,17 @@ export const PasswordField = (props: Props) => {
|
||||||
if (!hasSpecial.test(passwordToCheck)) {
|
if (!hasSpecial.test(passwordToCheck)) {
|
||||||
return t('Password should contain at least one special character: !@#$%^&*')
|
return t('Password should contain at least one special character: !@#$%^&*')
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInputChange = (value) => {
|
const handleInputBlur = (value: string) => {
|
||||||
|
if (props.variant === 'login') {
|
||||||
|
return props.onBlur(value)
|
||||||
|
}
|
||||||
|
if (value.length < 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
props.onInput(value)
|
props.onInput(value)
|
||||||
const errorValue = validatePassword(value)
|
const errorValue = validatePassword(value)
|
||||||
if (errorValue) {
|
if (errorValue) {
|
||||||
|
@ -58,14 +66,13 @@ export const PasswordField = (props: Props) => {
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
createEffect(() => {
|
||||||
|
setError(props.setError)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={clsx(styles.PassportField, props.class)}>
|
<div class={clsx(styles.PassportField, props.class)}>
|
||||||
<div
|
<div class="pretty-form__item">
|
||||||
class={clsx('pretty-form__item', {
|
|
||||||
'pretty-form__item--error': error() && props.variant !== 'login',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
@ -73,7 +80,7 @@ export const PasswordField = (props: Props) => {
|
||||||
autocomplete={props.disableAutocomplete ? 'one-time-code' : 'current-password'}
|
autocomplete={props.disableAutocomplete ? 'one-time-code' : 'current-password'}
|
||||||
type={showPassword() ? 'text' : 'password'}
|
type={showPassword() ? 'text' : 'password'}
|
||||||
placeholder={props.placeholder || t('Password')}
|
placeholder={props.placeholder || t('Password')}
|
||||||
onInput={(event) => handleInputChange(event.currentTarget.value)}
|
onBlur={(event) => handleInputBlur(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<label for="password">{t('Password')}</label>
|
<label for="password">{t('Password')}</label>
|
||||||
<button
|
<button
|
||||||
|
@ -83,8 +90,14 @@ export const PasswordField = (props: Props) => {
|
||||||
>
|
>
|
||||||
<Icon class={styles.passwordToggleIcon} name={showPassword() ? 'eye-off' : 'eye'} />
|
<Icon class={styles.passwordToggleIcon} name={showPassword() ? 'eye-off' : 'eye'} />
|
||||||
</button>
|
</button>
|
||||||
<Show when={error() && props.variant !== 'login'}>
|
<Show when={error()}>
|
||||||
<div class={clsx(styles.registerPassword, styles.validationError)}>{error()}</div>
|
<div
|
||||||
|
class={clsx(styles.registerPassword, styles.validationError, {
|
||||||
|
'form-message--error': props.setError,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{error()}
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,10 +28,6 @@ type FormFields = {
|
||||||
|
|
||||||
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
||||||
|
|
||||||
const handleEmailInput = (newEmail: string) => {
|
|
||||||
setEmail(newEmail.toLowerCase())
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RegisterForm = () => {
|
export const RegisterForm = () => {
|
||||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
@ -52,6 +48,7 @@ export const RegisterForm = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
|
console.log('!!! handleSubmit:', handleSubmit)
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (passwordError()) {
|
if (passwordError()) {
|
||||||
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
||||||
|
@ -137,7 +134,8 @@ export const RegisterForm = () => {
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
email: (
|
email: (
|
||||||
<>
|
<>
|
||||||
{t('This email is verified')}. {t('You can')}
|
{t('This email is registered')}. {t('try')}
|
||||||
|
{', '}
|
||||||
<span class="link" onClick={() => changeSearchParams({ mode: 'login' })}>
|
<span class="link" onClick={() => changeSearchParams({ mode: 'login' })}>
|
||||||
{t('enter')}
|
{t('enter')}
|
||||||
</span>
|
</span>
|
||||||
|
@ -173,17 +171,18 @@ export const RegisterForm = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleEmailInput = (newEmail: string) => {
|
||||||
|
setEmailStatus('')
|
||||||
|
setValidationErrors({})
|
||||||
|
setEmail(newEmail.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={!isSuccess()}>
|
<Show when={!isSuccess()}>
|
||||||
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef.current = el)}>
|
<form onSubmit={handleSubmit} class={styles.authForm} ref={(el) => (authFormRef.current = el)}>
|
||||||
<div>
|
<div>
|
||||||
<AuthModalHeader modalType="register" />
|
<AuthModalHeader modalType="register" />
|
||||||
<Show when={submitError()}>
|
|
||||||
<div class={styles.authInfo}>
|
|
||||||
<div class={styles.warn}>{submitError()}</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().fullName,
|
'pretty-form__item--error': validationErrors().fullName,
|
||||||
|
@ -218,9 +217,11 @@ export const RegisterForm = () => {
|
||||||
onBlur={handleEmailBlur}
|
onBlur={handleEmailBlur}
|
||||||
/>
|
/>
|
||||||
<label for="email">{t('Email')}</label>
|
<label for="email">{t('Email')}</label>
|
||||||
|
<Show when={validationErrors().email || emailStatus()}>
|
||||||
<div class={clsx(styles.validationError, { info: Boolean(emailStatus()) })}>
|
<div class={clsx(styles.validationError, { info: Boolean(emailStatus()) })}>
|
||||||
{validationErrors().email}
|
{validationErrors().email}
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PasswordField
|
<PasswordField
|
||||||
|
@ -260,6 +261,7 @@ export const RegisterForm = () => {
|
||||||
</form>
|
</form>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={isSuccess()}>
|
<Show when={isSuccess()}>
|
||||||
|
<div style={{ 'justify-content': 'center' }}>
|
||||||
<div class={styles.title}>{t('Almost done! Check your email.')}</div>
|
<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 class={styles.text}>{t("We've sent you a message with a link to enter our website.")}</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -267,6 +269,7 @@ export const RegisterForm = () => {
|
||||||
{t('Back to main page')}
|
{t('Back to main page')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
24
src/components/Nav/AuthModal/SendEmailConfirm.tsx
Normal file
24
src/components/Nav/AuthModal/SendEmailConfirm.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { hideModal } from '../../../stores/ui'
|
||||||
|
|
||||||
|
import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
|
export const SendEmailConfirm = () => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
'align-items': 'center',
|
||||||
|
'justify-content': 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={styles.text}>{t('Link sent, check your email')}</div>
|
||||||
|
<div>
|
||||||
|
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
|
||||||
|
{t('Go to main page')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -85,8 +85,12 @@ export const SendResetLinkForm = () => {
|
||||||
ref={(el) => (authFormRef.current = el)}
|
ref={(el) => (authFormRef.current = el)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h4>{t('Set the new password')}</h4>
|
<h4>{t('Forgot password?')}</h4>
|
||||||
<div class={styles.authSubtitle}>{t(message()) || t('Please give us your email address')}</div>
|
<Show when={!message()}>
|
||||||
|
<div class={styles.authSubtitle}>
|
||||||
|
{t("It's OK. Just enter your email to receive a link to change your password")}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email,
|
'pretty-form__item--error': validationErrors().email,
|
||||||
|
@ -110,7 +114,7 @@ export const SendResetLinkForm = () => {
|
||||||
class={'link'}
|
class={'link'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
mode: 'login',
|
mode: 'register',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -122,14 +126,15 @@ export const SendResetLinkForm = () => {
|
||||||
<div class={styles.validationError}>{validationErrors().email}</div>
|
<div class={styles.validationError}>{validationErrors().email}</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
<Show when={!message()} fallback={<div class={styles.authSubtitle}>{t(message())}</div>}>
|
||||||
|
<>
|
||||||
<div style={{ 'margin-top': '5rem' }}>
|
<div style={{ 'margin-top': '5rem' }}>
|
||||||
<button
|
<button
|
||||||
class={clsx('button', styles.submitButton)}
|
class={clsx('button', styles.submitButton)}
|
||||||
disabled={isSubmitting() || Boolean(message())}
|
disabled={isSubmitting() || Boolean(message())}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{isSubmitting() ? '...' : t('Send')}
|
{isSubmitting() ? '...' : t('Restore password')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.authControl}>
|
<div class={styles.authControl}>
|
||||||
|
@ -144,6 +149,8 @@ export const SendResetLinkForm = () => {
|
||||||
{t('I know the password')}
|
{t('I know the password')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { For } from 'solid-js'
|
import { For } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../../context/session'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../../_shared/Icon'
|
||||||
|
|
||||||
import styles from './SocialProviders.module.scss'
|
import styles from './SocialProviders.module.scss'
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export const SocialProviders = () => {
|
||||||
<div class={styles.social}>
|
<div class={styles.social}>
|
||||||
<For each={PROVIDERS}>
|
<For each={PROVIDERS}>
|
||||||
{(provider) => (
|
{(provider) => (
|
||||||
<button class={styles[provider]} onClick={(_e) => oauth(provider)}>
|
<button type="button" class={styles[provider]} onClick={(_e) => oauth(provider)}>
|
||||||
<Icon name={provider} />
|
<Icon name={provider} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
1
src/components/Nav/AuthModal/SocialProviders/index.ts
Normal file
1
src/components/Nav/AuthModal/SocialProviders/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { SocialProviders } from './SocialProviders'
|
|
@ -16,12 +16,14 @@ import { RegisterForm } from './RegisterForm'
|
||||||
import { SendResetLinkForm } from './SendResetLinkForm'
|
import { SendResetLinkForm } from './SendResetLinkForm'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
|
import { SendEmailConfirm } from './SendEmailConfirm'
|
||||||
|
|
||||||
const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
|
const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
|
||||||
login: LoginForm,
|
login: LoginForm,
|
||||||
register: RegisterForm,
|
register: RegisterForm,
|
||||||
'send-reset-link': SendResetLinkForm,
|
'send-reset-link': SendResetLinkForm,
|
||||||
'confirm-email': EmailConfirm,
|
'confirm-email': EmailConfirm,
|
||||||
|
'send-confirm-email': SendEmailConfirm,
|
||||||
'change-password': ChangePasswordForm,
|
'change-password': ChangePasswordForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
export type AuthModalMode = 'login' | 'register' | 'confirm-email' | 'send-reset-link' | 'change-password'
|
export type AuthModalMode =
|
||||||
|
| 'login'
|
||||||
|
| 'register'
|
||||||
|
| 'confirm-email'
|
||||||
|
| 'send-confirm-email'
|
||||||
|
| 'send-reset-link'
|
||||||
|
| 'change-password'
|
||||||
export type AuthModalSource =
|
export type AuthModalSource =
|
||||||
| 'discussions'
|
| 'discussions'
|
||||||
| 'vote'
|
| 'vote'
|
||||||
|
|
|
@ -464,7 +464,7 @@ form {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-message--error {
|
.form-message--error {
|
||||||
color: #d00820;
|
color: var(--danger-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user