From 751b76aead141cf437b79117540c69bfe0a8b648 Mon Sep 17 00:00:00 2001 From: armorbreak001 Date: Tue, 14 Apr 2026 19:22:13 +0800 Subject: [PATCH] feat(frontend): add interactive bounty submission UI flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #274 - Create BountySubmitModal with multi-step form (Form → Confirm → Success) - GitHub PR URL input with validation - Additional Notes textarea - Dynamic external links via react-hook-form useFieldArray (+Add Link button) - Confirmation step preventing accidental submissions - Success state with checkmark animation - Demo page with trigger button --- .../src/components/BountySubmitModal.demo.tsx | 45 ++++ frontend/src/components/BountySubmitModal.tsx | 245 ++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 frontend/src/components/BountySubmitModal.demo.tsx create mode 100644 frontend/src/components/BountySubmitModal.tsx diff --git a/frontend/src/components/BountySubmitModal.demo.tsx b/frontend/src/components/BountySubmitModal.demo.tsx new file mode 100644 index 0000000..58e16ca --- /dev/null +++ b/frontend/src/components/BountySubmitModal.demo.tsx @@ -0,0 +1,45 @@ +'use client'; + +import React, { useState } from 'react'; +import { BountySubmitModal } from './BountySubmitModal'; + +/** + * Demo page for Bounty Submission UI Flow. + * Shows a button that opens the submission modal. + */ +export default function BountySubmitDemo(): JSX.Element { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+
+

+ Interactive Bounty Submission +

+

+ Multi-step form with dynamic link management and confirmation step. +

+
+ + {/* Trigger button */} +
+ +

+ Click to open the bounty submission modal +

+
+ + {/* The modal */} + setIsOpen(false)} + bountyTitle="Implement User Authentication" + /> +
+ ); +} diff --git a/frontend/src/components/BountySubmitModal.tsx b/frontend/src/components/BountySubmitModal.tsx new file mode 100644 index 0000000..e207eed --- /dev/null +++ b/frontend/src/components/BountySubmitModal.tsx @@ -0,0 +1,245 @@ +'use client'; + +import React, { useState } from 'react'; +import { useForm, useFieldArray, watch } from 'react-hook-form'; +import { Modal } from '@/components/ui/Modal'; +import { Plus, Trash2, Send, ArrowLeft, CheckCircle2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface BountySubmissionForm { + prUrl: string; + notes: string; + externalLinks: { url: string }[]; +} + +type Step = 'form' | 'confirm' | 'success'; + +interface BountySubmitModalProps { + isOpen: boolean; + onClose: () => void; + bountyTitle?: string; +} + +/** + * Interactive Bounty Submission UI Flow + * + * Multi-step modal form for submitting bounty completion proof. + * Uses react-hook-form with useFieldArray for dynamic link management. + * Steps: Form → Confirmation → Success + */ +export function BountySubmitModal({ + isOpen, + onClose, + bountyTitle = 'Open Bounty', +}: BountySubmitModalProps): JSX.Element { + const [step, setStep] = useState('form'); + + const { + register, + control, + handleSubmit, + formState: { errors }, + watch: watchFn, + reset, + } = useForm({ + defaultValues: { + prUrl: '', + notes: '', + externalLinks: [{ url: '' }], + }, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: 'externalLinks', + }); + + // Watch values for confirmation step + const watchedPrUrl = watchFn('prUrl'); + const watchedNotes = watchFn('notes'); + const watchedLinks = watchFn('externalLinks'); + + const onSubmit = (data: BountySubmissionForm) => { + console.log('[BountySubmit] Submission data:', data); + setStep('confirm'); + }; + + const confirmSubmission = () => { + console.log('[BountySubmit] Confirmed and submitted!'); + setStep('success'); + }; + + const handleClose = () => { + setStep('form'); + reset(); + onClose(); + }; + + // Success state + if (step === 'success') { + return ( + +
+ +

+ Thank you! +

+

+ Your bounty submission has been received. Guild admins will review + your PR and get back to you soon. +

+ +
+
+ ); + } + + return ( + + {step === 'form' ? ( +
+ {/* GitHub PR URL */} +
+ + + {errors.prUrl && ( +

{errors.prUrl.message}

+ )} +
+ + {/* Additional Notes */} +
+ +