From 14f5d34775153b5613458605cdf6b042e2c32ccf Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sun, 3 May 2026 01:38:02 +0200 Subject: [PATCH] fix - CreateTrackerStep use plain fetch to avoid Inertia page reload, add csrf-token meta --- app/Http/Controllers/TrackerController.php | 8 +- .../Onboarding/CreateTrackerStep.tsx | 91 +++++++++++-------- resources/views/app.blade.php | 1 + 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/app/Http/Controllers/TrackerController.php b/app/Http/Controllers/TrackerController.php index 3a54b05..a94e999 100644 --- a/app/Http/Controllers/TrackerController.php +++ b/app/Http/Controllers/TrackerController.php @@ -23,7 +23,7 @@ public function show(): JsonResponse return response()->json($tracker->load('asset')); } - public function store(Request $request): RedirectResponse|JsonResponse + public function store(Request $request): JsonResponse { $validated = $request->validate([ 'label' => 'required|string|max:255', @@ -45,14 +45,14 @@ public function store(Request $request): RedirectResponse|JsonResponse $assetId = $asset->id; } - $user->tracker()->create([ + $tracker = $user->tracker()->create([ 'label' => $validated['label'], 'unit' => $validated['unit'], 'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? false, 'asset_id' => $assetId, ]); - return back(); + return response()->json($tracker->load('asset'), 201); } public function update(Request $request): RedirectResponse|JsonResponse @@ -68,7 +68,7 @@ public function update(Request $request): RedirectResponse|JsonResponse $tracker = User::default()->tracker; if (! $tracker) { - return response()->json(['error' => 'No tracker found.'], 404); + return back()->withErrors(['tracker' => 'No tracker found.']); } if (array_key_exists('symbol', $validated)) { diff --git a/resources/js/components/Onboarding/CreateTrackerStep.tsx b/resources/js/components/Onboarding/CreateTrackerStep.tsx index d88960b..275e45b 100644 --- a/resources/js/components/Onboarding/CreateTrackerStep.tsx +++ b/resources/js/components/Onboarding/CreateTrackerStep.tsx @@ -2,53 +2,68 @@ 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 [label, setLabel] = useState(''); + const [unit, setUnit] = useState(''); const [priceTracking, setPriceTracking] = useState(false); - - const { data, setData, post, processing, errors } = useForm({ - label: '', - unit: '', - price_tracking_enabled: '0', - symbol: '', - full_name: '', - }); + const [symbol, setSymbol] = useState(''); + const [fullName, setFullName] = useState(''); + const [processing, setProcessing] = useState(false); + const [errors, setErrors] = useState>({}); const togglePriceTracking = (enabled: boolean) => { setPriceTracking(enabled); - setData({ - ...data, - price_tracking_enabled: enabled ? '1' : '0', - symbol: enabled ? data.symbol : '', - full_name: enabled ? data.full_name : '', - }); + if (!enabled) { + setSymbol(''); + setFullName(''); + } }; - const submit: FormEventHandler = (e) => { + const submit: FormEventHandler = async (e) => { e.preventDefault(); + setProcessing(true); + setErrors({}); - post(route('tracker.store'), { - onSuccess: () => { + try { + const response = await fetch(route('tracker.store'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement)?.content ?? '', + 'Accept': 'application/json', + }, + body: JSON.stringify({ + label, + unit, + price_tracking_enabled: priceTracking ? 1 : 0, + symbol: priceTracking ? symbol : null, + full_name: priceTracking ? fullName : null, + }), + }); + + if (response.ok || response.status === 302) { onSuccess(priceTracking); - }, - }); + } else { + const data = await response.json(); + if (data.errors) { + setErrors(data.errors); + } else if (data.message) { + setErrors({ label: data.message }); + } + } + } catch { + setErrors({ label: 'Something went wrong. Please try again.' }); + } finally { + setProcessing(false); + } }; return ( @@ -68,8 +83,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) id="label" type="text" placeholder="My Portfolio" - value={data.label} - onChange={(e) => setData('label', e.target.value)} + value={label} + onChange={(e) => setLabel(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" />

@@ -86,8 +101,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) id="unit" type="text" placeholder="shares" - value={data.unit} - onChange={(e) => setData('unit', e.target.value)} + value={unit} + onChange={(e) => setUnit(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" />

@@ -122,8 +137,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) id="symbol" type="text" placeholder="VWCE" - value={data.symbol} - onChange={(e) => setData('symbol', e.target.value.toUpperCase())} + value={symbol} + onChange={(e) => setSymbol(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" /> @@ -136,8 +151,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) id="full_name" type="text" placeholder="Vanguard FTSE All-World UCITS ETF" - value={data.full_name} - onChange={(e) => setData('full_name', e.target.value)} + value={fullName} + onChange={(e) => setFullName(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" /> @@ -148,7 +163,7 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps)