diff --git a/packages/app/components/profile/AccountDeleted.story.tsx b/packages/app/components/profile/AccountDeleted.story.tsx new file mode 100644 index 0000000..60b72e9 --- /dev/null +++ b/packages/app/components/profile/AccountDeleted.story.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +import { ProfileLayout } from 'app/components/profile/Profile.story'; + +import AccountDeleted from './AccountDeleted'; + +storiesOf('Components/Profile', module).add('AccountDeleted', () => ( + + + +)); diff --git a/packages/app/components/profile/AccountDeleted.tsx b/packages/app/components/profile/AccountDeleted.tsx new file mode 100644 index 0000000..8f5b255 --- /dev/null +++ b/packages/app/components/profile/AccountDeleted.tsx @@ -0,0 +1,48 @@ +import React, { ComponentType } from 'react'; +import { FormattedMessage as Message } from 'react-intl'; +import { Helmet } from 'react-helmet-async'; + +import { Button } from 'app/components/ui/form'; + +import styles from './accountDeleted.scss'; + +interface Props { + onRestore?: () => void; +} + +const AccountDeleted: ComponentType = ({ onRestore }) => { + return ( +
+ + {(pageTitle: string) => ( +

+ + {pageTitle} +

+ )} +
+ +
+ +
+ +
+ +
+ + +
+ ); +}; + +export default AccountDeleted; diff --git a/packages/app/components/profile/Profile.story.tsx b/packages/app/components/profile/Profile.story.tsx index 637f119..4539646 100644 --- a/packages/app/components/profile/Profile.story.tsx +++ b/packages/app/components/profile/Profile.story.tsx @@ -22,6 +22,7 @@ storiesOf('Components/Profile', module).add('Profile', () => ( hasMojangUsernameCollision: true, isActive: true, isGuest: false, + isDeleted: false, isOtpEnabled: true, lang: 'unknown', passwordChangedAt: 1595328712, diff --git a/packages/app/components/profile/accountDeleted.scss b/packages/app/components/profile/accountDeleted.scss new file mode 100644 index 0000000..ccd025d --- /dev/null +++ b/packages/app/components/profile/accountDeleted.scss @@ -0,0 +1,24 @@ +.wrapper { + text-align: center; + + @media (min-height: 600px) { + margin-top: 140px; + } +} + +.title { + composes: indexTitle from '~app/components/profile/profile.scss'; + + margin-bottom: 25px; +} + +.description { + composes: indexDescription from '~app/components/profile/profile.scss'; + + margin: 0 auto 20px auto; + max-width: 330px; + + &:last-of-type { + margin-bottom: 25px; + } +} diff --git a/packages/app/components/user/reducer.ts b/packages/app/components/user/reducer.ts index 87b5e30..06bcef1 100644 --- a/packages/app/components/user/reducer.ts +++ b/packages/app/components/user/reducer.ts @@ -10,6 +10,7 @@ export interface User { lang: string; isGuest: boolean; isActive: boolean; + isDeleted: boolean; isOtpEnabled: boolean; passwordChangedAt: number; hasMojangUsernameCollision: boolean; @@ -31,6 +32,7 @@ const defaults: State = { avatar: '', lang: '', isActive: false, + isDeleted: false, isOtpEnabled: false, shouldAcceptRules: false, // whether user need to review updated rules passwordChangedAt: 0, diff --git a/packages/app/pages/profile/AccountDeletedPage.tsx b/packages/app/pages/profile/AccountDeletedPage.tsx new file mode 100644 index 0000000..d633d3b --- /dev/null +++ b/packages/app/pages/profile/AccountDeletedPage.tsx @@ -0,0 +1,26 @@ +import React, { ComponentType, useCallback, useContext } from 'react'; + +import { useReduxDispatch } from 'app/functions'; +import { restoreAccount } from 'app/services/api/accounts'; +import { updateUser } from 'app/components/user/actions'; +import ProfileContext from 'app/components/profile/Context'; + +import AccountDeleted from 'app/components/profile/AccountDeleted'; + +const AccountDeletedPage: ComponentType = () => { + const dispatch = useReduxDispatch(); + const context = useContext(ProfileContext); + const onRestore = useCallback(async () => { + await restoreAccount(context.userId); + dispatch( + updateUser({ + isDeleted: false, + }), + ); + context.goToProfile(); + }, [dispatch, context]); + + return ; +}; + +export default AccountDeletedPage; diff --git a/packages/app/pages/profile/DeleteAccountPage.tsx b/packages/app/pages/profile/DeleteAccountPage.tsx index 44d495c..48236d2 100644 --- a/packages/app/pages/profile/DeleteAccountPage.tsx +++ b/packages/app/pages/profile/DeleteAccountPage.tsx @@ -1,7 +1,7 @@ import React, { ComponentType, useCallback, useContext, useRef } from 'react'; import { useReduxDispatch } from 'app/functions'; -import { changePassword } from 'app/services/api/accounts'; +import { deleteAccount } from 'app/services/api/accounts'; import { FormModel } from 'app/components/ui/form'; import DeleteAccount from 'app/components/profile/deleteAccount'; import { updateUser } from 'app/components/user/actions'; @@ -16,13 +16,12 @@ const DeleteAccountPage: ComponentType = () => { context .onSubmit({ form, - // TODO: rework - sendData: () => changePassword(context.userId, form.serialize()), + sendData: () => deleteAccount(context.userId, form.serialize()), }) .then(() => { dispatch( updateUser({ - passwordChangedAt: Date.now() / 1000, + isDeleted: true, }), ); context.goToProfile(); diff --git a/packages/app/pages/profile/ProfileController.tsx b/packages/app/pages/profile/ProfileController.tsx index 332b6a3..662d114 100644 --- a/packages/app/pages/profile/ProfileController.tsx +++ b/packages/app/pages/profile/ProfileController.tsx @@ -11,7 +11,7 @@ import { browserHistory } from 'app/services/history'; import { FooterMenu } from 'app/components/footerMenu'; import { FormModel } from 'app/components/ui/form'; import { Provider } from 'app/components/profile/Context'; -import { ComponentLoader } from 'app/components/ui/loader'; +import { User } from 'app/components/user'; import styles from './profile.scss'; @@ -21,14 +21,15 @@ import ChangeUsernamePage from 'app/pages/profile/ChangeUsernamePage'; import ChangeEmailPage from 'app/pages/profile/ChangeEmailPage'; import MultiFactorAuthPage from 'app/pages/profile/MultiFactorAuthPage'; import DeleteAccountPage from 'app/pages/profile/DeleteAccountPage'; +import AccountDeletedPage from 'app/pages/profile/AccountDeletedPage'; interface Props { - userId: number; + user: User; onSubmit: (options: { form: FormModel; sendData: () => Promise }) => Promise; refreshUserData: () => Promise; } -const ProfileController: ComponentType = ({ userId, onSubmit, refreshUserData }) => { +const ProfileController: ComponentType = ({ user, onSubmit, refreshUserData }) => { const goToProfile = useCallback(async () => { await refreshUserData(); @@ -39,12 +40,14 @@ const ProfileController: ComponentType = ({ userId, onSubmit, refreshUser
- }> + {user.isDeleted ? ( + + ) : ( @@ -56,7 +59,7 @@ const ProfileController: ComponentType = ({ userId, onSubmit, refreshUser - + )}
@@ -68,7 +71,7 @@ const ProfileController: ComponentType = ({ userId, onSubmit, refreshUser export default connect( (state) => ({ - userId: state.user.id!, + user: state.user, }), { refreshUserData, diff --git a/packages/app/services/api/accounts.ts b/packages/app/services/api/accounts.ts index d07618a..6483443 100644 --- a/packages/app/services/api/accounts.ts +++ b/packages/app/services/api/accounts.ts @@ -86,3 +86,13 @@ export function confirmNewEmail(id: number, key: string): Promise<{ success: boo key, }); } + +export function deleteAccount(id: number, { password }: { password?: string }): Promise<{ success: boolean }> { + return request.delete(`/api/v1/accounts/${id}`, { + password, + }); +} + +export function restoreAccount(id: number): Promise<{ success: boolean }> { + return request.post(`/api/v1/accounts/${id}/restore`); +}