diff --git a/src/components/create-bounty-form.tsx b/src/components/create-bounty-form.tsx index 9fc27ac..d576362 100644 --- a/src/components/create-bounty-form.tsx +++ b/src/components/create-bounty-form.tsx @@ -2,20 +2,54 @@ import { useRef, useState } from "react"; -// BUG 2: Form validation - allows negative numbers and empty titles (see validation below) +// FIXED: Form validation with proper error states and UI feedback type CreateBountyFormProps = { onSubmit: (bounty: { title: string; reward: number; difficulty: string }) => void; }; +type FormErrors = { + title?: string; + reward?: string; +}; + export function CreateBountyForm({ onSubmit }: CreateBountyFormProps) { const [title, setTitle] = useState(""); const [reward, setReward] = useState(""); const [difficulty, setDifficulty] = useState("Easy"); const [submitting, setSubmitting] = useState(false); const [submissions, setSubmissions] = useState([]); + const [errors, setErrors] = useState({}); const isSubmittingRef = useRef(false); + const validateForm = (): boolean => { + const newErrors: FormErrors = {}; + + // Title validation: required, non-empty, minimum length + if (!title.trim()) { + newErrors.title = "Title is required"; + } else if (title.trim().length < 3) { + newErrors.title = "Title must be at least 3 characters"; + } else if (title.trim().length > 100) { + newErrors.title = "Title must be less than 100 characters"; + } + + // Reward validation: required, positive number + const rewardNum = Number(reward); + if (!reward.trim()) { + newErrors.reward = "Reward is required"; + } else if (isNaN(rewardNum)) { + newErrors.reward = "Reward must be a valid number"; + } else if (rewardNum <= 0) { + newErrors.reward = "Reward must be greater than $0"; + } else if (rewardNum > 1000000) { + newErrors.reward = "Reward cannot exceed $1,000,000"; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -25,16 +59,8 @@ export function CreateBountyForm({ onSubmit }: CreateBountyFormProps) { setSubmitting(true); try { - // Validation: check for empty title and negative reward - if (!title.trim()) { - alert("Title is required"); - isSubmittingRef.current = false; - setSubmitting(false); - return; - } - const rewardNum = Number(reward); - if (isNaN(rewardNum) || rewardNum <= 0) { - alert("Reward must be a positive number"); + // Validate form + if (!validateForm()) { isSubmittingRef.current = false; setSubmitting(false); return; @@ -52,53 +78,81 @@ export function CreateBountyForm({ onSubmit }: CreateBountyFormProps) { difficulty, }); + // Clear form on success setTitle(""); setReward(""); + setErrors({}); } finally { isSubmittingRef.current = false; setSubmitting(false); } }; + // Clear error when user starts typing + const handleTitleChange = (value: string) => { + setTitle(value); + if (errors.title) { + setErrors((prev) => ({ ...prev, title: undefined })); + } + }; + + const handleRewardChange = (value: string) => { + setReward(value); + if (errors.reward) { + setErrors((prev) => ({ ...prev, reward: undefined })); + } + }; + return (

Create New Bounty

-
+
setTitle(e.target.value)} - className="w-full rounded-lg border border-slate-200 px-3 py-2" + onChange={(e) => handleTitleChange(e.target.value)} + className={`w-full rounded-lg border px-3 py-2 ${ + errors.title + ? "border-red-300 bg-red-50 focus:ring-red-500" + : "border-slate-200 focus:ring-blue-500" + }`} placeholder="Bounty title" - required - minLength={1} + disabled={submitting} /> - {/* FIX 2: Show validation error */} {errors.title && ( -

{errors.title}

+

+ ⚠️ {errors.title} +

)}
setReward(e.target.value)} - className="w-full rounded-lg border border-slate-200 px-3 py-2" + onChange={(e) => handleRewardChange(e.target.value)} + className={`w-full rounded-lg border px-3 py-2 ${ + errors.reward + ? "border-red-300 bg-red-50 focus:ring-red-500" + : "border-slate-200 focus:ring-blue-500" + }`} placeholder="100" min="1" + step="0.01" + disabled={submitting} /> - {/* FIX 2: Show validation error */} {errors.reward && ( -

{errors.reward}

+

+ ⚠️ {errors.reward} +

)}
@@ -110,6 +164,7 @@ export function CreateBountyForm({ onSubmit }: CreateBountyFormProps) { value={difficulty} onChange={(e) => setDifficulty(e.target.value)} className="w-full rounded-lg border border-slate-200 px-3 py-2" + disabled={submitting} > @@ -117,10 +172,9 @@ export function CreateBountyForm({ onSubmit }: CreateBountyFormProps) {
- {/* FIX 1: Disable button while submitting */}