fix - CreateTrackerStep use plain fetch to avoid Inertia page reload, add csrf-token meta
This commit is contained in:
parent
e93ce7b342
commit
14f5d34775
3 changed files with 58 additions and 42 deletions
|
|
@ -23,7 +23,7 @@ public function show(): JsonResponse
|
||||||
return response()->json($tracker->load('asset'));
|
return response()->json($tracker->load('asset'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request): RedirectResponse|JsonResponse
|
public function store(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'label' => 'required|string|max:255',
|
'label' => 'required|string|max:255',
|
||||||
|
|
@ -45,14 +45,14 @@ public function store(Request $request): RedirectResponse|JsonResponse
|
||||||
$assetId = $asset->id;
|
$assetId = $asset->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->tracker()->create([
|
$tracker = $user->tracker()->create([
|
||||||
'label' => $validated['label'],
|
'label' => $validated['label'],
|
||||||
'unit' => $validated['unit'],
|
'unit' => $validated['unit'],
|
||||||
'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? false,
|
'price_tracking_enabled' => $validated['price_tracking_enabled'] ?? false,
|
||||||
'asset_id' => $assetId,
|
'asset_id' => $assetId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return back();
|
return response()->json($tracker->load('asset'), 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request): RedirectResponse|JsonResponse
|
public function update(Request $request): RedirectResponse|JsonResponse
|
||||||
|
|
@ -68,7 +68,7 @@ public function update(Request $request): RedirectResponse|JsonResponse
|
||||||
$tracker = User::default()->tracker;
|
$tracker = User::default()->tracker;
|
||||||
|
|
||||||
if (! $tracker) {
|
if (! $tracker) {
|
||||||
return response()->json(['error' => 'No tracker found.'], 404);
|
return back()->withErrors(['tracker' => 'No tracker found.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (array_key_exists('symbol', $validated)) {
|
if (array_key_exists('symbol', $validated)) {
|
||||||
|
|
|
||||||
|
|
@ -2,53 +2,68 @@ import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import InputError from '@/components/InputError';
|
import InputError from '@/components/InputError';
|
||||||
import { useForm } from '@inertiajs/react';
|
|
||||||
import { LoaderCircle } from 'lucide-react';
|
import { LoaderCircle } from 'lucide-react';
|
||||||
import { FormEventHandler, useState } from 'react';
|
import { FormEventHandler, useState } from 'react';
|
||||||
import ComponentTitle from '@/components/ui/ComponentTitle';
|
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 {
|
interface CreateTrackerStepProps {
|
||||||
onSuccess: (priceTrackingEnabled: boolean) => void;
|
onSuccess: (priceTrackingEnabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) {
|
export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps) {
|
||||||
|
const [label, setLabel] = useState('');
|
||||||
|
const [unit, setUnit] = useState('');
|
||||||
const [priceTracking, setPriceTracking] = useState(false);
|
const [priceTracking, setPriceTracking] = useState(false);
|
||||||
|
const [symbol, setSymbol] = useState('');
|
||||||
const { data, setData, post, processing, errors } = useForm<TrackerFormData>({
|
const [fullName, setFullName] = useState('');
|
||||||
label: '',
|
const [processing, setProcessing] = useState(false);
|
||||||
unit: '',
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
price_tracking_enabled: '0',
|
|
||||||
symbol: '',
|
|
||||||
full_name: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const togglePriceTracking = (enabled: boolean) => {
|
const togglePriceTracking = (enabled: boolean) => {
|
||||||
setPriceTracking(enabled);
|
setPriceTracking(enabled);
|
||||||
setData({
|
if (!enabled) {
|
||||||
...data,
|
setSymbol('');
|
||||||
price_tracking_enabled: enabled ? '1' : '0',
|
setFullName('');
|
||||||
symbol: enabled ? data.symbol : '',
|
}
|
||||||
full_name: enabled ? data.full_name : '',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit: FormEventHandler = (e) => {
|
const submit: FormEventHandler = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
setProcessing(true);
|
||||||
|
setErrors({});
|
||||||
|
|
||||||
post(route('tracker.store'), {
|
try {
|
||||||
onSuccess: () => {
|
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);
|
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 (
|
return (
|
||||||
|
|
@ -68,8 +83,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps)
|
||||||
id="label"
|
id="label"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="My Portfolio"
|
placeholder="My Portfolio"
|
||||||
value={data.label}
|
value={label}
|
||||||
onChange={(e) => setData('label', e.target.value)}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-red-400/60 mt-1 font-mono">
|
<p className="text-xs text-red-400/60 mt-1 font-mono">
|
||||||
|
|
@ -86,8 +101,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps)
|
||||||
id="unit"
|
id="unit"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="shares"
|
placeholder="shares"
|
||||||
value={data.unit}
|
value={unit}
|
||||||
onChange={(e) => setData('unit', e.target.value)}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-red-400/60 mt-1 font-mono">
|
<p className="text-xs text-red-400/60 mt-1 font-mono">
|
||||||
|
|
@ -122,8 +137,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps)
|
||||||
id="symbol"
|
id="symbol"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="VWCE"
|
placeholder="VWCE"
|
||||||
value={data.symbol}
|
value={symbol}
|
||||||
onChange={(e) => setData('symbol', e.target.value.toUpperCase())}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
<InputError message={errors.symbol} />
|
<InputError message={errors.symbol} />
|
||||||
|
|
@ -136,8 +151,8 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps)
|
||||||
id="full_name"
|
id="full_name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Vanguard FTSE All-World UCITS ETF"
|
placeholder="Vanguard FTSE All-World UCITS ETF"
|
||||||
value={data.full_name}
|
value={fullName}
|
||||||
onChange={(e) => setData('full_name', e.target.value)}
|
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"
|
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"
|
||||||
/>
|
/>
|
||||||
<InputError message={errors.full_name} />
|
<InputError message={errors.full_name} />
|
||||||
|
|
@ -148,7 +163,7 @@ export default function CreateTrackerStep({ onSuccess }: CreateTrackerStepProps)
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={processing || !data.label || !data.unit || (priceTracking && !data.symbol)}
|
disabled={processing || !label || !unit || (priceTracking && !symbol)}
|
||||||
className="w-full bg-red-500 hover:bg-red-500 text-black font-mono text-sm font-bold border-red-500 rounded-none border-2 uppercase tracking-wider transition-all glow-red"
|
className="w-full bg-red-500 hover:bg-red-500 text-black font-mono text-sm font-bold border-red-500 rounded-none border-2 uppercase tracking-wider transition-all glow-red"
|
||||||
>
|
>
|
||||||
{processing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
{processing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue