feat(mobile): add delete account function (#12688)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a "Delete my account" option in mobile settings with role-based warnings and confirmation modals.
- **Enhancements**
  - Added flexible row and reverse row layout options for modal footers and action buttons on mobile.
- **Localization**
  - Added English translation for the "Delete my account" setting.
- **Style**
  - Updated styles for modal footers and action buttons on mobile.
  - Added styling for account deletion dialog descriptions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
JimmFly
2025-06-03 10:17:45 +00:00
parent ee931d546e
commit 23ff398994
7 changed files with 225 additions and 3 deletions

View File

@@ -0,0 +1,7 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';
export const description = style({
fontSize: cssVar('fontBase'),
lineHeight: 1.6,
});

View File

@@ -0,0 +1,180 @@
import { ConfirmModal, notify, useConfirmModal } from '@affine/component';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
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 { useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { RowLayout } from '../row.layout';
import * as styles from './delete-account.css';
export const DeleteAccount = () => {
const t = useI18n();
const workspacesService = useService(WorkspacesService);
const authService = useService(AuthService);
const session = authService.session;
const account = useLiveData(session.account$);
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 (
<>
{account ? (
<RowLayout
label={t['com.affine.mobile.setting.others.delete-account']()}
onClick={openModal}
>
<ArrowRightSmallIcon fontSize={22} />
</RowLayout>
) : null}
{isTeamWorkspaceOwner ? (
<TeamOwnerWarningModal open={showModal} onOpenChange={setShowModal} />
) : (
<DeleteAccountModal open={showModal} onOpenChange={setShowModal} />
)}
</>
);
};
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 [isLoading, setIsLoading] = useState(false);
const { openConfirmModal } = useConfirmModal();
const navigate = useNavigate();
const onConfirm = useCallback(() => {
navigate('/');
}, [navigate]);
const handleDeleteAccount = useCallback(async () => {
try {
setIsLoading(true);
await authService.deleteAccount();
track.$.$.auth.deleteAccount();
openConfirmModal({
title: t['com.affine.setting.account.delete.success-title'](),
description: (
<>
<span>
{t['com.affine.setting.account.delete.success-description-1']()}
</span>
<br />
<span>
{t['com.affine.setting.account.delete.success-description-2']()}
</span>
</>
),
cancelButtonOptions: {
style: {
display: 'none',
},
},
confirmText: t['Confirm'](),
onConfirm,
confirmButtonOptions: {
variant: 'primary',
},
});
} catch (err) {
console.error(err);
const error = UserFriendlyError.fromAny(err);
notify.error(error);
} finally {
setIsLoading(false);
}
}, [authService, onConfirm, openConfirmModal, t]);
const onDeleteAccountConfirm = useAsyncCallback(async () => {
await handleDeleteAccount();
}, [handleDeleteAccount]);
const onCancel = useCallback(() => {
onOpenChange(false);
}, [onOpenChange]);
if (!account) {
return null;
}
return (
<ConfirmModal
open={open}
onOpenChange={onOpenChange}
title={t['com.affine.setting.account.delete.confirm-title']()}
description={
<Trans
i18nKey="com.affine.setting.account.delete.confirm-description-2"
components={{
1: <strong />,
}}
/>
}
descriptionClassName={styles.description}
confirmText={t['com.affine.setting.account.delete.confirm-button']()}
confirmButtonOptions={{
variant: 'error',
disabled: isLoading,
loading: isLoading,
onClick: onDeleteAccountConfirm,
}}
onCancel={onCancel}
cancelText={t['Cancel']()}
cancelButtonOptions={{
variant: 'primary',
}}
rowFooter
/>
);
};

View File

@@ -2,6 +2,7 @@ import { useI18n } from '@affine/i18n';
import { SettingGroup } from '../group';
import { RowLayout } from '../row.layout';
import { DeleteAccount } from './delete-account';
export const OthersGroup = () => {
const t = useI18n();
@@ -27,6 +28,7 @@ export const OthersGroup = () => {
label={t['com.affine.mobile.setting.others.terms']()}
href="https://affine.pro/terms"
/>
<DeleteAccount />
</SettingGroup>
);
};