diff --git a/src/components/ProfileSettings/index.ts b/src/components/ProfileSettings/index.ts deleted file mode 100644 index af814349..00000000 --- a/src/components/ProfileSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProfileSettings } from './ProfileSettings' diff --git a/src/components/Views/Profile/ProfileSecurity.tsx b/src/components/Views/Profile/ProfileSecurity.tsx new file mode 100644 index 00000000..934a3dc1 --- /dev/null +++ b/src/components/Views/Profile/ProfileSecurity.tsx @@ -0,0 +1,305 @@ +import { UpdateProfileInput } from '@authorizerdev/authorizer-js' +import { clsx } from 'clsx' +import { Show, createEffect, createSignal, on } from 'solid-js' +import { AuthGuard } from '~/components/AuthGuard' +import { PasswordField } from '~/components/Nav/AuthModal/PasswordField' +import { ProfileSettingsNavigation } from '~/components/Nav/ProfileSettingsNavigation' +import { Button } from '~/components/_shared/Button' +import { Icon } from '~/components/_shared/Icon' +import { Loading } from '~/components/_shared/Loading' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { DEFAULT_HEADER_OFFSET, useSnackbar, useUI } from '~/context/ui' +import { validateEmail } from '~/utils/validateEmail' +import styles from './Settings.module.scss' + +type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email' +type FormData = Record + +// biome-ignore lint/suspicious/noExplicitAny: +export const ProfileSecurityView = (_props: any) => { + const { t } = useLocalize() + const { updateProfile, session, isSessionLoaded } = useSession() + const { showConfirm } = useUI() + const { showSnackbar } = useSnackbar() + const [newPasswordError, setNewPasswordError] = createSignal() + const [oldPasswordError, setOldPasswordError] = createSignal() + const [emailError, setEmailError] = createSignal() + const [isSubmitting, setIsSubmitting] = createSignal() + const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false) + + const initialState = { + oldPassword: undefined, + newPassword: undefined, + newPasswordConfirm: undefined, + email: undefined + } as FormData + + const [formData, setFormData] = createSignal(initialState) + let oldPasswordRef: HTMLDivElement | undefined + let newPasswordRepeatRef: HTMLDivElement | undefined + + createEffect( + on( + () => session()?.user?.email, + (email) => { + setFormData((prevData: FormData) => ({ ...prevData, email }) as FormData) + } + ) + ) + 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 (formData() && !validateEmail(formData()['email'] || '')) { + setEmailError(t('Invalid email')) + return + } + } + const handleCheckNewPassword = (value: string) => { + handleInputChange('newPasswordConfirm', value) + if (newPasswordRepeatRef && value !== formData()['newPassword']) { + const rect = newPasswordRepeatRef.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 result = await updateProfile(options) + if (result) { + // FIXME: const { errors } = result + if (oldPasswordRef) { + // && errors.some((obj: Error) => obj.message === 'incorrect old password')) { + setOldPasswordError(t('Incorrect old password')) + showSnackbar({ type: 'error', body: t('Incorrect old password') }) + const rect = oldPasswordRef.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 ( + + }> +
+
+
+
+ +
+
+ +
+
+
+

{t('Login and security')}

+

{t('Settings for account, email, password and login methods.')}

+ +
+

{t('Email')}

+
+ setEmailError()} + onInput={(event) => handleChangeEmail(event.target.value)} + /> + + +
+ {emailError()} +
+
+
+ +

{t('Change password')}

+
{t('Current password')}
+ +
(oldPasswordRef = el)}> + setOldPasswordError()} + setError={oldPasswordError()} + onInput={(value) => handleInputChange('oldPassword', value)} + value={formData()['oldPassword'] || undefined} + disabled={isSubmitting()} + /> +
+ +
{t('New password')}
+ { + handleInputChange('newPassword', value) + handleInputChange('newPasswordConfirm', '') + }} + value={formData()['newPassword'] ?? ''} + disabled={isSubmitting()} + disableAutocomplete={true} + /> + +
{t('Confirm your new password')}
+
(newPasswordRepeatRef = el)}> + setNewPasswordError()} + setError={newPasswordError()} + onInput={(value) => handleCheckNewPassword(value)} + disabled={isSubmitting()} + disableAutocomplete={true} + /> +
+

{t('Social networks')}

+
Google
+
+

+ +

+
+ +
VK
+
+

+ +

+
+ +
Facebook
+
+

+ +

+
+ +
Apple
+
+

+ +

+
+ +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/Views/Profile/ProfileSettings.tsx similarity index 97% rename from src/components/ProfileSettings/ProfileSettings.tsx rename to src/components/Views/Profile/ProfileSettings.tsx index 2681ccba..6179ac2a 100644 --- a/src/components/ProfileSettings/ProfileSettings.tsx +++ b/src/components/Views/Profile/ProfileSettings.tsx @@ -24,14 +24,14 @@ import { handleImageUpload } from '~/lib/handleImageUpload' import { profileSocialLinks } from '~/lib/profileSocialLinks' import { clone } from '~/utils/clone' import { validateUrl } from '~/utils/validateUrl' -import { Modal } from '../Nav/Modal' -import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation' -import { Button } from '../_shared/Button' -import { Icon } from '../_shared/Icon' -import { ImageCropper } from '../_shared/ImageCropper' -import { Loading } from '../_shared/Loading' -import { Popover } from '../_shared/Popover' -import { SocialNetworkInput } from '../_shared/SocialNetworkInput' +import { Modal } from '../../Nav/Modal' +import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation' +import { Button } from '../../_shared/Button' +import { Icon } from '../../_shared/Icon' +import { ImageCropper } from '../../_shared/ImageCropper' +import { Loading } from '../../_shared/Loading' +import { Popover } from '../../_shared/Popover' +import { SocialNetworkInput } from '../../_shared/SocialNetworkInput' import styles from './Settings.module.scss' const SimplifiedEditor = lazy(() => import('~/components/Editor/SimplifiedEditor')) diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.module.scss b/src/components/Views/Profile/ProfileSubscriptions.module.scss similarity index 100% rename from src/components/Views/ProfileSubscriptions/ProfileSubscriptions.module.scss rename to src/components/Views/Profile/ProfileSubscriptions.module.scss diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx b/src/components/Views/Profile/ProfileSubscriptions.tsx similarity index 98% rename from src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx rename to src/components/Views/Profile/ProfileSubscriptions.tsx index 9d248220..8d23eeac 100644 --- a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx +++ b/src/components/Views/Profile/ProfileSubscriptions.tsx @@ -9,8 +9,8 @@ import { dummyFilter } from '~/lib/dummyFilter' import stylesSettings from '../../../styles/FeedSettings.module.scss' import { AuthorBadge } from '../../Author/AuthorBadge' import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation' -import styles from '../../ProfileSettings/Settings.module.scss' import { TopicBadge } from '../../Topic/TopicBadge' +import styles from '../Profile/Settings.module.scss' export const ProfileSubscriptions = () => { const { t, lang } = useLocalize() diff --git a/src/components/ProfileSettings/Settings.module.scss b/src/components/Views/Profile/Settings.module.scss similarity index 100% rename from src/components/ProfileSettings/Settings.module.scss rename to src/components/Views/Profile/Settings.module.scss diff --git a/src/components/Views/ProfileSubscriptions/index.ts b/src/components/Views/ProfileSubscriptions/index.ts deleted file mode 100644 index 17849841..00000000 --- a/src/components/Views/ProfileSubscriptions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProfileSubscriptions } from './ProfileSubscriptions' diff --git a/src/routes/profile/(settings).tsx b/src/routes/profile/(settings).tsx index 2a49caa4..a9fee019 100644 --- a/src/routes/profile/(settings).tsx +++ b/src/routes/profile/(settings).tsx @@ -1,5 +1,5 @@ import { AuthGuard } from '~/components/AuthGuard' -import { ProfileSettings } from '~/components/ProfileSettings' +import { ProfileSettings } from '~/components/Views/Profile/ProfileSettings' import { PageLayout } from '~/components/_shared/PageLayout' import { useLocalize } from '~/context/localize' import { ProfileProvider } from '~/context/profile' diff --git a/src/routes/profile/security.tsx b/src/routes/profile/security.tsx index 7e7371b0..187a56ed 100644 --- a/src/routes/profile/security.tsx +++ b/src/routes/profile/security.tsx @@ -1,317 +1,17 @@ -import { UpdateProfileInput } from '@authorizerdev/authorizer-js' -import { clsx } from 'clsx' -import { Show, createEffect, createSignal, on } from 'solid-js' import { AuthGuard } from '~/components/AuthGuard' -import { PasswordField } from '~/components/Nav/AuthModal/PasswordField' -import { ProfileSettingsNavigation } from '~/components/Nav/ProfileSettingsNavigation' -import { Button } from '~/components/_shared/Button' -import { Icon } from '~/components/_shared/Icon' -import { Loading } from '~/components/_shared/Loading' +import { ProfileSecurityView } from '~/components/Views/Profile/ProfileSecurity' import { PageLayout } from '~/components/_shared/PageLayout' import { useLocalize } from '~/context/localize' -import { useSession } from '~/context/session' -import { DEFAULT_HEADER_OFFSET, useSnackbar, useUI } from '~/context/ui' -import { validateEmail } from '~/utils/validateEmail' -import styles from './Settings.module.scss' +import { ProfileProvider } from '~/context/profile' -type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email' -type FormData = Record export default () => { const { t } = useLocalize() - const { updateProfile, session, isSessionLoaded } = useSession() - const { showSnackbar } = useSnackbar() - const { showConfirm } = useUI() - const [newPasswordError, setNewPasswordError] = createSignal() - const [oldPasswordError, setOldPasswordError] = createSignal() - const [emailError, setEmailError] = createSignal() - const [isSubmitting, setIsSubmitting] = createSignal() - const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false) - - const initialState = { - oldPassword: undefined, - newPassword: undefined, - newPasswordConfirm: undefined, - email: undefined - } as FormData - - const [formData, setFormData] = createSignal(initialState) - let oldPasswordRef: HTMLDivElement | undefined - let newPasswordRepeatRef: HTMLDivElement | undefined - - createEffect( - on( - () => session()?.user?.email, - (email) => { - setFormData((prevData: FormData) => ({ ...prevData, email }) as FormData) - } - ) - ) - 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 (formData() && !validateEmail(formData()['email'] || '')) { - setEmailError(t('Invalid email')) - return - } - } - const handleCheckNewPassword = (value: string) => { - handleInputChange('newPasswordConfirm', value) - if (newPasswordRepeatRef && value !== formData()['newPassword']) { - const rect = newPasswordRepeatRef.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 result = await updateProfile(options) - if (result) { - // FIXME: const { errors } = result - if (oldPasswordRef) { - // && errors.some((obj: Error) => obj.message === 'incorrect old password')) { - setOldPasswordError(t('Incorrect old password')) - showSnackbar({ type: 'error', body: t('Incorrect old password') }) - const rect = oldPasswordRef.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 ( - }> -
-
-
-
- -
-
- -
-
-
-

{t('Login and security')}

-

- {t('Settings for account, email, password and login methods.')} -

- -
-

{t('Email')}

-
- setEmailError()} - onInput={(event) => handleChangeEmail(event.target.value)} - /> - - -
- {emailError()} -
-
-
- -

{t('Change password')}

-
{t('Current password')}
- -
(oldPasswordRef = el)}> - setOldPasswordError()} - setError={oldPasswordError()} - onInput={(value) => handleInputChange('oldPassword', value)} - value={formData()['oldPassword'] || undefined} - disabled={isSubmitting()} - /> -
- -
{t('New password')}
- { - handleInputChange('newPassword', value) - handleInputChange('newPasswordConfirm', '') - }} - value={formData()['newPassword'] ?? ''} - disabled={isSubmitting()} - disableAutocomplete={true} - /> - -
{t('Confirm your new password')}
-
(newPasswordRepeatRef = el)}> - setNewPasswordError()} - setError={newPasswordError()} - onInput={(value) => handleCheckNewPassword(value)} - disabled={isSubmitting()} - disableAutocomplete={true} - /> -
-

{t('Social networks')}

-
Google
-
-

- -

-
- -
VK
-
-

- -

-
- -
Facebook
-
-

- -

-
- -
Apple
-
-

- -

-
- -
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ + +
) diff --git a/src/routes/profile/subs.tsx b/src/routes/profile/subs.tsx index 258ad112..8fadc8b9 100644 --- a/src/routes/profile/subs.tsx +++ b/src/routes/profile/subs.tsx @@ -1,5 +1,5 @@ import { AuthGuard } from '~/components/AuthGuard' -import { ProfileSubscriptions } from '~/components/Views/ProfileSubscriptions' +import { ProfileSubscriptions } from '~/components/Views/Profile/ProfileSubscriptions' import { PageLayout } from '~/components/_shared/PageLayout' import { useLocalize } from '~/context/localize'