parent
c261c8cad0
commit
cb5c78790b
|
@ -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",
|
"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",
|
"Restore password": "Restore password",
|
||||||
"Subscribing...": "Subscribing...",
|
"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": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
|
||||||
"Restore password": "Восстановить пароль",
|
"Restore password": "Восстановить пароль",
|
||||||
"Subscribing...": "Подписываем...",
|
"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": "Неверное подтверждение нового пароля"
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,11 +48,13 @@ type Props = {
|
||||||
onChange?: (text: string) => void
|
onChange?: (text: string) => void
|
||||||
variant?: 'minimal' | 'bordered'
|
variant?: 'minimal' | 'bordered'
|
||||||
maxLength?: number
|
maxLength?: number
|
||||||
|
noLimits?: boolean
|
||||||
maxHeight?: number
|
maxHeight?: number
|
||||||
submitButtonText?: string
|
submitButtonText?: string
|
||||||
quoteEnabled?: boolean
|
quoteEnabled?: boolean
|
||||||
imageEnabled?: boolean
|
imageEnabled?: boolean
|
||||||
setClear?: boolean
|
setClear?: boolean
|
||||||
|
resetToInitial?: boolean
|
||||||
smallHeight?: boolean
|
smallHeight?: boolean
|
||||||
submitByCtrlEnter?: boolean
|
submitByCtrlEnter?: boolean
|
||||||
onlyBubbleControls?: boolean
|
onlyBubbleControls?: boolean
|
||||||
|
@ -124,7 +126,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
openOnClick: false,
|
openOnClick: false,
|
||||||
}),
|
}),
|
||||||
CharacterCount.configure({
|
CharacterCount.configure({
|
||||||
limit: maxLength,
|
limit: props.noLimits ? null : maxLength,
|
||||||
}),
|
}),
|
||||||
Blockquote.configure({
|
Blockquote.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
|
@ -216,6 +218,10 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
if (props.setClear) {
|
if (props.setClear) {
|
||||||
editor().commands.clearContent(true)
|
editor().commands.clearContent(true)
|
||||||
}
|
}
|
||||||
|
if (props.resetToInitial) {
|
||||||
|
editor().commands.clearContent(true)
|
||||||
|
editor().commands.setContent(props.initialContent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
|
|
|
@ -16,6 +16,9 @@ type Props = {
|
||||||
onBlur?: (value: string) => void
|
onBlur?: (value: string) => void
|
||||||
variant?: 'login' | 'registration'
|
variant?: 'login' | 'registration'
|
||||||
disableAutocomplete?: boolean
|
disableAutocomplete?: boolean
|
||||||
|
noValidate?: boolean
|
||||||
|
onFocus?: () => void
|
||||||
|
value?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const minLength = 8
|
const minLength = 8
|
||||||
|
@ -27,7 +30,7 @@ export const PasswordField = (props: Props) => {
|
||||||
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: string) => {
|
||||||
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')
|
||||||
}
|
}
|
||||||
|
@ -50,11 +53,13 @@ export const PasswordField = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
props.onInput(value)
|
props.onInput(value)
|
||||||
const errorValue = validatePassword(value)
|
if (!props.noValidate) {
|
||||||
if (errorValue) {
|
const errorValue = validatePassword(value)
|
||||||
setError(errorValue)
|
if (errorValue) {
|
||||||
} else {
|
setError(errorValue)
|
||||||
setError()
|
} else {
|
||||||
|
setError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +83,8 @@ export const PasswordField = (props: Props) => {
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
disabled={props.disabled}
|
disabled={props.disabled}
|
||||||
|
onFocus={props.onFocus}
|
||||||
|
value={props.value ? props.value : ''}
|
||||||
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')}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { useLocalize } from '../../context/localize'
|
||||||
import { useProfileForm } from '../../context/profile'
|
import { useProfileForm } from '../../context/profile'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
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 { hideModal, showModal } from '../../stores/ui'
|
||||||
import { clone } from '../../utils/clone'
|
import { clone } from '../../utils/clone'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
|
@ -35,14 +37,12 @@ import { Loading } from '../_shared/Loading'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
||||||
|
|
||||||
import styles from '../../pages/profile/Settings.module.scss'
|
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor'))
|
const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor'))
|
||||||
const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea'))
|
const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||||
|
|
||||||
export const ProfileSettings = () => {
|
export const ProfileSettings = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [prevForm, setPrevForm] = createStore({})
|
const [prevForm, setPrevForm] = createStore<ProfileInput>({})
|
||||||
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
||||||
const [isSaving, setIsSaving] = createSignal(false)
|
const [isSaving, setIsSaving] = createSignal(false)
|
||||||
const [social, setSocial] = createSignal([])
|
const [social, setSocial] = createSignal([])
|
||||||
|
@ -59,6 +59,7 @@ export const ProfileSettings = () => {
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
const { loadAuthor, session } = useSession()
|
const { loadAuthor, session } = useSession()
|
||||||
const { showConfirm } = useConfirm()
|
const { showConfirm } = useConfirm()
|
||||||
|
const [clearAbout, setClearAbout] = createSignal(false)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (Object.keys(form).length > 0 && !isFormInitialized()) {
|
if (Object.keys(form).length > 0 && !isFormInitialized()) {
|
||||||
|
@ -121,7 +122,9 @@ export const ProfileSettings = () => {
|
||||||
declineButtonVariant: 'secondary',
|
declineButtonVariant: 'secondary',
|
||||||
})
|
})
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
|
setClearAbout(true)
|
||||||
setForm(clone(prevForm))
|
setForm(clone(prevForm))
|
||||||
|
setClearAbout(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,11 +174,13 @@ export const ProfileSettings = () => {
|
||||||
on(
|
on(
|
||||||
() => deepEqual(form, prevForm),
|
() => deepEqual(form, prevForm),
|
||||||
() => {
|
() => {
|
||||||
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
if (Object.keys(prevForm).length > 0) {
|
||||||
|
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleDeleteSocialLink = (link) => {
|
const handleDeleteSocialLink = (link) => {
|
||||||
updateFormField('links', link, true)
|
updateFormField('links', link, true)
|
||||||
}
|
}
|
||||||
|
@ -317,6 +322,8 @@ export const ProfileSettings = () => {
|
||||||
|
|
||||||
<h4>{t('About')}</h4>
|
<h4>{t('About')}</h4>
|
||||||
<SimplifiedEditor
|
<SimplifiedEditor
|
||||||
|
resetToInitial={clearAbout()}
|
||||||
|
noLimits={true}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
onlyBubbleControls={true}
|
onlyBubbleControls={true}
|
||||||
smallHeight={true}
|
smallHeight={true}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
LoginInput,
|
LoginInput,
|
||||||
ResendVerifyEmailInput,
|
ResendVerifyEmailInput,
|
||||||
SignupInput,
|
SignupInput,
|
||||||
|
UpdateProfileInput,
|
||||||
VerifyEmailInput,
|
VerifyEmailInput,
|
||||||
} from '@authorizerdev/authorizer-js'
|
} from '@authorizerdev/authorizer-js'
|
||||||
import {
|
import {
|
||||||
|
@ -58,6 +59,7 @@ export type SessionContextType = {
|
||||||
) => void
|
) => void
|
||||||
signUp: (params: SignupInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
signUp: (params: SignupInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||||
signIn: (params: LoginInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
signIn: (params: LoginInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||||
|
updateProfile: (params: UpdateProfileInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||||
signOut: () => Promise<void>
|
signOut: () => Promise<void>
|
||||||
oauth: (provider: string) => Promise<void>
|
oauth: (provider: string) => Promise<void>
|
||||||
forgotPassword: (
|
forgotPassword: (
|
||||||
|
@ -305,6 +307,8 @@ export const SessionProvider = (props: {
|
||||||
}
|
}
|
||||||
const signUp = async (params: SignupInput) => await authenticate(authorizer().signup, params)
|
const signUp = async (params: SignupInput) => await authenticate(authorizer().signup, params)
|
||||||
const signIn = async (params: LoginInput) => await authenticate(authorizer().login, 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 signOut = async () => {
|
||||||
const authResult: ApiResponse<GenericResponse> = await authorizer().logout()
|
const authResult: ApiResponse<GenericResponse> = await authorizer().logout()
|
||||||
|
@ -381,6 +385,7 @@ export const SessionProvider = (props: {
|
||||||
signIn,
|
signIn,
|
||||||
signOut,
|
signOut,
|
||||||
confirmEmail,
|
confirmEmail,
|
||||||
|
updateProfile,
|
||||||
setIsSessionLoaded,
|
setIsSessionLoaded,
|
||||||
setSession,
|
setSession,
|
||||||
setAuthor,
|
setAuthor,
|
||||||
|
|
|
@ -100,17 +100,6 @@ h5 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.passwordToggleControl {
|
|
||||||
position: absolute;
|
|
||||||
right: 1em;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.passwordInput {
|
|
||||||
padding-right: 3em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchContainer {
|
.searchContainer {
|
||||||
margin-top: 2.4rem;
|
margin-top: 2.4rem;
|
||||||
}
|
}
|
||||||
|
@ -331,3 +320,12 @@ div[data-lastpass-infield="true"] {
|
||||||
opacity: 0 !important;
|
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 { PageLayout } from '../../components/_shared/PageLayout'
|
||||||
import { useLocalize } from '../../context/localize'
|
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'
|
import styles from './Settings.module.scss'
|
||||||
|
|
||||||
|
type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email'
|
||||||
export const ProfileSecurityPage = () => {
|
export const ProfileSecurityPage = () => {
|
||||||
const { t } = useLocalize()
|
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 (
|
return (
|
||||||
<PageLayout title={t('Profile')}>
|
<PageLayout title={t('Profile')}>
|
||||||
<AuthGuard>
|
<AuthGuard>
|
||||||
<div class="wide-container">
|
<Show when={isSessionLoaded()} fallback={<Loading />}>
|
||||||
<div class="row">
|
<div class="wide-container">
|
||||||
<div class="col-md-5">
|
<div class="row">
|
||||||
<div class={clsx('left-navigation', styles.leftNavigation)}>
|
<div class="col-md-5">
|
||||||
<ProfileSettingsNavigation />
|
<div class={clsx('left-navigation', styles.leftNavigation)}>
|
||||||
|
<ProfileSettingsNavigation />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-19">
|
<div class="col-md-19">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-20 col-lg-18 col-xl-16">
|
<div class="col-md-20 col-lg-18 col-xl-16">
|
||||||
<h1>Вход и безопасность</h1>
|
<h1>{t('Login and security')}</h1>
|
||||||
<p class="description">Настройки аккаунта, почты, пароля и способов входа.</p>
|
<p class="description">
|
||||||
|
{t('Settings for account, email, password and login methods.')}
|
||||||
<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>
|
|
||||||
</p>
|
</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>
|
</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>
|
</AuthGuard>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user