176 lines
8.6 KiB
TypeScript
176 lines
8.6 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import InputError from '@/components/InputError';
|
|
import { LoaderCircle } from 'lucide-react';
|
|
import { FormEventHandler, useState } from 'react';
|
|
import ComponentTitle from '@/components/ui/ComponentTitle';
|
|
|
|
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 [symbol, setSymbol] = useState('');
|
|
const [fullName, setFullName] = useState('');
|
|
const [processing, setProcessing] = useState(false);
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
|
|
const togglePriceTracking = (enabled: boolean) => {
|
|
setPriceTracking(enabled);
|
|
if (!enabled) {
|
|
setSymbol('');
|
|
setFullName('');
|
|
}
|
|
};
|
|
|
|
const submit: FormEventHandler = async (e) => {
|
|
e.preventDefault();
|
|
setProcessing(true);
|
|
setErrors({});
|
|
|
|
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 === 201 || response.status === 409) {
|
|
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 (
|
|
<div className="w-full">
|
|
<div className="space-y-4">
|
|
<ComponentTitle>SET UP YOUR TRACKER</ComponentTitle>
|
|
<p className="text-sm text-red-400/60 font-mono">
|
|
[SYSTEM] What are you tracking?
|
|
</p>
|
|
|
|
<form onSubmit={submit} className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="label" className="text-red-400 font-mono text-xs uppercase tracking-wider">
|
|
> Tracker Name
|
|
</Label>
|
|
<Input
|
|
id="label"
|
|
type="text"
|
|
placeholder="My Portfolio"
|
|
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"
|
|
/>
|
|
<p className="text-xs text-red-400/60 mt-1 font-mono">
|
|
[REQUIRED] e.g. "My Portfolio", "Books Read", "KM Run"
|
|
</p>
|
|
<InputError message={errors.label} />
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="unit" className="text-red-400 font-mono text-xs uppercase tracking-wider">
|
|
> Unit
|
|
</Label>
|
|
<Input
|
|
id="unit"
|
|
type="text"
|
|
placeholder="shares"
|
|
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"
|
|
/>
|
|
<p className="text-xs text-red-400/60 mt-1 font-mono">
|
|
[REQUIRED] e.g. "shares", "books", "km"
|
|
</p>
|
|
<InputError message={errors.unit} />
|
|
</div>
|
|
|
|
<div className="border border-red-500/30 p-4 space-y-3">
|
|
<label className="flex items-center gap-3 cursor-pointer group">
|
|
<input
|
|
type="checkbox"
|
|
checked={priceTracking}
|
|
onChange={(e) => togglePriceTracking(e.target.checked)}
|
|
className="w-4 h-4 accent-red-500"
|
|
/>
|
|
<span className="text-red-400 font-mono text-sm uppercase tracking-wider group-hover:text-red-300">
|
|
Enable price tracking
|
|
</span>
|
|
</label>
|
|
<p className="text-red-400/60 font-mono text-xs">
|
|
Track market price, portfolio value, and P&L. Requires an asset symbol.
|
|
</p>
|
|
|
|
{priceTracking && (
|
|
<div className="space-y-3 pt-2">
|
|
<div>
|
|
<Label htmlFor="symbol" className="text-red-400 font-mono text-xs uppercase tracking-wider">
|
|
> Asset Symbol
|
|
</Label>
|
|
<Input
|
|
id="symbol"
|
|
type="text"
|
|
placeholder="VWCE"
|
|
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"
|
|
/>
|
|
<InputError message={errors.symbol} />
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="full_name" className="text-red-400 font-mono text-xs uppercase tracking-wider">
|
|
> Full Name (Optional)
|
|
</Label>
|
|
<Input
|
|
id="full_name"
|
|
type="text"
|
|
placeholder="Vanguard FTSE All-World UCITS ETF"
|
|
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"
|
|
/>
|
|
<InputError message={errors.full_name} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<Button
|
|
type="submit"
|
|
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"
|
|
>
|
|
{processing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
|
|
[INITIALIZE]
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|