From 80274e03a8e697509ddbd0ec3e7a4e9d5d752d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Sun, 12 Apr 2026 16:22:38 +0100 Subject: [PATCH] fix: align onboarding account types with current asset support (#273) ## Summary - add real estate to the onboarding account type explainer and update investment copy to mention crypto and cold wallets - align onboarding manual account creation with the real-estate feature flag and real-estate payload requirements - cover the onboarding copy and real-estate creation flow with browser tests ## Testing - php artisan test --compact tests/Browser/OnboardingFlowTest.php --- .../onboarding/step-account-types.tsx | 17 +++- .../onboarding/step-create-account.tsx | 94 +++++++++++++++---- tests/Browser/OnboardingFlowTest.php | 71 ++++++++++++++ 3 files changed, 161 insertions(+), 21 deletions(-) diff --git a/resources/js/components/onboarding/step-account-types.tsx b/resources/js/components/onboarding/step-account-types.tsx index c3bf84e6..6f746a5f 100644 --- a/resources/js/components/onboarding/step-account-types.tsx +++ b/resources/js/components/onboarding/step-account-types.tsx @@ -1,7 +1,9 @@ import { StepButton } from '@/components/onboarding/step-button'; import { StepHeader } from '@/components/onboarding/step-header'; +import { type SharedData } from '@/types'; import { accountIconByType } from '@/types/account'; import { __ } from '@/utils/i18n'; +import { usePage } from '@inertiajs/react'; import { Banknote } from 'lucide-react'; interface StepAccountTypesProps { @@ -30,7 +32,7 @@ const accountTypes = [ { type: 'investment' as const, nameKey: 'Investment', - descriptionKey: 'Stocks, ETFs, and portfolios', + descriptionKey: 'Stocks, ETFs, crypto, and cold wallets', hasTransactions: false, }, { @@ -45,9 +47,20 @@ const accountTypes = [ descriptionKey: 'Mortgages and loans', hasTransactions: false, }, + { + type: 'real_estate' as const, + nameKey: 'Real Estate', + descriptionKey: 'Properties and real estate assets', + hasTransactions: false, + }, ]; export function StepAccountTypes({ onContinue }: StepAccountTypesProps) { + const { features } = usePage().props; + const visibleAccountTypes = features['real-estate'] + ? accountTypes + : accountTypes.filter((account) => account.type !== 'real_estate'); + return (
- {accountTypes.map((account) => { + {visibleAccountTypes.map((account) => { const Icon = accountIconByType(account.type); return ( diff --git a/resources/js/components/onboarding/step-create-account.tsx b/resources/js/components/onboarding/step-create-account.tsx index da965997..98c7269a 100644 --- a/resources/js/components/onboarding/step-create-account.tsx +++ b/resources/js/components/onboarding/step-create-account.tsx @@ -74,6 +74,7 @@ export function StepCreateAccount({ const { pricing, subscriptionsEnabled, features, locale } = usePage().props; const openBankingEnabled = features['open-banking']; + const realEstateEnabled = features['real-estate']; const [mode, setMode] = useState('select'); const [selectedMode, setSelectedMode] = useState<'manual' | 'connected'>( 'manual', @@ -164,35 +165,87 @@ export function StepCreateAccount({ setIsSubmitting(true); try { - let finalBankId: string; + const isRealEstate = type === 'real_estate'; + let finalBankId: string | null = null; - if (customBank) { - if (!customBank.name.trim()) { - setError(__('Please enter a bank name.')); - setIsSubmitting(false); - return; + if (!isRealEstate) { + if (customBank) { + if (!customBank.name.trim()) { + setError(__('Please enter a bank name.')); + setIsSubmitting(false); + return; + } + + const createdBankId = await createBankAndGetId(); + if (!createdBankId) { + throw new Error('Failed to create bank'); + } + + finalBankId = createdBankId; + } else { + if (!bankId) { + setError(__('Please select a bank.')); + setIsSubmitting(false); + return; + } + + finalBankId = String(bankId); } - const createdBankId = await createBankAndGetId(); - if (!createdBankId) { - throw new Error('Failed to create bank'); - } - finalBankId = createdBankId; - } else { - if (!bankId) { - setError(__('Please select a bank.')); - setIsSubmitting(false); - return; - } - finalBankId = String(bankId); } const response = await fetch(store.url(), { method: 'POST', body: JSON.stringify({ name: displayName, - bank_id: finalBankId, + ...(finalBankId ? { bank_id: finalBankId } : {}), type: type, currency_code: currencyCode, + ...(formDataRef.current.balance !== null + ? { balance: formDataRef.current.balance } + : {}), + ...(formDataRef.current.realEstate + ? { + property_type: + formDataRef.current.realEstate.propertyType, + address: + formDataRef.current.realEstate.address || + null, + purchase_price: + formDataRef.current.realEstate + .purchasePrice || null, + purchase_date: + formDataRef.current.realEstate.purchaseDate || + null, + area_value: + formDataRef.current.realEstate.areaValue || + null, + area_unit: + formDataRef.current.realEstate.areaUnit, + linked_loan_account_id: + formDataRef.current.realEstate + .linkedLoanAccountId, + notes: + formDataRef.current.realEstate.notes || null, + revaluation_percentage: + formDataRef.current.realEstate + .revaluationPercentage || null, + } + : {}), + ...(formDataRef.current.loan + ? { + annual_interest_rate: + formDataRef.current.loan.annualInterestRate || + null, + loan_term_months: + formDataRef.current.loan.loanTermMonths || + null, + loan_start_date: + formDataRef.current.loan.startDate || null, + original_amount: + formDataRef.current.loan.originalAmount || + null, + } + : {}), }), headers: { 'Content-Type': 'application/json', @@ -488,6 +541,9 @@ export function StepCreateAccount({ > assertSee('Whisper Money') ->click("Let's Get Started") ->wait(1) + ->assertSee('Stocks, ETFs, crypto, and cold wallets') ->assertSee('Account Types') ->assertNoJavascriptErrors(); }); +it('shows real estate on onboarding account types when feature is enabled', function () { + $user = User::factory()->create([ + 'onboarded_at' => null, + ]); + + Feature::for($user)->activate('real-estate'); + + $this->actingAs($user); + + $page = visit('/onboarding'); + + $page->click("Let's Get Started") + ->wait(1) + ->assertSee('Account Types') + ->assertSee('Real Estate') + ->assertSee('Properties and real estate assets') + ->assertNoJavascriptErrors(); +}); + // ============================================================================= // Existing Account Flow Tests // ============================================================================= @@ -212,6 +232,57 @@ it('shows add another account form without first account restriction', function ->assertNoJavascriptErrors(); }); +it('creates a real estate account during onboarding when feature is enabled', function () { + $user = User::factory()->create([ + 'onboarded_at' => null, + ]); + + Feature::for($user)->activate('real-estate'); + + $this->actingAs($user); + + $page = visit('/onboarding'); + + $page->click("Let's Get Started") + ->wait(1) + ->click('Create Your First Account') + ->wait(1) + ->assertSee('Create an Account') + ->click('Manual') + ->wait(1) + ->click('Continue') + ->wait(1) + ->fill('#display_name', 'My Apartment') + ->click('Select account type') + ->wait(1) + ->click('[role="option"]:has-text("Real Estate")') + ->wait(1) + ->click('Select currency') + ->wait(1) + ->click('[role="option"]:has-text("EUR")') + ->wait(1) + ->click('Select property type') + ->wait(1) + ->click('[role="option"]:has-text("Residential")') + ->wait(1) + ->click('Create Account') + ->wait(5) + ->assertSee('Set Account Balance') + ->assertNoJavascriptErrors(); + + $user->refresh(); + + $account = $user->accounts()->first(); + + expect($account)->not->toBeNull(); + expect($account->type->value)->toBe('real_estate'); + expect($account->name)->toBe('My Apartment'); + expect($account->currency_code)->toBe('EUR'); + expect($account->bank_id)->toBeNull(); + expect($account->realEstateDetail)->not->toBeNull(); + expect($account->realEstateDetail->property_type->value)->toBe('residential'); +}); + // ============================================================================= // Full End-to-End Flow Test // =============================================================================