mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-22 08:47:10 +08:00
feat(core): add account deletion entry to account settings (#12385)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Implemented account deletion functionality with confirmation dialogs and success notifications. - Added a warning modal for team workspace owners before account deletion. - Introduced a new, richly formatted internationalized message for account deletion confirmation. - Added a new dialog component to inform users of successful account deletion. - **Improvements** - Updated localization strings to provide detailed guidance and warnings for account deletion. - Enhanced error handling by converting errors into user-friendly notifications. - Simplified and improved the sign-out process with better error handling and streamlined navigation. - **Style** - Added new style constants for success and warning modals related to account deletion. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -3,11 +3,10 @@ import {
|
|||||||
notify,
|
notify,
|
||||||
useConfirmModal,
|
useConfirmModal,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { AuthService, ServerService } from '@affine/core/modules/cloud';
|
import { AuthService } from '@affine/core/modules/cloud';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { UserFriendlyError } from '@affine/error';
|
||||||
import { WorkspacesService } from '@affine/core/modules/workspace';
|
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useNavigateHelper } from '../use-navigate-helper';
|
import { useNavigateHelper } from '../use-navigate-helper';
|
||||||
@@ -26,47 +25,21 @@ export const useSignOut = ({
|
|||||||
}: ConfirmModalProps = {}) => {
|
}: ConfirmModalProps = {}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
const { openPage } = useNavigateHelper();
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
|
|
||||||
const serverService = useService(ServerService);
|
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
const workspacesService = useService(WorkspacesService);
|
|
||||||
const globalContextService = useService(GlobalContextService);
|
|
||||||
|
|
||||||
const workspaces = useLiveData(workspacesService.list.workspaces$);
|
|
||||||
const currentWorkspaceFlavour = useLiveData(
|
|
||||||
globalContextService.globalContext.workspaceFlavour.$
|
|
||||||
);
|
|
||||||
|
|
||||||
const signOut = useCallback(async () => {
|
const signOut = useCallback(async () => {
|
||||||
onConfirm?.()?.catch(console.error);
|
onConfirm?.()?.catch(console.error);
|
||||||
try {
|
try {
|
||||||
await authService.signOut();
|
await authService.signOut();
|
||||||
|
jumpToIndex();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
// TODO(@eyhn): i18n
|
const error = UserFriendlyError.fromAny(err);
|
||||||
notify.error({
|
notify.error(error);
|
||||||
title: 'Failed to sign out',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}, [authService, jumpToIndex, onConfirm]);
|
||||||
// if current workspace is sign out, switch to other workspace
|
|
||||||
if (currentWorkspaceFlavour === serverService.server.id) {
|
|
||||||
const localWorkspace = workspaces.find(
|
|
||||||
w => w.flavour !== serverService.server.id
|
|
||||||
);
|
|
||||||
if (localWorkspace) {
|
|
||||||
openPage(localWorkspace.id, 'all');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
authService,
|
|
||||||
currentWorkspaceFlavour,
|
|
||||||
onConfirm,
|
|
||||||
openPage,
|
|
||||||
serverService.server.id,
|
|
||||||
workspaces,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const getDefaultText = useCallback(
|
const getDefaultText = useCallback(
|
||||||
(key: SignOutConfirmModalI18NKeys) => {
|
(key: SignOutConfirmModalI18NKeys) => {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const successDeleteAccountContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { ConfirmModal } from '@affine/component';
|
||||||
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
|
import type {
|
||||||
|
DialogComponentProps,
|
||||||
|
GLOBAL_DIALOG_SCHEMA,
|
||||||
|
} from '@affine/core/modules/dialogs';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './index.css';
|
||||||
|
export const DeletedAccountDialog = ({
|
||||||
|
close,
|
||||||
|
}: DialogComponentProps<GLOBAL_DIALOG_SCHEMA['deleted-account']>) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
|
const callback = useCallback(() => {
|
||||||
|
jumpToIndex();
|
||||||
|
}, [jumpToIndex]);
|
||||||
|
|
||||||
|
const handleOpenChange = useCallback(() => {
|
||||||
|
callback();
|
||||||
|
close();
|
||||||
|
}, [callback, close]);
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
open
|
||||||
|
persistent
|
||||||
|
title={t['com.affine.setting.account.delete.success-title']()}
|
||||||
|
description={
|
||||||
|
<span className={styles.successDeleteAccountContainer}>
|
||||||
|
{t['com.affine.setting.account.delete.success-description-1']()}
|
||||||
|
<span>
|
||||||
|
{t['com.affine.setting.account.delete.success-description-2']()}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
confirmText={t['Confirm']()}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
onConfirm={handleOpenChange}
|
||||||
|
confirmButtonOptions={{
|
||||||
|
variant: 'primary',
|
||||||
|
}}
|
||||||
|
cancelButtonOptions={{
|
||||||
|
style: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -10,6 +10,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
|||||||
import { ChangePasswordDialog } from './change-password';
|
import { ChangePasswordDialog } from './change-password';
|
||||||
import { CollectionEditorDialog } from './collection-editor';
|
import { CollectionEditorDialog } from './collection-editor';
|
||||||
import { CreateWorkspaceDialog } from './create-workspace';
|
import { CreateWorkspaceDialog } from './create-workspace';
|
||||||
|
import { DeletedAccountDialog } from './deleted-account';
|
||||||
import { DocInfoDialog } from './doc-info';
|
import { DocInfoDialog } from './doc-info';
|
||||||
import { EnableCloudDialog } from './enable-cloud';
|
import { EnableCloudDialog } from './enable-cloud';
|
||||||
import { ImportDialog } from './import';
|
import { ImportDialog } from './import';
|
||||||
@@ -31,6 +32,7 @@ const GLOBAL_DIALOGS = {
|
|||||||
'change-password': ChangePasswordDialog,
|
'change-password': ChangePasswordDialog,
|
||||||
'verify-email': VerifyEmailDialog,
|
'verify-email': VerifyEmailDialog,
|
||||||
'enable-cloud': EnableCloudDialog,
|
'enable-cloud': EnableCloudDialog,
|
||||||
|
'deleted-account': DeletedAccountDialog,
|
||||||
} satisfies {
|
} satisfies {
|
||||||
[key in keyof GLOBAL_DIALOG_SCHEMA]?: React.FC<
|
[key in keyof GLOBAL_DIALOG_SCHEMA]?: React.FC<
|
||||||
DialogComponentProps<GLOBAL_DIALOG_SCHEMA[key]>
|
DialogComponentProps<GLOBAL_DIALOG_SCHEMA[key]>
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { ConfirmModal, Input, notify } from '@affine/component';
|
||||||
|
import {
|
||||||
|
SettingRow,
|
||||||
|
SettingWrapper,
|
||||||
|
} from '@affine/component/setting-components';
|
||||||
|
import { AuthService } from '@affine/core/modules/cloud';
|
||||||
|
import { WorkspacesService } from '@affine/core/modules/workspace';
|
||||||
|
import { UserFriendlyError } from '@affine/error';
|
||||||
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
|
import { track } from '@affine/track';
|
||||||
|
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
import * as styles from './style.css';
|
||||||
|
|
||||||
|
export const DeleteAccount = () => {
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
const workspacesService = useService(WorkspacesService);
|
||||||
|
const workspaceProfiles = workspacesService.getAllWorkspaceProfile();
|
||||||
|
const isTeamWorkspaceOwner = workspaceProfiles.some(
|
||||||
|
profile => profile.profile$.value?.isTeam && profile.profile$.value.isOwner
|
||||||
|
);
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const openModal = useCallback(() => {
|
||||||
|
setShowModal(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingWrapper>
|
||||||
|
<SettingRow
|
||||||
|
name={
|
||||||
|
<span style={{ color: cssVarV2('status/error') }}>
|
||||||
|
{t['com.affine.setting.account.delete']()}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
desc={t['com.affine.setting.account.delete.message']()}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={openModal}
|
||||||
|
data-testid="delete-account-button"
|
||||||
|
>
|
||||||
|
<ArrowRightSmallIcon />
|
||||||
|
</SettingRow>
|
||||||
|
{isTeamWorkspaceOwner ? (
|
||||||
|
<TeamOwnerWarningModal open={showModal} onOpenChange={setShowModal} />
|
||||||
|
) : (
|
||||||
|
<DeleteAccountModal open={showModal} onOpenChange={setShowModal} />
|
||||||
|
)}
|
||||||
|
</SettingWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TeamOwnerWarningModal = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const onConfirm = useCallback(() => {
|
||||||
|
onOpenChange(false);
|
||||||
|
}, [onOpenChange]);
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
open={open}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
title={t['com.affine.setting.account.delete.team-warning-title']()}
|
||||||
|
description={t[
|
||||||
|
'com.affine.setting.account.delete.team-warning-description'
|
||||||
|
]()}
|
||||||
|
confirmText={t['Confirm']()}
|
||||||
|
confirmButtonOptions={{
|
||||||
|
variant: 'primary',
|
||||||
|
}}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
cancelButtonOptions={{
|
||||||
|
style: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeleteAccountModal = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const authService = useService(AuthService);
|
||||||
|
const session = authService.session;
|
||||||
|
const account = useLiveData(session.account$);
|
||||||
|
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleDeleteAccount = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
await authService.deleteAccount();
|
||||||
|
track.$.$.auth.deleteAccount();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const error = UserFriendlyError.fromAny(err);
|
||||||
|
notify.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [authService]);
|
||||||
|
|
||||||
|
const onDeleteAccountConfirm = useCallback(async () => {
|
||||||
|
await handleDeleteAccount();
|
||||||
|
}, [handleDeleteAccount]);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
open={open}
|
||||||
|
cancelText={t['com.affine.confirmModal.button.cancel']()}
|
||||||
|
onConfirm={onDeleteAccountConfirm}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
title={t['com.affine.setting.account.delete.confirm-title']()}
|
||||||
|
description={t[
|
||||||
|
'com.affine.setting.account.delete.confirm-description-1'
|
||||||
|
]()}
|
||||||
|
confirmText={t['com.affine.setting.account.delete.confirm-button']()}
|
||||||
|
confirmButtonOptions={{
|
||||||
|
variant: 'error',
|
||||||
|
disabled: email !== account.email,
|
||||||
|
loading: isLoading,
|
||||||
|
}}
|
||||||
|
childrenContentClassName={styles.confirmContent}
|
||||||
|
>
|
||||||
|
<Trans
|
||||||
|
i18nKey="com.affine.setting.account.delete.confirm-description-2"
|
||||||
|
components={{
|
||||||
|
1: <strong />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder={t['com.affine.setting.account.delete.input-placeholder']()}
|
||||||
|
value={email}
|
||||||
|
onChange={setEmail}
|
||||||
|
className={styles.inputWrapper}
|
||||||
|
/>
|
||||||
|
</ConfirmModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ import { FlexWrapper, Input, notify } from '@affine/component';
|
|||||||
import {
|
import {
|
||||||
SettingHeader,
|
SettingHeader,
|
||||||
SettingRow,
|
SettingRow,
|
||||||
|
SettingWrapper,
|
||||||
} from '@affine/component/setting-components';
|
} from '@affine/component/setting-components';
|
||||||
import { Avatar } from '@affine/component/ui/avatar';
|
import { Avatar } from '@affine/component/ui/avatar';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
@@ -20,6 +21,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { AuthService, ServerService } from '../../../../modules/cloud';
|
import { AuthService, ServerService } from '../../../../modules/cloud';
|
||||||
import type { SettingState } from '../types';
|
import type { SettingState } from '../types';
|
||||||
import { AIUsagePanel } from './ai-usage-panel';
|
import { AIUsagePanel } from './ai-usage-panel';
|
||||||
|
import { DeleteAccount } from './delete-account';
|
||||||
import { StorageProgress } from './storage-progress';
|
import { StorageProgress } from './storage-progress';
|
||||||
import * as styles from './style.css';
|
import * as styles from './style.css';
|
||||||
|
|
||||||
@@ -214,51 +216,42 @@ export const AccountSetting = ({
|
|||||||
data-testid="account-title"
|
data-testid="account-title"
|
||||||
/>
|
/>
|
||||||
<AvatarAndName />
|
<AvatarAndName />
|
||||||
<SettingRow name={t['com.affine.settings.email']()} desc={account.email}>
|
<SettingWrapper>
|
||||||
<Button onClick={onChangeEmail}>
|
<SettingRow
|
||||||
{account.info?.emailVerified
|
name={t['com.affine.settings.email']()}
|
||||||
? t['com.affine.settings.email.action.change']()
|
desc={account.email}
|
||||||
: t['com.affine.settings.email.action.verify']()}
|
>
|
||||||
</Button>
|
<Button onClick={onChangeEmail}>
|
||||||
</SettingRow>
|
{account.info?.emailVerified
|
||||||
<SettingRow
|
? t['com.affine.settings.email.action.change']()
|
||||||
name={t['com.affine.settings.password']()}
|
: t['com.affine.settings.email.action.verify']()}
|
||||||
desc={t['com.affine.settings.password.message']()}
|
</Button>
|
||||||
>
|
</SettingRow>
|
||||||
<Button onClick={onPasswordButtonClick}>
|
<SettingRow
|
||||||
{account.info?.hasPassword
|
name={t['com.affine.settings.password']()}
|
||||||
? t['com.affine.settings.password.action.change']()
|
desc={t['com.affine.settings.password.message']()}
|
||||||
: t['com.affine.settings.password.action.set']()}
|
>
|
||||||
</Button>
|
<Button onClick={onPasswordButtonClick}>
|
||||||
</SettingRow>
|
{account.info?.hasPassword
|
||||||
<StoragePanel onChangeSettingState={onChangeSettingState} />
|
? t['com.affine.settings.password.action.change']()
|
||||||
{serverFeatures?.copilot && (
|
: t['com.affine.settings.password.action.set']()}
|
||||||
<AIUsagePanel onChangeSettingState={onChangeSettingState} />
|
</Button>
|
||||||
)}
|
</SettingRow>
|
||||||
<SettingRow
|
<StoragePanel onChangeSettingState={onChangeSettingState} />
|
||||||
name={t[`Sign out`]()}
|
{serverFeatures?.copilot && (
|
||||||
desc={t['com.affine.setting.sign.out.message']()}
|
<AIUsagePanel onChangeSettingState={onChangeSettingState} />
|
||||||
style={{ cursor: 'pointer' }}
|
)}
|
||||||
data-testid="sign-out-button"
|
<SettingRow
|
||||||
onClick={openSignOutModal}
|
name={t[`Sign out`]()}
|
||||||
>
|
desc={t['com.affine.setting.sign.out.message']()}
|
||||||
<ArrowRightSmallIcon />
|
style={{ cursor: 'pointer' }}
|
||||||
</SettingRow>
|
data-testid="sign-out-button"
|
||||||
{/*<SettingRow*/}
|
onClick={openSignOutModal}
|
||||||
{/* name={*/}
|
>
|
||||||
{/* <span style={{ color: 'var(--affine-warning-color)' }}>*/}
|
<ArrowRightSmallIcon />
|
||||||
{/* {t['com.affine.setting.account.delete']()}*/}
|
</SettingRow>
|
||||||
{/* </span>*/}
|
</SettingWrapper>
|
||||||
{/* }*/}
|
<DeleteAccount />
|
||||||
{/* desc={t['com.affine.setting.account.delete.message']()}*/}
|
|
||||||
{/* style={{ cursor: 'pointer' }}*/}
|
|
||||||
{/* onClick={useCallback(() => {*/}
|
|
||||||
{/* toast('Function coming soon');*/}
|
|
||||||
{/* }, [])}*/}
|
|
||||||
{/* testId="delete-account-button"*/}
|
|
||||||
{/*>*/}
|
|
||||||
{/* <ArrowRightSmallIcon />*/}
|
|
||||||
{/*</SettingRow>*/}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,3 +39,17 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
|
|||||||
color: cssVar('white'),
|
color: cssVar('white'),
|
||||||
fontSize: cssVar('fontH4'),
|
fontSize: cssVar('fontH4'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const successDeleteAccountContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
});
|
||||||
|
export const confirmContent = style({
|
||||||
|
paddingLeft: '0',
|
||||||
|
paddingRight: '0',
|
||||||
|
});
|
||||||
|
export const inputWrapper = style({
|
||||||
|
marginTop: '12px',
|
||||||
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
|
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -101,7 +102,7 @@ export const DeleteLeaveWorkspace = ({
|
|||||||
<>
|
<>
|
||||||
<SettingRow
|
<SettingRow
|
||||||
name={
|
name={
|
||||||
<span style={{ color: 'var(--affine-error-color)' }}>
|
<span style={{ color: cssVarV2('status/error') }}>
|
||||||
{isOwner
|
{isOwner
|
||||||
? t['com.affine.workspaceDelete.title']()
|
? t['com.affine.workspaceDelete.title']()
|
||||||
: t['com.affine.deleteLeaveWorkspace.leave']()}
|
: t['com.affine.deleteLeaveWorkspace.leave']()}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ import { DocCreatedByUpdatedBySyncService } from './services/doc-created-by-upda
|
|||||||
import { WorkspacePermissionService } from '../permissions';
|
import { WorkspacePermissionService } from '../permissions';
|
||||||
import { DocScope, DocService, DocsService } from '../doc';
|
import { DocScope, DocService, DocsService } from '../doc';
|
||||||
import { DocCreatedByUpdatedBySyncStore } from './stores/doc-created-by-updated-by-sync';
|
import { DocCreatedByUpdatedBySyncStore } from './stores/doc-created-by-updated-by-sync';
|
||||||
|
import { GlobalDialogService } from '../dialogs';
|
||||||
|
|
||||||
export function configureCloudModule(framework: Framework) {
|
export function configureCloudModule(framework: Framework) {
|
||||||
configureDefaultAuthProvider(framework);
|
configureDefaultAuthProvider(framework);
|
||||||
@@ -123,7 +124,12 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
f.getOptional(ValidatorProvider)
|
f.getOptional(ValidatorProvider)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.service(AuthService, [FetchService, AuthStore, UrlService])
|
.service(AuthService, [
|
||||||
|
FetchService,
|
||||||
|
AuthStore,
|
||||||
|
UrlService,
|
||||||
|
GlobalDialogService,
|
||||||
|
])
|
||||||
.store(AuthStore, [
|
.store(AuthStore, [
|
||||||
FetchService,
|
FetchService,
|
||||||
GraphQLService,
|
GraphQLService,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { OnEvent, Service } from '@toeverything/infra';
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { distinctUntilChanged, map, skip } from 'rxjs';
|
import { distinctUntilChanged, map, skip } from 'rxjs';
|
||||||
|
|
||||||
|
import type { GlobalDialogService } from '../../dialogs';
|
||||||
import { ApplicationFocused } from '../../lifecycle';
|
import { ApplicationFocused } from '../../lifecycle';
|
||||||
import type { UrlService } from '../../url';
|
import type { UrlService } from '../../url';
|
||||||
import { AuthSession } from '../entities/session';
|
import { AuthSession } from '../entities/session';
|
||||||
@@ -23,7 +24,8 @@ export class AuthService extends Service {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly fetchService: FetchService,
|
private readonly fetchService: FetchService,
|
||||||
private readonly store: AuthStore,
|
private readonly store: AuthStore,
|
||||||
private readonly urlService: UrlService
|
private readonly urlService: UrlService,
|
||||||
|
private readonly dialogService: GlobalDialogService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -189,6 +191,14 @@ export class AuthService extends Service {
|
|||||||
this.session.revalidate();
|
this.session.revalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteAccount() {
|
||||||
|
const res = await this.store.deleteAccount();
|
||||||
|
this.store.setCachedAuthSession(null);
|
||||||
|
this.session.revalidate();
|
||||||
|
this.dialogService.open('deleted-account', {});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
checkUserByEmail(email: string) {
|
checkUserByEmail(email: string) {
|
||||||
return this.store.checkUserByEmail(email);
|
return this.store.checkUserByEmail(email);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
deleteAccountMutation,
|
||||||
removeAvatarMutation,
|
removeAvatarMutation,
|
||||||
updateUserProfileMutation,
|
updateUserProfileMutation,
|
||||||
uploadAvatarMutation,
|
uploadAvatarMutation,
|
||||||
@@ -150,4 +151,11 @@ export class AuthStore extends Store {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteAccount() {
|
||||||
|
const res = await this.gqlService.gql({
|
||||||
|
query: deleteAccountMutation,
|
||||||
|
});
|
||||||
|
return res.deleteAccount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export type GLOBAL_DIALOG_SCHEMA = {
|
|||||||
openPageId?: string;
|
openPageId?: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
}) => boolean;
|
}) => boolean;
|
||||||
|
'deleted-account': () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WORKSPACE_DIALOG_SCHEMA = {
|
export type WORKSPACE_DIALOG_SCHEMA = {
|
||||||
|
|||||||
@@ -4830,13 +4830,49 @@ export function useAFFiNEI18N(): {
|
|||||||
*/
|
*/
|
||||||
["com.affine.setting.account"](): string;
|
["com.affine.setting.account"](): string;
|
||||||
/**
|
/**
|
||||||
* `Delete account`
|
* `Delete your account`
|
||||||
*/
|
*/
|
||||||
["com.affine.setting.account.delete"](): string;
|
["com.affine.setting.account.delete"](): string;
|
||||||
/**
|
/**
|
||||||
* `Permanently delete this account and the Workspace data backup in AFFiNE Cloud. This action can not be undone.`
|
* `Once deleted, your account will no longer be accessible, and all data in your personal cloud space will be permanently deleted.`
|
||||||
*/
|
*/
|
||||||
["com.affine.setting.account.delete.message"](): string;
|
["com.affine.setting.account.delete.message"](): string;
|
||||||
|
/**
|
||||||
|
* `Cannot delete account`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.team-warning-title"](): string;
|
||||||
|
/**
|
||||||
|
* `You’re the owner of a team workspace. To delete your account, please delete the workspace or transfer ownership first.`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.team-warning-description"](): string;
|
||||||
|
/**
|
||||||
|
* `Delete your account?`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.confirm-title"](): string;
|
||||||
|
/**
|
||||||
|
* `Are you sure you want to delete your account?`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.confirm-description-1"](): string;
|
||||||
|
/**
|
||||||
|
* `Please type your email to confirm`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.input-placeholder"](): string;
|
||||||
|
/**
|
||||||
|
* `Delete`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.confirm-button"](): string;
|
||||||
|
/**
|
||||||
|
* `Account deleted`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.success-title"](): string;
|
||||||
|
/**
|
||||||
|
* `Your account and cloud data have been deleted.`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.success-description-1"](): string;
|
||||||
|
/**
|
||||||
|
* `Local data can be deleted by uninstalling app and clearing browser data.`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.success-description-2"](): string;
|
||||||
/**
|
/**
|
||||||
* `Your personal information`
|
* `Your personal information`
|
||||||
*/
|
*/
|
||||||
@@ -9152,6 +9188,12 @@ export const TypedTrans: {
|
|||||||
}, {
|
}, {
|
||||||
["1"]: JSX.Element;
|
["1"]: JSX.Element;
|
||||||
}>>;
|
}>>;
|
||||||
|
/**
|
||||||
|
* `Your account will be inaccessible, and your personal cloud space will be permanently deleted. You can remove local data by uninstalling the app or clearing your browser storage. <1>This action is irreversible.</1>`
|
||||||
|
*/
|
||||||
|
["com.affine.setting.account.delete.confirm-description-2"]: ComponentType<TypedTransProps<Readonly<{}>, {
|
||||||
|
["1"]: JSX.Element;
|
||||||
|
}>>;
|
||||||
/**
|
/**
|
||||||
* `Don't have the app? <1>Click to download</1>.`
|
* `Don't have the app? <1>Click to download</1>.`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1199,8 +1199,18 @@
|
|||||||
"com.affine.setting.notifications.email.invites.title": "Invites",
|
"com.affine.setting.notifications.email.invites.title": "Invites",
|
||||||
"com.affine.setting.notifications.email.invites.subtitle": "Invitation related messages will be sent through emails.",
|
"com.affine.setting.notifications.email.invites.subtitle": "Invitation related messages will be sent through emails.",
|
||||||
"com.affine.setting.account": "Account settings",
|
"com.affine.setting.account": "Account settings",
|
||||||
"com.affine.setting.account.delete": "Delete account",
|
"com.affine.setting.account.delete": "Delete your account",
|
||||||
"com.affine.setting.account.delete.message": "Permanently delete this account and the Workspace data backup in AFFiNE Cloud. This action can not be undone.",
|
"com.affine.setting.account.delete.message": "Once deleted, your account will no longer be accessible, and all data in your personal cloud space will be permanently deleted.",
|
||||||
|
"com.affine.setting.account.delete.team-warning-title": "Cannot delete account",
|
||||||
|
"com.affine.setting.account.delete.team-warning-description": "You’re the owner of a team workspace. To delete your account, please delete the workspace or transfer ownership first.",
|
||||||
|
"com.affine.setting.account.delete.confirm-title": "Delete your account?",
|
||||||
|
"com.affine.setting.account.delete.confirm-description-1": "Are you sure you want to delete your account?",
|
||||||
|
"com.affine.setting.account.delete.confirm-description-2": "Your account will be inaccessible, and your personal cloud space will be permanently deleted. You can remove local data by uninstalling the app or clearing your browser storage. <1>This action is irreversible.</1>",
|
||||||
|
"com.affine.setting.account.delete.input-placeholder": "Please type your email to confirm",
|
||||||
|
"com.affine.setting.account.delete.confirm-button": "Delete",
|
||||||
|
"com.affine.setting.account.delete.success-title": "Account deleted",
|
||||||
|
"com.affine.setting.account.delete.success-description-1": "Your account and cloud data have been deleted.",
|
||||||
|
"com.affine.setting.account.delete.success-description-2": "Local data can be deleted by uninstalling app and clearing browser data.",
|
||||||
"com.affine.setting.account.message": "Your personal information",
|
"com.affine.setting.account.message": "Your personal information",
|
||||||
"com.affine.setting.sign.message": "Sync with AFFiNE Cloud",
|
"com.affine.setting.sign.message": "Sync with AFFiNE Cloud",
|
||||||
"com.affine.setting.sign.out.message": "Securely sign out of your account.",
|
"com.affine.setting.sign.out.message": "Securely sign out of your account.",
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ type AuthEvents =
|
|||||||
| 'signIn'
|
| 'signIn'
|
||||||
| 'signInFail'
|
| 'signInFail'
|
||||||
| 'signedIn'
|
| 'signedIn'
|
||||||
| 'signOut';
|
| 'signOut'
|
||||||
|
| 'deleteAccount';
|
||||||
type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName';
|
type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName';
|
||||||
type PaymentEvents =
|
type PaymentEvents =
|
||||||
| 'viewPlans'
|
| 'viewPlans'
|
||||||
@@ -235,7 +236,14 @@ interface PageEvents extends PageDivision {
|
|||||||
$: {
|
$: {
|
||||||
$: {
|
$: {
|
||||||
$: ['createWorkspace', 'checkout'];
|
$: ['createWorkspace', 'checkout'];
|
||||||
auth: ['requestSignIn', 'signIn', 'signedIn', 'signInFail', 'signOut'];
|
auth: [
|
||||||
|
'requestSignIn',
|
||||||
|
'signIn',
|
||||||
|
'signedIn',
|
||||||
|
'signInFail',
|
||||||
|
'signOut',
|
||||||
|
'deleteAccount',
|
||||||
|
];
|
||||||
};
|
};
|
||||||
sharePanel: {
|
sharePanel: {
|
||||||
$: [
|
$: [
|
||||||
|
|||||||
Reference in New Issue
Block a user