mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-26 16:52:06 +05:30
Implemented handlers to delete/restore account. Implemented "account deleted" page
This commit is contained in:
parent
9247ad7178
commit
c0b3e328b6
13
packages/app/components/profile/AccountDeleted.story.tsx
Normal file
13
packages/app/components/profile/AccountDeleted.story.tsx
Normal file
@ -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', () => (
|
||||||
|
<ProfileLayout>
|
||||||
|
<AccountDeleted onRestore={action('onRestore')} />
|
||||||
|
</ProfileLayout>
|
||||||
|
));
|
48
packages/app/components/profile/AccountDeleted.tsx
Normal file
48
packages/app/components/profile/AccountDeleted.tsx
Normal file
@ -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<Props> = ({ onRestore }) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<Message key="accountDeleted" defaultMessage="Account deleted">
|
||||||
|
{(pageTitle: string) => (
|
||||||
|
<h2 className={styles.title}>
|
||||||
|
<Helmet title={pageTitle} />
|
||||||
|
{pageTitle}
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
</Message>
|
||||||
|
|
||||||
|
<div className={styles.description}>
|
||||||
|
<Message
|
||||||
|
key="accountDeletedDescription"
|
||||||
|
// TODO: verify translation
|
||||||
|
defaultMessage="The account has been marked for deletion and will be permanently removed within a week. Until then, all activity on the account has been suspended."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.description}>
|
||||||
|
<Message
|
||||||
|
key="ifYouWantToRestoreAccount"
|
||||||
|
// TODO: verify translation
|
||||||
|
defaultMessage="If you want to restore your account, click on the button below."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={onRestore} color="black" small>
|
||||||
|
<Message key="restoreAccount" defaultMessage="Restore account" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountDeleted;
|
@ -22,6 +22,7 @@ storiesOf('Components/Profile', module).add('Profile', () => (
|
|||||||
hasMojangUsernameCollision: true,
|
hasMojangUsernameCollision: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isGuest: false,
|
isGuest: false,
|
||||||
|
isDeleted: false,
|
||||||
isOtpEnabled: true,
|
isOtpEnabled: true,
|
||||||
lang: 'unknown',
|
lang: 'unknown',
|
||||||
passwordChangedAt: 1595328712,
|
passwordChangedAt: 1595328712,
|
||||||
|
24
packages/app/components/profile/accountDeleted.scss
Normal file
24
packages/app/components/profile/accountDeleted.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ export interface User {
|
|||||||
lang: string;
|
lang: string;
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
isDeleted: boolean;
|
||||||
isOtpEnabled: boolean;
|
isOtpEnabled: boolean;
|
||||||
passwordChangedAt: number;
|
passwordChangedAt: number;
|
||||||
hasMojangUsernameCollision: boolean;
|
hasMojangUsernameCollision: boolean;
|
||||||
@ -31,6 +32,7 @@ const defaults: State = {
|
|||||||
avatar: '',
|
avatar: '',
|
||||||
lang: '',
|
lang: '',
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
isDeleted: false,
|
||||||
isOtpEnabled: false,
|
isOtpEnabled: false,
|
||||||
shouldAcceptRules: false, // whether user need to review updated rules
|
shouldAcceptRules: false, // whether user need to review updated rules
|
||||||
passwordChangedAt: 0,
|
passwordChangedAt: 0,
|
||||||
|
26
packages/app/pages/profile/AccountDeletedPage.tsx
Normal file
26
packages/app/pages/profile/AccountDeletedPage.tsx
Normal file
@ -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 <AccountDeleted onRestore={onRestore} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountDeletedPage;
|
@ -1,7 +1,7 @@
|
|||||||
import React, { ComponentType, useCallback, useContext, useRef } from 'react';
|
import React, { ComponentType, useCallback, useContext, useRef } from 'react';
|
||||||
|
|
||||||
import { useReduxDispatch } from 'app/functions';
|
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 { FormModel } from 'app/components/ui/form';
|
||||||
import DeleteAccount from 'app/components/profile/deleteAccount';
|
import DeleteAccount from 'app/components/profile/deleteAccount';
|
||||||
import { updateUser } from 'app/components/user/actions';
|
import { updateUser } from 'app/components/user/actions';
|
||||||
@ -16,13 +16,12 @@ const DeleteAccountPage: ComponentType = () => {
|
|||||||
context
|
context
|
||||||
.onSubmit({
|
.onSubmit({
|
||||||
form,
|
form,
|
||||||
// TODO: rework
|
sendData: () => deleteAccount(context.userId, form.serialize()),
|
||||||
sendData: () => changePassword(context.userId, form.serialize()),
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateUser({
|
updateUser({
|
||||||
passwordChangedAt: Date.now() / 1000,
|
isDeleted: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
context.goToProfile();
|
context.goToProfile();
|
||||||
|
@ -11,7 +11,7 @@ import { browserHistory } from 'app/services/history';
|
|||||||
import { FooterMenu } from 'app/components/footerMenu';
|
import { FooterMenu } from 'app/components/footerMenu';
|
||||||
import { FormModel } from 'app/components/ui/form';
|
import { FormModel } from 'app/components/ui/form';
|
||||||
import { Provider } from 'app/components/profile/Context';
|
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';
|
import styles from './profile.scss';
|
||||||
|
|
||||||
@ -21,14 +21,15 @@ import ChangeUsernamePage from 'app/pages/profile/ChangeUsernamePage';
|
|||||||
import ChangeEmailPage from 'app/pages/profile/ChangeEmailPage';
|
import ChangeEmailPage from 'app/pages/profile/ChangeEmailPage';
|
||||||
import MultiFactorAuthPage from 'app/pages/profile/MultiFactorAuthPage';
|
import MultiFactorAuthPage from 'app/pages/profile/MultiFactorAuthPage';
|
||||||
import DeleteAccountPage from 'app/pages/profile/DeleteAccountPage';
|
import DeleteAccountPage from 'app/pages/profile/DeleteAccountPage';
|
||||||
|
import AccountDeletedPage from 'app/pages/profile/AccountDeletedPage';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
userId: number;
|
user: User;
|
||||||
onSubmit: (options: { form: FormModel; sendData: () => Promise<any> }) => Promise<void>;
|
onSubmit: (options: { form: FormModel; sendData: () => Promise<any> }) => Promise<void>;
|
||||||
refreshUserData: () => Promise<any>;
|
refreshUserData: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileController: ComponentType<Props> = ({ userId, onSubmit, refreshUserData }) => {
|
const ProfileController: ComponentType<Props> = ({ user, onSubmit, refreshUserData }) => {
|
||||||
const goToProfile = useCallback(async () => {
|
const goToProfile = useCallback(async () => {
|
||||||
await refreshUserData();
|
await refreshUserData();
|
||||||
|
|
||||||
@ -39,12 +40,14 @@ const ProfileController: ComponentType<Props> = ({ userId, onSubmit, refreshUser
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Provider
|
<Provider
|
||||||
value={{
|
value={{
|
||||||
userId,
|
userId: user.id!,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
goToProfile,
|
goToProfile,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<React.Suspense fallback={<ComponentLoader />}>
|
{user.isDeleted ? (
|
||||||
|
<AccountDeletedPage />
|
||||||
|
) : (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/profile/mfa/step:step([1-3])" component={MultiFactorAuthPage} />
|
<Route path="/profile/mfa/step:step([1-3])" component={MultiFactorAuthPage} />
|
||||||
<Route path="/profile/mfa" exact component={MultiFactorAuthPage} />
|
<Route path="/profile/mfa" exact component={MultiFactorAuthPage} />
|
||||||
@ -56,7 +59,7 @@ const ProfileController: ComponentType<Props> = ({ userId, onSubmit, refreshUser
|
|||||||
<Route path="/" exact component={Profile} />
|
<Route path="/" exact component={Profile} />
|
||||||
<Redirect to="/404" />
|
<Redirect to="/404" />
|
||||||
</Switch>
|
</Switch>
|
||||||
</React.Suspense>
|
)}
|
||||||
|
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
<FooterMenu />
|
<FooterMenu />
|
||||||
@ -68,7 +71,7 @@ const ProfileController: ComponentType<Props> = ({ userId, onSubmit, refreshUser
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state) => ({
|
(state) => ({
|
||||||
userId: state.user.id!,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
refreshUserData,
|
refreshUserData,
|
||||||
|
@ -86,3 +86,13 @@ export function confirmNewEmail(id: number, key: string): Promise<{ success: boo
|
|||||||
key,
|
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`);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user