Merge branch 'dev' into hotfix/posting-author

This commit is contained in:
Tony 2024-03-07 14:09:00 +03:00 committed by GitHub
commit bf9f0d9c7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 203 additions and 169 deletions

View File

@ -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"
} }

View File

@ -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": "Восстановить пароль"
} }

View File

@ -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;

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}

View File

@ -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 (

View File

@ -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>

View File

@ -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);

View File

@ -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>

View File

@ -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>
</> </>
) )

View 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>
)
}

View File

@ -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>
) )

View File

@ -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>
)} )}

View File

@ -0,0 +1 @@
export { SocialProviders } from './SocialProviders'

View File

@ -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,
} }

View File

@ -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'

View File

@ -464,7 +464,7 @@ form {
} }
.form-message--error { .form-message--error {
color: #d00820; color: var(--danger-color) !important;
} }
select { select {