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:
parent
d73cc9b01f
commit
8e113b1aa2
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue