2022-11-28 22:14:19 +00:00
|
|
|
import { clsx } from 'clsx'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-09-21 11:38:22 +00:00
|
|
|
import { AuthGuard } from '../../components/AuthGuard'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { ProfileSettingsNavigation } from '../../components/Nav/ProfileSettingsNavigation'
|
2024-02-04 11:25:21 +00:00
|
|
|
import { Icon } from '../../components/_shared/Icon'
|
|
|
|
import { PageLayout } from '../../components/_shared/PageLayout'
|
2023-11-14 10:45:44 +00:00
|
|
|
import { useLocalize } from '../../context/localize'
|
2022-11-28 22:14:19 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
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'
|
2023-11-14 15:10:00 +00:00
|
|
|
import styles from './Settings.module.scss'
|
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email'
|
2023-02-10 01:19:20 +00:00
|
|
|
export const ProfileSecurityPage = () => {
|
2023-11-14 10:45:44 +00:00
|
|
|
const { t } = useLocalize()
|
2024-05-12 23:36:46 +00:00
|
|
|
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,
|
2024-05-20 23:15:52 +00:00
|
|
|
(email) => {
|
2024-05-12 23:36:46 +00:00
|
|
|
setFormData((prevData) => ({
|
|
|
|
...prevData,
|
2024-05-20 23:15:52 +00:00
|
|
|
email,
|
2024-05-12 23:36:46 +00:00
|
|
|
}))
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2023-11-14 10:45:44 +00:00
|
|
|
|
2022-11-28 22:14:19 +00:00
|
|
|
return (
|
2023-11-14 10:45:44 +00:00
|
|
|
<PageLayout title={t('Profile')}>
|
2023-09-21 11:38:22 +00:00
|
|
|
<AuthGuard>
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2023-09-21 11:38:22 +00:00
|
|
|
</div>
|
2022-11-28 22:14:19 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2022-11-30 18:15:01 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2023-03-23 22:05:23 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<h4>{t('Change password')}</h4>
|
|
|
|
<h5>{t('Current password')}</h5>
|
2022-11-30 18:15:01 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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}
|
2023-09-21 11:38:22 +00:00
|
|
|
/>
|
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2023-03-23 22:05:23 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2023-09-21 11:38:22 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2023-09-21 11:38:22 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
|
|
|
</Show>
|
2023-09-21 11:38:22 +00:00
|
|
|
|
2024-05-12 23:36:46 +00:00
|
|
|
<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>
|
2023-09-21 11:38:22 +00:00
|
|
|
</div>
|
2023-03-23 22:05:23 +00:00
|
|
|
</div>
|
2022-11-28 22:14:19 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2024-05-12 23:36:46 +00:00
|
|
|
</Show>
|
2023-09-21 11:38:22 +00:00
|
|
|
</AuthGuard>
|
2023-02-17 09:21:02 +00:00
|
|
|
</PageLayout>
|
2022-11-28 22:14:19 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-02-17 09:21:02 +00:00
|
|
|
export const Page = ProfileSecurityPage
|