diff --git a/resources/js/components/onboarding/step-import-transactions.tsx b/resources/js/components/onboarding/step-import-transactions.tsx
index 63b8721e..f8b6ce4d 100644
--- a/resources/js/components/onboarding/step-import-transactions.tsx
+++ b/resources/js/components/onboarding/step-import-transactions.tsx
@@ -150,6 +150,7 @@ export function StepImportTransactions({
categories={categories}
banks={banks}
automationRules={automationRules}
+ autoSelectSingleAccount
/>
);
diff --git a/resources/js/components/transactions/import-step-account.test.tsx b/resources/js/components/transactions/import-step-account.test.tsx
new file mode 100644
index 00000000..3aaa2bb2
--- /dev/null
+++ b/resources/js/components/transactions/import-step-account.test.tsx
@@ -0,0 +1,64 @@
+import type { Account } from '@/types/account';
+import { render, screen, waitFor } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import { ImportStepAccount } from './import-step-account';
+
+const account: Account = {
+ id: 'account-1',
+ name: 'Checking',
+ name_iv: null,
+ encrypted: false,
+ bank: null,
+ type: 'checking',
+ currency_code: 'USD',
+ banking_connection_id: null,
+ external_account_id: null,
+ linked_at: null,
+};
+
+describe('ImportStepAccount', () => {
+ it('shows a single account without auto-selecting by default', () => {
+ const onAccountSelect = vi.fn();
+ const onNext = vi.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText('Checking')).not.toBeNull();
+ expect(screen.getByRole('button', { name: 'Next' })).toHaveProperty(
+ 'disabled',
+ true,
+ );
+ expect(onAccountSelect).not.toHaveBeenCalled();
+ expect(onNext).not.toHaveBeenCalled();
+ });
+
+ it('auto-selects a single account when enabled for onboarding', async () => {
+ const onAutoSelect = vi.fn();
+ const onAccountSelect = vi.fn();
+ const onNext = vi.fn();
+
+ render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(onAutoSelect).toHaveBeenCalledWith(account.id);
+ expect(onNext).toHaveBeenCalled();
+ });
+ expect(onAccountSelect).not.toHaveBeenCalled();
+ });
+});
diff --git a/resources/js/components/transactions/import-step-account.tsx b/resources/js/components/transactions/import-step-account.tsx
index e12226fd..8058bb62 100644
--- a/resources/js/components/transactions/import-step-account.tsx
+++ b/resources/js/components/transactions/import-step-account.tsx
@@ -17,6 +17,8 @@ interface ImportStepAccountProps {
selectedAccountId: UUID | null;
onAccountSelect: (accountId: UUID) => void;
onNext: () => void;
+ autoSelectSingleAccount?: boolean;
+ onAutoSelect?: (accountId: UUID) => void;
}
export function ImportStepAccount({
@@ -24,16 +26,35 @@ export function ImportStepAccount({
selectedAccountId,
onAccountSelect,
onNext,
+ autoSelectSingleAccount = false,
+ onAutoSelect,
}: ImportStepAccountProps) {
const accounts = filterTransactionalAccounts(rawAccounts);
- // If there is only one account, auto-select it, and proceed to next step
useEffect(() => {
- if (accounts.length === 1) {
- onAccountSelect(accounts[0].id);
+ if (
+ autoSelectSingleAccount &&
+ accounts.length === 1 &&
+ !selectedAccountId
+ ) {
+ const accountId = accounts[0].id;
+
+ if (onAutoSelect) {
+ onAutoSelect(accountId);
+ } else {
+ onAccountSelect(accountId);
+ }
+
onNext();
}
- }, [accounts, onAccountSelect, onNext]);
+ }, [
+ accounts,
+ autoSelectSingleAccount,
+ onAccountSelect,
+ onAutoSelect,
+ onNext,
+ selectedAccountId,
+ ]);
if (accounts.length === 0) {
return (
diff --git a/resources/js/components/transactions/import-step-upload.test.tsx b/resources/js/components/transactions/import-step-upload.test.tsx
new file mode 100644
index 00000000..22db2f55
--- /dev/null
+++ b/resources/js/components/transactions/import-step-upload.test.tsx
@@ -0,0 +1,20 @@
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import { ImportStepUpload } from './import-step-upload';
+
+describe('ImportStepUpload', () => {
+ it('hides the back button when requested', () => {
+ render(
+ ,
+ );
+
+ expect(screen.queryByRole('button', { name: 'Back' })).toBeNull();
+ expect(screen.getByRole('button', { name: 'Next' })).not.toBeNull();
+ });
+});
diff --git a/resources/js/components/transactions/import-step-upload.tsx b/resources/js/components/transactions/import-step-upload.tsx
index f00496ab..794a8c8d 100644
--- a/resources/js/components/transactions/import-step-upload.tsx
+++ b/resources/js/components/transactions/import-step-upload.tsx
@@ -9,6 +9,7 @@ interface ImportStepUploadProps {
onFileSelect: (file: File) => void;
onNext: () => void;
onBack: () => void;
+ showBackButton?: boolean;
}
export function ImportStepUpload({
@@ -16,6 +17,7 @@ export function ImportStepUpload({
onFileSelect,
onNext,
onBack,
+ showBackButton = true,
}: ImportStepUploadProps) {
const [isDragging, setIsDragging] = useState(false);
@@ -135,10 +137,17 @@ export function ImportStepUpload({
)}
-
-
+
+ {showBackButton && (
+
+ )}
diff --git a/resources/js/components/transactions/import-transactions-drawer.tsx b/resources/js/components/transactions/import-transactions-drawer.tsx
index 40b10b0a..4e0e3549 100644
--- a/resources/js/components/transactions/import-transactions-drawer.tsx
+++ b/resources/js/components/transactions/import-transactions-drawer.tsx
@@ -31,6 +31,7 @@ import {
type ColumnMapping,
type ImportState,
} from '@/types/import';
+import { type UUID } from '@/types/uuid';
import { __ } from '@/utils/i18n';
import { router, usePage } from '@inertiajs/react';
import { useEffect, useState } from 'react';
@@ -48,6 +49,7 @@ interface ImportTransactionsDrawerProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onImportComplete?: () => void;
+ autoSelectSingleAccount?: boolean;
}
interface ImportError {
@@ -68,6 +70,7 @@ export function ImportTransactionsDrawer({
open,
onOpenChange,
onImportComplete,
+ autoSelectSingleAccount = false,
}: ImportTransactionsDrawerProps) {
const { locale } = usePage
().props;
const [isImporting, setIsImporting] = useState(false);
@@ -75,6 +78,8 @@ export function ImportTransactionsDrawer({
const [importTotal, setImportTotal] = useState(0);
const [importErrors, setImportErrors] = useState([]);
const [error, setError] = useState(null);
+ const [wasSingleAccountAutoSelected, setWasSingleAccountAutoSelected] =
+ useState(false);
const [selectedAccount, setSelectedAccount] = useState(
null,
);
@@ -89,6 +94,7 @@ export function ImportTransactionsDrawer({
transaction_date: null,
description: null,
amount: null,
+ balance: null,
},
dateFormat: DateFormat.YearMonthDay,
dateFormatDetected: false,
@@ -119,6 +125,7 @@ export function ImportTransactionsDrawer({
transaction_date: null,
description: null,
amount: null,
+ balance: null,
},
dateFormat: DateFormat.YearMonthDay,
dateFormatDetected: false,
@@ -126,14 +133,20 @@ export function ImportTransactionsDrawer({
});
setIsImporting(false);
setError(null);
+ setWasSingleAccountAutoSelected(false);
setSelectedAccount(null);
}
}, [open]);
- const handleAccountSelect = (accountId: number) => {
+ const handleAccountSelect = (accountId: UUID) => {
setState((prev) => ({ ...prev, selectedAccountId: accountId }));
};
+ const handleSingleAccountAutoSelect = (accountId: UUID) => {
+ setWasSingleAccountAutoSelected(true);
+ handleAccountSelect(accountId);
+ };
+
const handleFileSelect = async (file: File) => {
if (!file) {
setState((prev) => ({
@@ -653,6 +666,8 @@ export function ImportTransactionsDrawer({
onNext={() => {
moveToStep(ImportStep.UploadFile);
}}
+ autoSelectSingleAccount={autoSelectSingleAccount}
+ onAutoSelect={handleSingleAccountAutoSelect}
/>
);
@@ -665,6 +680,7 @@ export function ImportTransactionsDrawer({
moveToStep(ImportStep.MapColumns);
}}
onBack={() => moveToStep(ImportStep.SelectAccount)}
+ showBackButton={!wasSingleAccountAutoSelected}
/>
);
diff --git a/resources/js/lib/import-config-storage.ts b/resources/js/lib/import-config-storage.ts
index 52dfe1d6..5a85a811 100644
--- a/resources/js/lib/import-config-storage.ts
+++ b/resources/js/lib/import-config-storage.ts
@@ -1,4 +1,5 @@
import { type ColumnMapping, DateFormat } from '@/types/import';
+import { type UUID } from '@/types/uuid';
interface ImportConfig {
columnMapping: ColumnMapping;
@@ -7,10 +8,7 @@ interface ImportConfig {
const STORAGE_KEY_PREFIX = 'import_config_account_';
-export function saveImportConfig(
- accountId: number,
- config: ImportConfig,
-): void {
+export function saveImportConfig(accountId: UUID, config: ImportConfig): void {
if (typeof window === 'undefined') return;
try {
@@ -21,7 +19,7 @@ export function saveImportConfig(
}
}
-export function loadImportConfig(accountId: number): ImportConfig | null {
+export function loadImportConfig(accountId: UUID): ImportConfig | null {
if (typeof window === 'undefined') return null;
try {
@@ -45,7 +43,7 @@ export function loadImportConfig(accountId: number): ImportConfig | null {
}
}
-export function clearImportConfig(accountId: number): void {
+export function clearImportConfig(accountId: UUID): void {
if (typeof window === 'undefined') return;
try {
diff --git a/resources/js/types/import.ts b/resources/js/types/import.ts
index fe1af2a0..b32d678f 100644
--- a/resources/js/types/import.ts
+++ b/resources/js/types/import.ts
@@ -1,3 +1,5 @@
+import { UUID } from './uuid';
+
export enum ImportStep {
SelectAccount = 'select-account',
UploadFile = 'upload-file',
@@ -40,7 +42,7 @@ export interface ColumnOption {
export interface ImportState {
step: ImportStep;
- selectedAccountId: number | null;
+ selectedAccountId: UUID | null;
file: File | null;
parsedData: ParsedRow[];
columnHeaders: string[];