add predictions form component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
127
packages/client/src/components/predictions-form.tsx
Normal file
127
packages/client/src/components/predictions-form.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { useState } from "react"
|
||||
import type { Country, Prediction } from "@celebrate-esc/shared"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
|
||||
interface PredictionsFormProps {
|
||||
countries: Country[]
|
||||
existingPrediction: Prediction | null
|
||||
locked: boolean
|
||||
onSubmit: (prediction: { predictedWinner: string; top3: string[]; nulPointsPick: string }) => void
|
||||
}
|
||||
|
||||
export function PredictionsForm({ countries, existingPrediction, locked, onSubmit }: PredictionsFormProps) {
|
||||
const [winner, setWinner] = useState(existingPrediction?.predictedWinner ?? "")
|
||||
const [top3, setTop3] = useState<string[]>(existingPrediction?.top3 ?? [])
|
||||
const [nulPoints, setNulPoints] = useState(existingPrediction?.nulPointsPick ?? "")
|
||||
|
||||
if (locked) {
|
||||
if (!existingPrediction) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="py-6 text-center text-muted-foreground">
|
||||
Predictions are locked. You didn't submit one in time.
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Your Predictions (locked)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-2 text-sm">
|
||||
<p>
|
||||
<span className="font-medium">Winner:</span>{" "}
|
||||
{countries.find((c) => c.code === existingPrediction.predictedWinner)?.name}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">Top 3:</span>{" "}
|
||||
{existingPrediction.top3.map((code) => countries.find((c) => c.code === code)?.name).join(", ")}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">Nul Points:</span>{" "}
|
||||
{countries.find((c) => c.code === existingPrediction.nulPointsPick)?.name}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function toggleTop3(code: string) {
|
||||
setTop3((prev) => {
|
||||
if (prev.includes(code)) return prev.filter((c) => c !== code)
|
||||
if (prev.length >= 3) return prev
|
||||
return [...prev, code]
|
||||
})
|
||||
}
|
||||
|
||||
const canSubmit = winner && top3.length === 3 && nulPoints && !top3.includes(winner)
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Predictions</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">Winner</label>
|
||||
<select
|
||||
className="w-full rounded-md border bg-background px-3 py-2 text-sm"
|
||||
value={winner}
|
||||
onChange={(e) => setWinner(e.target.value)}
|
||||
>
|
||||
<option value="">Select a country...</option>
|
||||
{countries.map((c) => (
|
||||
<option key={c.code} value={c.code}>
|
||||
{c.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">Top 3 (select 3, excl. winner)</label>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{countries
|
||||
.filter((c) => c.code !== winner)
|
||||
.map((c) => (
|
||||
<button
|
||||
type="button"
|
||||
key={c.code}
|
||||
onClick={() => toggleTop3(c.code)}
|
||||
className={`rounded-full border px-2 py-0.5 text-xs transition-colors ${
|
||||
top3.includes(c.code)
|
||||
? "border-primary bg-primary text-primary-foreground"
|
||||
: "border-border hover:bg-muted"
|
||||
} ${top3.length >= 3 && !top3.includes(c.code) ? "opacity-40" : ""}`}
|
||||
>
|
||||
{c.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">Nul Points (last place)</label>
|
||||
<select
|
||||
className="w-full rounded-md border bg-background px-3 py-2 text-sm"
|
||||
value={nulPoints}
|
||||
onChange={(e) => setNulPoints(e.target.value)}
|
||||
>
|
||||
<option value="">Select a country...</option>
|
||||
{countries.map((c) => (
|
||||
<option key={c.code} value={c.code}>
|
||||
{c.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => onSubmit({ predictedWinner: winner, top3, nulPointsPick: nulPoints })} disabled={!canSubmit}>
|
||||
{existingPrediction ? "Update Prediction" : "Submit Prediction"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user