From 5c1f3bb18314cf68b49e19b521d95b951d5d37c3 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sat, 2 May 2026 18:17:42 +0200 Subject: [PATCH] 33 - Onboarding: CreateTrackerStep, update OnboardingFlow, fix dashboard fetch URLs --- app/Http/Controllers/TrackerController.php | 27 +-- .../js/components/Assets/AssetSetupForm.tsx | 14 +- .../Onboarding/CreateTrackerStep.tsx | 161 ++++++++++++++++++ .../components/Onboarding/OnboardingFlow.tsx | 87 +++------- resources/js/pages/dashboard.tsx | 84 +++++---- 5 files changed, 256 insertions(+), 117 deletions(-) create mode 100644 resources/js/components/Onboarding/CreateTrackerStep.tsx diff --git a/app/Http/Controllers/TrackerController.php b/app/Http/Controllers/TrackerController.php index 32d0d68..01890de 100644 --- a/app/Http/Controllers/TrackerController.php +++ b/app/Http/Controllers/TrackerController.php @@ -34,16 +34,16 @@ public function store(Request $request): JsonResponse $user = User::default(); + if ($user->tracker) { + return response()->json(['error' => 'Tracker already exists.'], 409); + } + $assetId = null; if (! empty($validated['symbol'])) { $asset = Asset::findOrCreateBySymbol($validated['symbol'], $validated['full_name'] ?? null); $assetId = $asset->id; } - if ($user->tracker) { - return response()->json(['error' => 'Tracker already exists.'], 409); - } - $tracker = $user->tracker()->create([ 'label' => $validated['label'], 'unit' => $validated['unit'], @@ -79,12 +79,19 @@ public function update(Request $request): JsonResponse } } - $update = array_filter([ - 'label' => $validated['label'] ?? null, - 'unit' => $validated['unit'] ?? null, - 'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? null, - 'asset_id' => $tracker->asset_id, - ], fn ($v) => $v !== null); + $update = []; + if (isset($validated['label'])) { + $update['label'] = $validated['label']; + } + if (isset($validated['unit'])) { + $update['unit'] = $validated['unit']; + } + if (array_key_exists('price_tracking_enabled', $validated)) { + $update['price_tracking_enabled'] = $validated['price_tracking_enabled']; + } + if (array_key_exists('symbol', $validated)) { + $update['asset_id'] = $tracker->asset_id; + } $tracker->update($update); diff --git a/resources/js/components/Assets/AssetSetupForm.tsx b/resources/js/components/Assets/AssetSetupForm.tsx index 315d72b..5295b1e 100644 --- a/resources/js/components/Assets/AssetSetupForm.tsx +++ b/resources/js/components/Assets/AssetSetupForm.tsx @@ -19,7 +19,7 @@ interface AssetSetupFormProps { } export default function AssetSetupForm({ onSuccess, onCancel }: AssetSetupFormProps) { - const { data, setData, post, processing, errors } = useForm({ + const { data, setData, patch, processing, errors } = useForm({ symbol: '', full_name: '', }); @@ -28,13 +28,13 @@ export default function AssetSetupForm({ onSuccess, onCancel }: AssetSetupFormPr useEffect(() => { const fetchCurrentAsset = async () => { try { - const response = await fetch('/assets/current'); + const response = await fetch('/tracker'); if (response.ok) { - const assetData = await response.json(); - if (assetData.asset) { + const tracker = await response.json(); + if (tracker?.asset) { setData({ - symbol: assetData.asset.symbol || '', - full_name: assetData.asset.full_name || '', + symbol: tracker.asset.symbol || '', + full_name: tracker.asset.full_name || '', }); } } @@ -57,7 +57,7 @@ export default function AssetSetupForm({ onSuccess, onCancel }: AssetSetupFormPr const submit: FormEventHandler = (e) => { e.preventDefault(); - post(route('assets.set-current'), { + patch(route('tracker.update'), { onSuccess: () => { if (onSuccess) onSuccess(); }, diff --git a/resources/js/components/Onboarding/CreateTrackerStep.tsx b/resources/js/components/Onboarding/CreateTrackerStep.tsx new file mode 100644 index 0000000..d88960b --- /dev/null +++ b/resources/js/components/Onboarding/CreateTrackerStep.tsx @@ -0,0 +1,161 @@ +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import InputError from '@/components/InputError'; +import { useForm } from '@inertiajs/react'; +import { LoaderCircle } from 'lucide-react'; +import { FormEventHandler, useState } from 'react'; +import ComponentTitle from '@/components/ui/ComponentTitle'; + +interface TrackerFormData { + label: string; + unit: string; + price_tracking_enabled: string; + symbol: string; + full_name: string; + [key: string]: string; +} + +interface CreateTrackerStepProps { + onSuccess: (priceTrackingEnabled: boolean) => void; +} + +export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) { + const [priceTracking, setPriceTracking] = useState(false); + + const { data, setData, post, processing, errors } = useForm({ + label: '', + unit: '', + price_tracking_enabled: '0', + symbol: '', + full_name: '', + }); + + const togglePriceTracking = (enabled: boolean) => { + setPriceTracking(enabled); + setData({ + ...data, + price_tracking_enabled: enabled ? '1' : '0', + symbol: enabled ? data.symbol : '', + full_name: enabled ? data.full_name : '', + }); + }; + + const submit: FormEventHandler = (e) => { + e.preventDefault(); + + post(route('tracker.store'), { + onSuccess: () => { + onSuccess(priceTracking); + }, + }); + }; + + return ( +
+
+ SET UP YOUR TRACKER +

+ [SYSTEM] What are you tracking? +

+ +
+
+ + setData('label', e.target.value)} + className="bg-black border-red-500 text-red-400 focus:border-red-300 font-mono text-sm rounded-none border-2 focus:ring-0 focus:outline-none focus:shadow-[0_0_10px_rgba(239,68,68,0.5)] placeholder:text-red-400/40 transition-all" + /> +

+ [REQUIRED] e.g. "My Portfolio", "Books Read", "KM Run" +

+ +
+ +
+ + setData('unit', e.target.value)} + className="bg-black border-red-500 text-red-400 focus:border-red-300 font-mono text-sm rounded-none border-2 focus:ring-0 focus:outline-none focus:shadow-[0_0_10px_rgba(239,68,68,0.5)] placeholder:text-red-400/40 transition-all" + /> +

+ [REQUIRED] e.g. "shares", "books", "km" +

+ +
+ +
+ +

+ Track market price, portfolio value, and P&L. Requires an asset symbol. +

+ + {priceTracking && ( +
+
+ + setData('symbol', e.target.value.toUpperCase())} + className="bg-black border-red-500 text-red-400 focus:border-red-300 font-mono text-sm rounded-none border-2 focus:ring-0 focus:outline-none focus:shadow-[0_0_10px_rgba(239,68,68,0.5)] placeholder:text-red-400/40 transition-all" + /> + +
+
+ + setData('full_name', e.target.value)} + className="bg-black border-red-500 text-red-400 focus:border-red-300 font-mono text-sm rounded-none border-2 focus:ring-0 focus:outline-none focus:shadow-[0_0_10px_rgba(239,68,68,0.5)] placeholder:text-red-400/40 transition-all" + /> + +
+
+ )} +
+ + +
+
+
+ ); +} diff --git a/resources/js/components/Onboarding/OnboardingFlow.tsx b/resources/js/components/Onboarding/OnboardingFlow.tsx index 3053acf..04f6615 100644 --- a/resources/js/components/Onboarding/OnboardingFlow.tsx +++ b/resources/js/components/Onboarding/OnboardingFlow.tsx @@ -3,44 +3,7 @@ import AssetSetupForm from '@/components/Assets/AssetSetupForm'; import AddPurchaseForm from '@/components/Transactions/AddPurchaseForm'; import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm'; import UpdatePriceForm from '@/components/Pricing/UpdatePriceForm'; - -type TrackerType = 'simple' | 'asset'; - -function TrackerTypeSelector({ onSelect }: { onSelect: (type: TrackerType) => void }) { - return ( -
-

- [SELECT] What do you want to track? -

- -
- - - -
-
- ); -} +import CreateTrackerStep from '@/components/Onboarding/CreateTrackerStep'; function PriceTrackingStep({ onEnable, onSkip }: { onEnable: () => void; onSkip?: () => void }) { const [enabled, setEnabled] = useState(false); @@ -70,7 +33,7 @@ function PriceTrackingStep({ onEnable, onSkip }: { onEnable: () => void; onSkip? {!enabled && (