Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 95 additions & 49 deletions src/components/features/EditAccountModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useGlobal } from '@/context/GlobalContext';
import { useUpdateAccount, useDeleteAccount, useCreateAccount, useAllAccounts, AccountType, Account } from '@/lib/hooks';
import { accountService } from '@/lib/services';
import {
Dialog,
DialogContent,
Expand Down Expand Up @@ -71,6 +72,8 @@ const EditAccountForm = ({ account, onClose }: EditAccountFormProps) => {

const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false);
const [migrationTargets, setMigrationTargets] = useState<Record<string, string>>({});
const [transactionCount, setTransactionCount] = useState<number | null>(null);
const [isLoadingCount, setIsLoadingCount] = useState(false);

const handleAddChild = () => {
setChildren([...children, {
Expand Down Expand Up @@ -158,7 +161,25 @@ const EditAccountForm = ({ account, onClose }: EditAccountFormProps) => {
);
};

const prepareDelete = () => {
const prepareDelete = async () => {
setIsLoadingCount(true);
setTransactionCount(null);
try {
// Get transaction count for this account and all children
const accountIds = [account.id, ...children.filter(c => !c.isNew).map(c => c.id)];
let totalCount = 0;
for (const id of accountIds) {
const { count } = await accountService.getTransactionCount(id);
totalCount += count;
}
setTransactionCount(totalCount);
} catch (error) {
console.error('Failed to get transaction count:', error);
// Default to require migration if we can't determine
setTransactionCount(1);
} finally {
setIsLoadingCount(false);
}
setMigrationTargets({});
setIsDeleteAlertOpen(true);
};
Expand Down Expand Up @@ -317,60 +338,85 @@ const EditAccountForm = ({ account, onClose }: EditAccountFormProps) => {
<DialogTitle>{t('accounts:delete_account_title')}</DialogTitle>
<DialogDescription asChild>
<div className="space-y-4 text-muted-foreground text-sm">
<p>{t('accounts:delete_account_confirm')}</p>
<div className="space-y-4">
{requiredCurrencies.map(curr => {
const targets = getAvailableTargets(curr);
const isTargetMissing = targets.length === 0;

return (
<div key={curr} className="space-y-2">
<Label>{t('accounts:migrate_balance_for', { currency: curr, defaultValue: `Migrate ${curr} balance to:` })}</Label>

{isTargetMissing ? (
<div className="text-sm text-red-500 flex items-center gap-2 border border-red-200 bg-red-50 p-2 rounded-md">
<AlertTriangle size={14} className="shrink-0" />
<span>
{t('accounts:no_available_funding_accounts', {
type: account?.type,
defaultValue: `No available ${account?.type} funding accounts, please create one before proceeding with deletion`
})}
</span>
{isLoadingCount ? (
<p>{t('common:loading') || '加载中...'}</p>
) : transactionCount === 0 ? (
// No transactions - simple confirmation
<p>{t('accounts:delete_no_transactions_confirm')}</p>
) : (
// Has transactions - show migration UI
<>
<p>{t('accounts:delete_account_confirm')}</p>
<div className="space-y-4">
{requiredCurrencies.map(curr => {
const targets = getAvailableTargets(curr);
const isTargetMissing = targets.length === 0;

return (
<div key={curr} className="space-y-2">
<Label>{t('accounts:migrate_balance_for', { currency: curr, defaultValue: `Migrate ${curr} balance to:` })}</Label>

{isTargetMissing ? (
<div className="text-sm text-red-500 flex items-center gap-2 border border-red-200 bg-red-50 p-2 rounded-md">
<AlertTriangle size={14} className="shrink-0" />
<span>
{t('accounts:no_available_funding_accounts', {
type: account?.type,
defaultValue: `No available ${account?.type} funding accounts, please create one before proceeding with deletion`
})}
</span>
</div>
) : (
<Select
value={migrationTargets[curr]}
onValueChange={(val) => setMigrationTargets(prev => ({ ...prev, [curr]: val }))}
>
<SelectTrigger>
<SelectValue placeholder={t('accounts:select_account')} />
</SelectTrigger>
<SelectContent>
{targets.map(t => (
<SelectItem key={t.id} value={t.id}>{t.name} ({t.currency})</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
) : (
<Select
value={migrationTargets[curr]}
onValueChange={(val) => setMigrationTargets(prev => ({ ...prev, [curr]: val }))}
>
<SelectTrigger>
<SelectValue placeholder={t('accounts:select_account')} />
</SelectTrigger>
<SelectContent>
{targets.map(t => (
<SelectItem key={t.id} value={t.id}>{t.name} ({t.currency})</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
);
})}
</div>
);
})}
</div>
</>
)}
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setIsDeleteAlertOpen(false)}>{t('common:cancel')}</Button>
<Button
variant="destructive"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
if (!hasBlockedCurrencies && isMigrationComplete) handleDeleteConfirm();
}}
disabled={isPending || hasBlockedCurrencies || !isMigrationComplete}
>
{isPending ? t('common:deleting') || '删除中...' : t('common:delete')}
</Button>
{transactionCount === 0 ? (
// No transactions - direct delete button
<Button
variant="destructive"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
handleDeleteConfirm();
}}
disabled={isPending || isLoadingCount}
>
{isPending ? t('common:deleting') || '删除中...' : t('common:delete')}
</Button>
) : (
// Has transactions - require migration selection
<Button
variant="destructive"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
if (!hasBlockedCurrencies && isMigrationComplete) handleDeleteConfirm();
}}
disabled={isPending || hasBlockedCurrencies || !isMigrationComplete || isLoadingCount}
>
{isPending ? t('common:deleting') || '删除中...' : t('common:delete')}
</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
Expand Down
1 change: 1 addition & 0 deletions src/lib/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {
useDeleteAccount,
useAllAccounts,
useAllAccountsSuspense,
useAccountTransactionCount,
accountKeys,
} from './useAccounts';

Expand Down
9 changes: 9 additions & 0 deletions src/lib/hooks/useAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ export function useDeleteAccount() {
});
}

// Get account transaction count
export function useAccountTransactionCount(id: string, enabled = true) {
return useQuery({
queryKey: [...accountKeys.detail(id), 'transactionCount'],
queryFn: () => accountService.getTransactionCount(id),
enabled: !!id && enabled,
});
}

// ========== Convenience Hooks ==========

// Get all accounts (flat list)
Expand Down
4 changes: 4 additions & 0 deletions src/lib/services/accountService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,8 @@ export const accountService = {
method: 'DELETE',
body: migrationTargets ? JSON.stringify({ migrationTargets }) : undefined,
}),

getTransactionCount: (id: string): Promise<{ count: number }> =>
apiRequest(`/api/accounts/${id}/transaction-count`),
};

5 changes: 3 additions & 2 deletions src/locales/en/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
"delete_child_hint": "To delete a sub-account, please go to its specific page or delete the parent account.",
"delete_account_title": "Delete Account",
"delete_account_confirm": "Are you sure you want to delete this account? This action cannot be undone.",
"migrate_balance_for": "Migrate {{currency}} balance to:",
"migrate_balance_for": "Migrate {{currency}} balance and transaction records to:",
"select_account": "Select Account",
"delete_success": "Account deleted successfully",
"migration_error_no_target": "Cannot delete: No other {{currencies}} account found for balance migration.",
"quick_add_currency": "Quick add new currency",
"no_available_funding_accounts": "No available {{type}} funding accounts, please create one before proceeding with deletion",
"free_asset_limit_reached": "Free users can create up to 5 asset accounts. Upgrade to Pro for unlimited accounts.",
"free_group_disabled": "Parent-child accounts are available for Pro users only. Upgrade to unlock multi-currency account management."
"free_group_disabled": "Parent-child accounts are available for Pro users only. Upgrade to unlock multi-currency account management.",
"delete_no_transactions_confirm": "This account has no transactions. Are you sure you want to delete it? The balance will be cleared and this action cannot be undone."
}
5 changes: 3 additions & 2 deletions src/locales/ja/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
"delete_child_hint": "子アカウントを削除するには、その子アカウントの詳細ページに移動するか、親アカウントを削除してください。",
"delete_account_title": "アカウントを削除",
"delete_account_confirm": "このアカウントを削除してもよろしいですか?この操作は取り消せません。",
"migrate_balance_for": "{{currency}} 残高の移行先:",
"migrate_balance_for": "{{currency}} 残高と取引記録の移行先:",
"select_account": "アカウントを選択",
"delete_success": "アカウントが削除されました",
"migration_error_no_target": "削除できません:残高移行用の他の {{currencies}} 口座が見つかりません。",
"quick_add_currency": "新しい通貨を素早く追加",
"no_available_funding_accounts": "利用可能な {{type}} 資金口座がありません。削除を進める前に作成してください。",
"free_asset_limit_reached": "無料ユーザーは最大5つの資産口座を作成できます。Proにアップグレードして無制限に。",
"free_group_disabled": "親子口座はProユーザー限定です。アップグレードして多通貨管理を解除。"
"free_group_disabled": "親子口座はProユーザー限定です。アップグレードして多通貨管理を解除。",
"delete_no_transactions_confirm": "このアカウントには取引記録がありません。削除してもよろしいですか?残高はゼロになり、この操作は取り消せません。"
}
5 changes: 3 additions & 2 deletions src/locales/zh-CN/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
"delete_child_hint": "如需删除子账户,请前往该子账户详情页或删除父账户。",
"delete_account_title": "删除账户",
"delete_account_confirm": "确定要删除该账户吗?此操作无法撤销。",
"migrate_balance_for": "迁移 {{currency}} 余额至:",
"migrate_balance_for": "迁移 {{currency}} 余额和交易记录至:",
"select_account": "选择账户",
"delete_success": "账户已删除",
"migration_error_no_target": "无法删除:未找到其他 {{currencies}} 账户用于迁移余额。",
"quick_add_currency": "快速添加新币种",
"no_available_funding_accounts": "没有可用的 {{type}} 资金账户,请在继续删除之前创建一个。",
"free_asset_limit_reached": "免费用户最多只能创建5个资产账户,升级到 Pro 解锁无限账户",
"free_group_disabled": "父子账户功能仅限 Pro 用户,升级后可使用多币种账户管理"
"free_group_disabled": "父子账户功能仅限 Pro 用户,升级后可使用多币种账户管理",
"delete_no_transactions_confirm": "该账户没有交易记录,确定要删除吗?删除后余额将直接清零,此操作无法撤销。"
}
5 changes: 3 additions & 2 deletions src/locales/zh-TW/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
"delete_child_hint": "如需刪除子帳戶,請前往該子帳戶詳情頁或刪除父帳戶。",
"delete_account_title": "刪除帳戶",
"delete_account_confirm": "確定要刪除該帳戶嗎?此操作無法復原。",
"migrate_balance_for": "遷移 {{currency}} 餘額至:",
"migrate_balance_for": "遷移 {{currency}} 餘額和交易記錄至:",
"select_account": "選擇帳戶",
"delete_success": "帳戶已刪除",
"migration_error_no_target": "無法刪除:未找到其他 {{currencies}} 帳戶用於餘額遷移。",
"quick_add_currency": "快速添加新幣種",
"no_available_funding_accounts": "沒有可用的 {{type}} 資金帳戶,請在繼續刪除之前創建一個。",
"free_asset_limit_reached": "免費用戶最多只能創建5個資產帳戶,升級到 Pro 解鎖無限帳戶",
"free_group_disabled": "父子帳戶功能僅限 Pro 用戶,升級後可使用多幣種帳戶管理"
"free_group_disabled": "父子帳戶功能僅限 Pro 用戶,升級後可使用多幣種帳戶管理",
"delete_no_transactions_confirm": "該帳戶沒有交易記錄,確定要刪除嗎?刪除後餘額將直接清零,此操作無法撤銷。"
}