Move account delete into edit modal (#410)

## Summary
- remove account delete action from account detail submenu
- add ghost destructive delete button to edit account modal
- keep existing DELETE confirmation flow before destroy

## Tests
- npm test --
resources/js/components/accounts/edit-account-dialog.test.tsx
- npm run types -- --pretty false (project has pre-existing unrelated
type errors; no edit-account-dialog errors)
This commit is contained in:
Víctor Falcón 2026-05-20 16:46:13 +01:00 committed by GitHub
parent d73cc9b01f
commit 8e113b1aa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 149 additions and 38 deletions

View File

@ -0,0 +1,103 @@
import type { Account } from '@/types/account';
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { EditAccountDialog } from './edit-account-dialog';
vi.mock('@inertiajs/react', () => ({
router: {
patch: vi.fn(),
visit: vi.fn(),
},
}));
vi.mock('@/actions/App/Http/Controllers/Settings/AccountController', () => ({
update: {
url: (id: string) => `/settings/accounts/${id}`,
},
}));
vi.mock('@/actions/App/Http/Controllers/Settings/BankController', () => ({
store: {
url: () => '/settings/banks',
},
}));
vi.mock('./account-form', () => ({
AccountForm: () => <div data-testid="account-form" />,
}));
vi.mock('./delete-account-dialog', () => ({
DeleteAccountDialog: ({
open,
redirectTo,
}: {
open: boolean;
redirectTo?: string;
}) =>
open ? (
<div
data-testid="delete-account-dialog"
data-redirect-to={redirectTo}
>
Delete Account confirmation
</div>
) : null,
}));
function makeAccount(): Account {
return {
id: 'account-1',
name: 'Checking',
name_iv: null,
encrypted: false,
bank: {
id: 'bank-1',
user_id: null,
name: 'Test Bank',
logo: null,
},
type: 'checking',
currency_code: 'USD',
banking_connection_id: null,
external_account_id: null,
linked_at: null,
};
}
describe('EditAccountDialog', () => {
it('shows account deletion inside edit modal with confirmation dialog', () => {
render(
<EditAccountDialog
account={makeAccount()}
open={true}
onOpenChange={vi.fn()}
deleteRedirectTo="/accounts"
/>,
);
fireEvent.click(screen.getByRole('button', { name: 'Delete account' }));
expect(
screen.getByTestId('delete-account-dialog').textContent,
).toContain('Delete Account confirmation');
expect(
screen
.getByTestId('delete-account-dialog')
.getAttribute('data-redirect-to'),
).toBe('/accounts');
});
it('hides account deletion when no delete redirect is provided', () => {
render(
<EditAccountDialog
account={makeAccount()}
open={true}
onOpenChange={vi.fn()}
/>,
);
expect(
screen.queryByRole('button', { name: 'Delete account' }),
).toBeNull();
});
});

View File

@ -20,6 +20,7 @@ import {
LoanFormData,
RealEstateFormData,
} from './account-form';
import { DeleteAccountDialog } from './delete-account-dialog';
interface AccountWithDetails extends Account {
loan_detail?: LoanDetail | null;
@ -32,6 +33,7 @@ interface EditAccountDialogProps {
onOpenChange: (open: boolean) => void;
onSuccess?: () => void;
redirectTo?: string;
deleteRedirectTo?: string;
}
export function EditAccountDialog({
@ -40,8 +42,10 @@ export function EditAccountDialog({
onOpenChange,
onSuccess,
redirectTo,
deleteRedirectTo,
}: EditAccountDialogProps) {
const [decryptedName, setDecryptedName] = useState('');
const [deleteOpen, setDeleteOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const formDataRef = useRef<AccountFormData>({
@ -91,6 +95,7 @@ export function EditAccountDialog({
loanTermMonths: detail.loan_term_months?.toString() ?? '',
startDate: detail.start_date?.slice(0, 10) ?? '',
originalAmount: detail.original_amount ?? 0,
linkedRealEstateAccountId: null,
};
}, [account.loan_detail]);
@ -328,23 +333,48 @@ export function EditAccountDialog({
</div>
)}
<div className="flex justify-end gap-2 pt-4">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
>
{__('Cancel')}
</Button>
<Button
type="submit"
disabled={isSubmitting || !initialValues}
>
{isSubmitting ? 'Updating...' : 'Update'}
</Button>
<div className="flex items-center justify-between gap-2 pt-4">
{deleteRedirectTo ? (
<Button
type="button"
variant="ghost"
className="text-destructive hover:bg-destructive/10 hover:text-destructive dark:hover:bg-destructive/20"
onClick={() => setDeleteOpen(true)}
disabled={isSubmitting}
>
{__('Delete account')}
</Button>
) : (
<span />
)}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isSubmitting}
>
{__('Cancel')}
</Button>
<Button
type="submit"
disabled={isSubmitting || !initialValues}
>
{isSubmitting ? 'Updating...' : 'Update'}
</Button>
</div>
</div>
</form>
{deleteRedirectTo && (
<DeleteAccountDialog
account={account}
open={deleteOpen}
onOpenChange={setDeleteOpen}
redirectTo={deleteRedirectTo}
/>
)}
</DialogContent>
</Dialog>
);

View File

@ -7,7 +7,6 @@ import {
type ChartComputedData,
} from '@/components/accounts/account-balance-chart';
import { BalancesModal } from '@/components/accounts/balances-modal';
import { DeleteAccountDialog } from '@/components/accounts/delete-account-dialog';
import { EditAccountDialog } from '@/components/accounts/edit-account-dialog';
import { EditLoanDetailDialog } from '@/components/accounts/edit-loan-detail-dialog';
import { ImportBalancesDrawer } from '@/components/accounts/import-balances-drawer';
@ -92,7 +91,6 @@ export default function AccountShow({
automationRules = [],
}: Props) {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const [updateBalanceOpen, setUpdateBalanceOpen] = useState(false);
const [updateLoanBalanceOpen, setUpdateLoanBalanceOpen] = useState(false);
const [importBalancesOpen, setImportBalancesOpen] = useState(false);
@ -253,13 +251,6 @@ export default function AccountShow({
>
{__('Edit loan details')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setDeleteOpen(true)}
variant="destructive"
>
{__('Delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
@ -304,13 +295,6 @@ export default function AccountShow({
>
{__('Edit account')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setDeleteOpen(true)}
variant="destructive"
>
{__('Delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
@ -396,13 +380,7 @@ export default function AccountShow({
open={editOpen}
onOpenChange={setEditOpen}
redirectTo={show.url(account.id)}
/>
<DeleteAccountDialog
account={account}
open={deleteOpen}
onOpenChange={setDeleteOpen}
redirectTo={index().url}
deleteRedirectTo={isConnected ? undefined : index().url}
/>
<UpdateBalanceDialog