Merge branch 'dev' into feature/empty-feed
This commit is contained in:
commit
0ecbf07ef6
|
@ -546,5 +546,13 @@
|
|||
"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",
|
||||
"Subscribing...": "Subscribing...",
|
||||
"Unsubscribing...": "Unsubscribing..."
|
||||
"Unsubscribing...": "Unsubscribing...",
|
||||
"Login and security": "Login and security",
|
||||
"Settings for account, email, password and login methods.": "Settings for account, email, password and login methods.",
|
||||
"Current password": "Current password",
|
||||
"Confirm your new password": "Confirm your new password",
|
||||
"Connect": "Connect",
|
||||
"Incorrect old password": "Incorrect old password",
|
||||
"Repeat new password": "Repeat new password",
|
||||
"Incorrect new password confirm": "Incorrect new password confirm"
|
||||
}
|
||||
|
|
|
@ -573,5 +573,13 @@
|
|||
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
||||
"Restore password": "Восстановить пароль",
|
||||
"Subscribing...": "Подписываем...",
|
||||
"Unsubscribing...": "Отписываем..."
|
||||
"Unsubscribing...": "Отписываем...",
|
||||
"Login and security": "Вход и безопасность",
|
||||
"Settings for account, email, password and login methods.": "Настройки аккаунта, почты, пароля и способов входа.",
|
||||
"Current password": "Текущий пароль",
|
||||
"Confirm your new password": "Подтвердите новый пароль",
|
||||
"Connect": "Привязать",
|
||||
"Incorrect old password": "Старый пароль не верен",
|
||||
"Repeat new password": "Повторите новый пароль",
|
||||
"Incorrect new password confirm": "Неверное подтверждение нового пароля"
|
||||
}
|
||||
|
|
|
@ -538,7 +538,7 @@ export const FullArticle = (props: Props) => {
|
|||
{(triggerRef: (el) => void) => (
|
||||
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
||||
<a
|
||||
href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}
|
||||
href={getPagePath(router, 'edit', { shoutId: props.article?.id.toString() })}
|
||||
class={styles.shoutStatsItemInner}
|
||||
>
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
|
|
|
@ -54,7 +54,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
requireAuthentication(() => {
|
||||
openPage(router, 'inbox')
|
||||
changeSearchParams({
|
||||
initChat: props.author.id.toString(),
|
||||
initChat: props.author?.id.toString(),
|
||||
})
|
||||
}, 'discussions')
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ export const AuthorCard = (props: Props) => {
|
|||
requireAuthentication(() => {
|
||||
openPage(router, 'inbox')
|
||||
changeSearchParams({
|
||||
initChat: props.author.id.toString(),
|
||||
initChat: props.author?.id.toString(),
|
||||
})
|
||||
}, 'discussions')
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ export const Draft = (props: Props) => {
|
|||
<div class={styles.actions}>
|
||||
<a
|
||||
class={styles.actionItem}
|
||||
href={getPagePath(router, 'edit', { shoutId: props.shout.id.toString() })}
|
||||
href={getPagePath(router, 'edit', { shoutId: props.shout?.id.toString() })}
|
||||
>
|
||||
{t('Edit')}
|
||||
</a>
|
||||
|
|
|
@ -48,11 +48,13 @@ type Props = {
|
|||
onChange?: (text: string) => void
|
||||
variant?: 'minimal' | 'bordered'
|
||||
maxLength?: number
|
||||
noLimits?: boolean
|
||||
maxHeight?: number
|
||||
submitButtonText?: string
|
||||
quoteEnabled?: boolean
|
||||
imageEnabled?: boolean
|
||||
setClear?: boolean
|
||||
resetToInitial?: boolean
|
||||
smallHeight?: boolean
|
||||
submitByCtrlEnter?: boolean
|
||||
onlyBubbleControls?: boolean
|
||||
|
@ -124,7 +126,7 @@ const SimplifiedEditor = (props: Props) => {
|
|||
openOnClick: false,
|
||||
}),
|
||||
CharacterCount.configure({
|
||||
limit: maxLength,
|
||||
limit: props.noLimits ? null : maxLength,
|
||||
}),
|
||||
Blockquote.configure({
|
||||
HTMLAttributes: {
|
||||
|
@ -216,6 +218,10 @@ const SimplifiedEditor = (props: Props) => {
|
|||
if (props.setClear) {
|
||||
editor().commands.clearContent(true)
|
||||
}
|
||||
if (props.resetToInitial) {
|
||||
editor().commands.clearContent(true)
|
||||
editor().commands.setContent(props.initialContent)
|
||||
}
|
||||
})
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
|
|
|
@ -328,7 +328,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
|
||||
{(triggerRef: (el) => void) => (
|
||||
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
||||
<a href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}>
|
||||
<a href={getPagePath(router, 'edit', { shoutId: props.article?.id.toString() })}>
|
||||
<Icon name="pencil-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
||||
<Icon
|
||||
name="pencil-outline-hover"
|
||||
|
|
|
@ -16,6 +16,9 @@ type Props = {
|
|||
onBlur?: (value: string) => void
|
||||
variant?: 'login' | 'registration'
|
||||
disableAutocomplete?: boolean
|
||||
noValidate?: boolean
|
||||
onFocus?: () => void
|
||||
value?: string
|
||||
}
|
||||
|
||||
const minLength = 8
|
||||
|
@ -27,7 +30,7 @@ export const PasswordField = (props: Props) => {
|
|||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
const [error, setError] = createSignal<string>()
|
||||
|
||||
const validatePassword = (passwordToCheck) => {
|
||||
const validatePassword = (passwordToCheck: string) => {
|
||||
if (passwordToCheck.length < minLength) {
|
||||
return t('Password should be at least 8 characters')
|
||||
}
|
||||
|
@ -50,11 +53,13 @@ export const PasswordField = (props: Props) => {
|
|||
}
|
||||
|
||||
props.onInput(value)
|
||||
const errorValue = validatePassword(value)
|
||||
if (errorValue) {
|
||||
setError(errorValue)
|
||||
} else {
|
||||
setError()
|
||||
if (!props.noValidate) {
|
||||
const errorValue = validatePassword(value)
|
||||
if (errorValue) {
|
||||
setError(errorValue)
|
||||
} else {
|
||||
setError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,6 +83,8 @@ export const PasswordField = (props: Props) => {
|
|||
id="password"
|
||||
name="password"
|
||||
disabled={props.disabled}
|
||||
onFocus={props.onFocus}
|
||||
value={props.value ? props.value : ''}
|
||||
autocomplete={props.disableAutocomplete ? 'one-time-code' : 'current-password'}
|
||||
type={showPassword() ? 'text' : 'password'}
|
||||
placeholder={props.placeholder || t('Password')}
|
||||
|
|
|
@ -103,7 +103,13 @@ export const HeaderAuth = (props: Props) => {
|
|||
<div class={clsx('col-auto col-lg-7', styles.usernav)}>
|
||||
<div class={styles.userControl}>
|
||||
<Show when={isCreatePostButtonVisible() && session()?.access_token}>
|
||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||
<div
|
||||
class={clsx(
|
||||
styles.userControlItem,
|
||||
styles.userControlItemVerbose,
|
||||
styles.userControlItemCreate,
|
||||
)}
|
||||
>
|
||||
<a href={getPagePath(router, 'create')}>
|
||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
|
@ -210,11 +216,17 @@ export const HeaderAuth = (props: Props) => {
|
|||
</Show>
|
||||
|
||||
<Show when={isCreatePostButtonVisible() && !session()?.access_token}>
|
||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||
<div
|
||||
class={clsx(
|
||||
styles.userControlItem,
|
||||
styles.userControlItemVerbose,
|
||||
styles.userControlItemCreate,
|
||||
)}
|
||||
>
|
||||
<a href={getPagePath(router, 'create')}>
|
||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||
<Icon name="pencil" class={styles.icon} />
|
||||
<Icon name="pencil" class={clsx(styles.icon, styles.iconHover)} />
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
@ -227,7 +239,7 @@ export const HeaderAuth = (props: Props) => {
|
|||
<a href="?m=auth&mode=login">
|
||||
<span class={styles.textLabel}>{t('Enter')}</span>
|
||||
<Icon name="key" class={styles.icon} />
|
||||
{/*<Icon name="user-default" class={clsx(styles.icon, styles.iconHover)} />*/}
|
||||
<Icon name="key" class={clsx(styles.icon, styles.iconHover)} />
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
@ -20,6 +20,8 @@ import { useLocalize } from '../../context/localize'
|
|||
import { useProfileForm } from '../../context/profile'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useSnackbar } from '../../context/snackbar'
|
||||
import { ProfileInput } from '../../graphql/schema/core.gen'
|
||||
import styles from '../../pages/profile/Settings.module.scss'
|
||||
import { hideModal, showModal } from '../../stores/ui'
|
||||
import { clone } from '../../utils/clone'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
@ -35,14 +37,12 @@ import { Loading } from '../_shared/Loading'
|
|||
import { Popover } from '../_shared/Popover'
|
||||
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
||||
|
||||
import styles from '../../pages/profile/Settings.module.scss'
|
||||
|
||||
const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor'))
|
||||
const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||
|
||||
export const ProfileSettings = () => {
|
||||
const { t } = useLocalize()
|
||||
const [prevForm, setPrevForm] = createStore({})
|
||||
const [prevForm, setPrevForm] = createStore<ProfileInput>({})
|
||||
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
||||
const [isSaving, setIsSaving] = createSignal(false)
|
||||
const [social, setSocial] = createSignal([])
|
||||
|
@ -59,6 +59,7 @@ export const ProfileSettings = () => {
|
|||
const { showSnackbar } = useSnackbar()
|
||||
const { loadAuthor, session } = useSession()
|
||||
const { showConfirm } = useConfirm()
|
||||
const [clearAbout, setClearAbout] = createSignal(false)
|
||||
|
||||
createEffect(() => {
|
||||
if (Object.keys(form).length > 0 && !isFormInitialized()) {
|
||||
|
@ -121,7 +122,9 @@ export const ProfileSettings = () => {
|
|||
declineButtonVariant: 'secondary',
|
||||
})
|
||||
if (isConfirmed) {
|
||||
setClearAbout(true)
|
||||
setForm(clone(prevForm))
|
||||
setClearAbout(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,11 +174,13 @@ export const ProfileSettings = () => {
|
|||
on(
|
||||
() => deepEqual(form, prevForm),
|
||||
() => {
|
||||
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
||||
if (Object.keys(prevForm).length > 0) {
|
||||
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
||||
}
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
const handleDeleteSocialLink = (link) => {
|
||||
updateFormField('links', link, true)
|
||||
}
|
||||
|
@ -317,6 +322,8 @@ export const ProfileSettings = () => {
|
|||
|
||||
<h4>{t('About')}</h4>
|
||||
<SimplifiedEditor
|
||||
resetToInitial={clearAbout()}
|
||||
noLimits={true}
|
||||
variant="bordered"
|
||||
onlyBubbleControls={true}
|
||||
smallHeight={true}
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
|
||||
.info {
|
||||
@include font-size(1.4rem);
|
||||
|
||||
border: none;
|
||||
|
||||
// display: flex;
|
||||
|
@ -63,13 +62,11 @@
|
|||
|
||||
.title {
|
||||
@include font-size(2.2rem);
|
||||
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include font-size(1.6rem);
|
||||
|
||||
line-height: 1.4;
|
||||
margin: 0.8rem 0;
|
||||
-webkit-line-clamp: 2;
|
||||
|
@ -107,7 +104,6 @@
|
|||
|
||||
.title {
|
||||
@include font-size(1.4rem);
|
||||
|
||||
font-weight: 500;
|
||||
line-height: 1em;
|
||||
color: var(--blue-500);
|
||||
|
@ -116,9 +112,7 @@
|
|||
|
||||
.description {
|
||||
color: var(--black-400);
|
||||
|
||||
@include font-size(1.2rem);
|
||||
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export const TopicBadge = (props: Props) => {
|
|||
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
||||
|
||||
return (
|
||||
<div class={clsx(styles.TopicBadge, props.subscriptionsMode)}>
|
||||
<div class={clsx(styles.TopicBadge, { [styles.TopicBadgeSubscriptionsMode]: props.subscriptionsMode })}>
|
||||
<div class={styles.content}>
|
||||
<div class={styles.basicInfo}>
|
||||
<Show when={props.subscriptionsMode}>
|
||||
|
|
|
@ -37,7 +37,6 @@ export const ArticleCardSwiper = (props: Props) => {
|
|||
[styles.Swiper]: props.slides.length > 1,
|
||||
[styles.articleMode]: true,
|
||||
[styles.ArticleCardSwiper]: props.slides.length > 1,
|
||||
[styles.unswiped]: props.slides.length === 1,
|
||||
})}
|
||||
>
|
||||
<Show when={props.title}>
|
||||
|
|
|
@ -61,10 +61,10 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
|
|||
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
||||
setConnected(true)
|
||||
} else if (response.status === 401) {
|
||||
throw new Error('unauthorized')
|
||||
throw new Error('SSE: cannot connect to real-time updates')
|
||||
} else {
|
||||
setRetried((r) => r + 1)
|
||||
throw new Error('Internal Error')
|
||||
throw new Error(`SSE: failed to connect ${retried()} times`)
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
LoginInput,
|
||||
ResendVerifyEmailInput,
|
||||
SignupInput,
|
||||
UpdateProfileInput,
|
||||
VerifyEmailInput,
|
||||
} from '@authorizerdev/authorizer-js'
|
||||
import {
|
||||
|
@ -58,6 +59,7 @@ export type SessionContextType = {
|
|||
) => void
|
||||
signUp: (params: SignupInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||
signIn: (params: LoginInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||
updateProfile: (params: UpdateProfileInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||
signOut: () => Promise<void>
|
||||
oauth: (provider: string) => Promise<void>
|
||||
forgotPassword: (
|
||||
|
@ -223,9 +225,12 @@ export const SessionProvider = (props: {
|
|||
const appdata = session()?.user.app_data
|
||||
if (appdata) {
|
||||
const { profile } = appdata
|
||||
setAuthor(profile)
|
||||
addAuthors([profile])
|
||||
if (!profile) loadAuthor()
|
||||
if (profile?.id) {
|
||||
setAuthor(profile)
|
||||
addAuthors([profile])
|
||||
} else {
|
||||
setTimeout(loadAuthor, 15)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -305,6 +310,8 @@ export const SessionProvider = (props: {
|
|||
}
|
||||
const signUp = async (params: SignupInput) => await authenticate(authorizer().signup, params)
|
||||
const signIn = async (params: LoginInput) => await authenticate(authorizer().login, params)
|
||||
const updateProfile = async (params: UpdateProfileInput) =>
|
||||
await authenticate(authorizer().updateProfile, params)
|
||||
|
||||
const signOut = async () => {
|
||||
const authResult: ApiResponse<GenericResponse> = await authorizer().logout()
|
||||
|
@ -381,6 +388,7 @@ export const SessionProvider = (props: {
|
|||
signIn,
|
||||
signOut,
|
||||
confirmEmail,
|
||||
updateProfile,
|
||||
setIsSessionLoaded,
|
||||
setSession,
|
||||
setAuthor,
|
||||
|
|
|
@ -18,7 +18,7 @@ import styles from '../styles/Create.module.scss'
|
|||
const handleCreate = async (layout: LayoutType) => {
|
||||
const shout = await apiClient.createArticle({ article: { layout: layout } })
|
||||
redirectPage(router, 'edit', {
|
||||
shoutId: shout.id.toString(),
|
||||
shoutId: shout?.id.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -100,17 +100,6 @@ h5 {
|
|||
}
|
||||
}
|
||||
|
||||
.passwordToggleControl {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.passwordInput {
|
||||
padding-right: 3em !important;
|
||||
}
|
||||
|
||||
.searchContainer {
|
||||
margin-top: 2.4rem;
|
||||
}
|
||||
|
@ -331,3 +320,12 @@ div[data-lastpass-infield="true"] {
|
|||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
.emailValidationError {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin-top: 0.3em;
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
|
|
@ -6,135 +6,321 @@ import { Icon } from '../../components/_shared/Icon'
|
|||
import { PageLayout } from '../../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
|
||||
import { UpdateProfileInput } from '@authorizerdev/authorizer-js'
|
||||
import { Show, createEffect, createSignal, on } from 'solid-js'
|
||||
import { PasswordField } from '../../components/Nav/AuthModal/PasswordField'
|
||||
import { Button } from '../../components/_shared/Button'
|
||||
import { Loading } from '../../components/_shared/Loading'
|
||||
import { useConfirm } from '../../context/confirm'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useSnackbar } from '../../context/snackbar'
|
||||
import { DEFAULT_HEADER_OFFSET } from '../../stores/router'
|
||||
import { validateEmail } from '../../utils/validateEmail'
|
||||
import styles from './Settings.module.scss'
|
||||
|
||||
type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email'
|
||||
export const ProfileSecurityPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const { updateProfile, session, isSessionLoaded } = useSession()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { showConfirm } = useConfirm()
|
||||
|
||||
const [newPasswordError, setNewPasswordError] = createSignal<string | undefined>()
|
||||
const [oldPasswordError, setOldPasswordError] = createSignal<string | undefined>()
|
||||
const [emailError, setEmailError] = createSignal<string | undefined>()
|
||||
const [isSubmitting, setIsSubmitting] = createSignal<boolean>()
|
||||
const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false)
|
||||
|
||||
const initialState = {
|
||||
oldPassword: undefined,
|
||||
newPassword: undefined,
|
||||
newPasswordConfirm: undefined,
|
||||
email: undefined,
|
||||
}
|
||||
const [formData, setFormData] = createSignal(initialState)
|
||||
const oldPasswordRef: { current: HTMLDivElement } = { current: null }
|
||||
const newPasswordRepeatRef: { current: HTMLDivElement } = { current: null }
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => session()?.user?.email,
|
||||
() => {
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
['email']: session()?.user?.email,
|
||||
}))
|
||||
},
|
||||
),
|
||||
)
|
||||
const handleInputChange = (name: FormField, value: string) => {
|
||||
if (
|
||||
name === 'email' ||
|
||||
(name === 'newPasswordConfirm' && value && value?.length > 0 && !emailError() && !newPasswordError())
|
||||
) {
|
||||
setIsFloatingPanelVisible(true)
|
||||
} else {
|
||||
setIsFloatingPanelVisible(false)
|
||||
}
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[name]: value,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleCancel = async () => {
|
||||
const isConfirmed = await showConfirm({
|
||||
confirmBody: t('Do you really want to reset all changes?'),
|
||||
confirmButtonVariant: 'primary',
|
||||
declineButtonVariant: 'secondary',
|
||||
})
|
||||
if (isConfirmed) {
|
||||
setEmailError()
|
||||
setFormData({
|
||||
...initialState,
|
||||
['email']: session()?.user?.email,
|
||||
})
|
||||
setIsFloatingPanelVisible(false)
|
||||
}
|
||||
}
|
||||
const handleChangeEmail = (_value: string) => {
|
||||
if (!validateEmail(formData()['email'])) {
|
||||
setEmailError(t('Invalid email'))
|
||||
return
|
||||
}
|
||||
}
|
||||
const handleCheckNewPassword = (value: string) => {
|
||||
handleInputChange('newPasswordConfirm', value)
|
||||
if (value !== formData()['newPassword']) {
|
||||
const rect = newPasswordRepeatRef.current.getBoundingClientRect()
|
||||
const topPosition = window.scrollY + rect.top - DEFAULT_HEADER_OFFSET * 2
|
||||
window.scrollTo({
|
||||
top: topPosition,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
showSnackbar({ type: 'error', body: t('Incorrect new password confirm') })
|
||||
setNewPasswordError(t('Passwords are not equal'))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsSubmitting(true)
|
||||
|
||||
const options: UpdateProfileInput = {
|
||||
old_password: formData()['oldPassword'],
|
||||
new_password: formData()['newPassword'] || formData()['oldPassword'],
|
||||
confirm_new_password: formData()['newPassword'] || formData()['oldPassword'],
|
||||
email: formData()['email'],
|
||||
}
|
||||
|
||||
try {
|
||||
const { errors } = await updateProfile(options)
|
||||
if (errors.length > 0) {
|
||||
console.error(errors)
|
||||
if (errors.some((obj) => obj.message === 'incorrect old password')) {
|
||||
setOldPasswordError(t('Incorrect old password'))
|
||||
showSnackbar({ type: 'error', body: t('Incorrect old password') })
|
||||
const rect = oldPasswordRef.current.getBoundingClientRect()
|
||||
const topPosition = window.scrollY + rect.top - DEFAULT_HEADER_OFFSET * 2
|
||||
window.scrollTo({
|
||||
top: topPosition,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
setIsFloatingPanelVisible(false)
|
||||
}
|
||||
return
|
||||
}
|
||||
showSnackbar({ type: 'success', body: t('Profile successfully saved') })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Profile')}>
|
||||
<AuthGuard>
|
||||
<div class="wide-container">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class={clsx('left-navigation', styles.leftNavigation)}>
|
||||
<ProfileSettingsNavigation />
|
||||
<Show when={isSessionLoaded()} fallback={<Loading />}>
|
||||
<div class="wide-container">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<div class={clsx('left-navigation', styles.leftNavigation)}>
|
||||
<ProfileSettingsNavigation />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-19">
|
||||
<div class="row">
|
||||
<div class="col-md-20 col-lg-18 col-xl-16">
|
||||
<h1>Вход и безопасность</h1>
|
||||
<p class="description">Настройки аккаунта, почты, пароля и способов входа.</p>
|
||||
|
||||
<form>
|
||||
<h4>Почта</h4>
|
||||
<div class="pretty-form__item">
|
||||
<input type="text" name="email" id="email" placeholder="Почта" />
|
||||
<label for="email">Почта</label>
|
||||
</div>
|
||||
|
||||
<h4>Изменить пароль</h4>
|
||||
<h5>Текущий пароль</h5>
|
||||
<div class="pretty-form__item">
|
||||
<input
|
||||
type="text"
|
||||
name="password-current"
|
||||
id="password-current"
|
||||
class={clsx(styles.passwordInput, 'nolabel')}
|
||||
/>
|
||||
<button type="button" class={styles.passwordToggleControl}>
|
||||
<Icon name="password-hide" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h5>Новый пароль</h5>
|
||||
<div class="pretty-form__item">
|
||||
<input
|
||||
type="password"
|
||||
name="password-new"
|
||||
id="password-new"
|
||||
class={clsx(styles.passwordInput, 'nolabel')}
|
||||
/>
|
||||
<button type="button" class={styles.passwordToggleControl}>
|
||||
<Icon name="password-open" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h5>Подтвердите новый пароль</h5>
|
||||
<div class="pretty-form__item">
|
||||
<input
|
||||
type="password"
|
||||
name="password-new-confirm"
|
||||
id="password-new-confirm"
|
||||
class={clsx(styles.passwordInput, 'nolabel')}
|
||||
/>
|
||||
<button type="button" class={styles.passwordToggleControl}>
|
||||
<Icon name="password-open" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h4>Социальные сети</h4>
|
||||
<h5>Google</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button class={clsx('button', 'button--light', styles.socialButton)} type="button">
|
||||
<Icon name="google" class={styles.icon} />
|
||||
Привязать
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h5>VK</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button class={clsx(styles.socialButton, 'button', 'button--light')} type="button">
|
||||
<Icon name="vk" class={styles.icon} />
|
||||
Привязать
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h5>Facebook</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button class={clsx(styles.socialButton, 'button', 'button--light')} type="button">
|
||||
<Icon name="facebook" class={styles.icon} />
|
||||
Привязать
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h5>Apple</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button
|
||||
class={clsx(
|
||||
styles.socialButton,
|
||||
styles.socialButtonApple,
|
||||
'button' + ' button--light',
|
||||
)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="apple" class={styles.icon} />
|
||||
Привязать
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<p>
|
||||
<button class="button button--submit" type="submit">
|
||||
Сохранить настройки
|
||||
</button>
|
||||
<div class="col-md-19">
|
||||
<div class="row">
|
||||
<div class="col-md-20 col-lg-18 col-xl-16">
|
||||
<h1>{t('Login and security')}</h1>
|
||||
<p class="description">
|
||||
{t('Settings for account, email, password and login methods.')}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<h4>{t('Email')}</h4>
|
||||
<div class="pretty-form__item">
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
id="email"
|
||||
disabled={isSubmitting()}
|
||||
value={formData()['email'] || ''}
|
||||
placeholder={t('Email')}
|
||||
onFocus={() => setEmailError()}
|
||||
onInput={(event) => handleChangeEmail(event.target.value)}
|
||||
/>
|
||||
<label for="email">{t('Email')}</label>
|
||||
<Show when={emailError()}>
|
||||
<div
|
||||
class={clsx(styles.emailValidationError, {
|
||||
'form-message--error': emailError(),
|
||||
})}
|
||||
>
|
||||
{emailError()}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<h4>{t('Change password')}</h4>
|
||||
<h5>{t('Current password')}</h5>
|
||||
|
||||
<div ref={(el) => (oldPasswordRef.current = el)}>
|
||||
<PasswordField
|
||||
onFocus={() => setOldPasswordError()}
|
||||
setError={oldPasswordError()}
|
||||
onInput={(value) => handleInputChange('oldPassword', value)}
|
||||
value={formData()['oldPassword'] ?? null}
|
||||
disabled={isSubmitting()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h5>{t('New password')}</h5>
|
||||
<PasswordField
|
||||
onInput={(value) => {
|
||||
handleInputChange('newPassword', value)
|
||||
handleInputChange('newPasswordConfirm', '')
|
||||
}}
|
||||
value={formData()['newPassword'] ?? ''}
|
||||
disabled={isSubmitting()}
|
||||
disableAutocomplete={true}
|
||||
/>
|
||||
|
||||
<h5>{t('Confirm your new password')}</h5>
|
||||
<div ref={(el) => (newPasswordRepeatRef.current = el)}>
|
||||
<PasswordField
|
||||
noValidate={true}
|
||||
value={
|
||||
formData()['newPasswordConfirm']?.length > 0
|
||||
? formData()['newPasswordConfirm']
|
||||
: null
|
||||
}
|
||||
onFocus={() => setNewPasswordError()}
|
||||
setError={newPasswordError()}
|
||||
onInput={(value) => handleCheckNewPassword(value)}
|
||||
disabled={isSubmitting()}
|
||||
disableAutocomplete={true}
|
||||
/>
|
||||
</div>
|
||||
<h4>{t('Social networks')}</h4>
|
||||
<h5>Google</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button
|
||||
class={clsx('button', 'button--light', styles.socialButton)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="google" class={styles.icon} />
|
||||
{t('Connect')}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h5>VK</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button
|
||||
class={clsx(styles.socialButton, 'button', 'button--light')}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="vk" class={styles.icon} />
|
||||
{t('Connect')}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h5>Facebook</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button
|
||||
class={clsx(styles.socialButton, 'button', 'button--light')}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="facebook" class={styles.icon} />
|
||||
{t('Connect')}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h5>Apple</h5>
|
||||
<div class="pretty-form__item">
|
||||
<p>
|
||||
<button
|
||||
class={clsx(
|
||||
styles.socialButton,
|
||||
styles.socialButtonApple,
|
||||
'button' + ' button--light',
|
||||
)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="apple" class={styles.icon} />
|
||||
{t('Connect')}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={isFloatingPanelVisible() && !emailError() && !newPasswordError()}>
|
||||
<div class={styles.formActions}>
|
||||
<div class="wide-container">
|
||||
<div class="row">
|
||||
<div class="col-md-19 offset-md-5">
|
||||
<div class="row">
|
||||
<div class="col-md-20 col-lg-18 col-xl-16">
|
||||
<div class={styles.content}>
|
||||
<Button
|
||||
class={styles.cancel}
|
||||
variant="light"
|
||||
value={
|
||||
<>
|
||||
<span class={styles.cancelLabel}>{t('Cancel changes')}</span>
|
||||
<span class={styles.cancelLabelMobile}>{t('Cancel')}</span>
|
||||
</>
|
||||
}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
variant="primary"
|
||||
disabled={isSubmitting()}
|
||||
value={isSubmitting() ? t('Saving...') : t('Save settings')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</AuthGuard>
|
||||
</PageLayout>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user