diff --git a/components/bounty-detail/bounty-detail-submissions-card.tsx b/components/bounty-detail/bounty-detail-submissions-card.tsx index 8fdd99e..8d5992c 100644 --- a/components/bounty-detail/bounty-detail-submissions-card.tsx +++ b/components/bounty-detail/bounty-detail-submissions-card.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect, useRef } from "react"; import { Loader2, DollarSign } from "lucide-react"; import { Button } from "@/components/ui/button"; import { @@ -21,6 +21,7 @@ import { useMarkSubmissionPaid, } from "@/hooks/use-submission-mutations"; import { authClient } from "@/lib/auth-client"; +import { useSubmissionDraft } from "@/hooks/use-submission-draft"; interface ExtendedUser { id: string; @@ -44,6 +45,7 @@ export function BountyDetailSubmissionsCard({ }: BountyDetailSubmissionsCardProps) { const { data: session } = authClient.useSession(); const submissions = bounty.submissions || []; + const { draft, clearDraft, autoSave } = useSubmissionDraft(bounty.id); const [submitDialogOpen, setSubmitDialogOpen] = useState(false); const [reviewDialogOpen, setReviewDialogOpen] = useState(false); @@ -64,6 +66,24 @@ export function BountyDetailSubmissionsCard({ const reviewSubmission = useReviewSubmission(); const markSubmissionPaid = useMarkSubmissionPaid(); + const hasHydratedDraft = useRef(false); + + // Load draft on mount + useEffect(() => { + if (draft?.formData) { + setPrUrl(draft.formData.githubPullRequestUrl); + setSubmitComments(draft.formData.comments); + } + hasHydratedDraft.current = true; + }, [draft]); + + // Auto-save on form changes + useEffect(() => { + if (!hasHydratedDraft.current) return; + const cleanup = autoSave({ githubPullRequestUrl: prUrl, comments: submitComments }); + return cleanup; + }, [prUrl, submitComments, autoSave]); + const isOrgMember = (session?.user as ExtendedUser)?.organizations?.includes( bounty.organizationId, @@ -77,6 +97,7 @@ export function BountyDetailSubmissionsCard({ githubPullRequestUrl: prUrl, comments: submitComments.trim() || undefined, }); + clearDraft(); } catch (err) { // Replace with toast or error UI as needed console.error("Submit PR failed:", err); @@ -161,6 +182,11 @@ export function BountyDetailSubmissionsCard({ Submit Pull Request Submit your GitHub pull request URL. + {draft && ( + + Draft restored from {new Date(draft.updatedAt).toLocaleString()} + + )} diff --git a/docs/SUBMISSION_DRAFTS_EXAMPLE.tsx b/docs/SUBMISSION_DRAFTS_EXAMPLE.tsx new file mode 100644 index 0000000..cb5c256 --- /dev/null +++ b/docs/SUBMISSION_DRAFTS_EXAMPLE.tsx @@ -0,0 +1,68 @@ +/** + * Submission Draft System - Quick Start Example + * + * This example shows how to integrate the submission draft system + * into any form component. + */ + +import { useState, useEffect } from "react"; +import { useSubmissionDraft } from "@/hooks/use-submission-draft"; + +export function SubmissionFormExample({ bountyId }: { bountyId: string }) { + const { draft, clearDraft, autoSave } = useSubmissionDraft(bountyId); + + const [prUrl, setPrUrl] = useState(draft?.formData.githubPullRequestUrl || ""); + const [comments, setComments] = useState(draft?.formData.comments || ""); + + // 2. Auto-save when form changes + useEffect(() => { + const cleanup = autoSave({ + githubPullRequestUrl: prUrl, + comments + }); + return cleanup; + }, [prUrl, comments, autoSave]); + + // 3. Clear draft on successful submit + const handleSubmit = async () => { + // Your submit logic here + await submitToAPI({ prUrl, comments }); + + // Clear draft after success + clearDraft(); + + // Reset form + setPrUrl(""); + setComments(""); + }; + + return ( +
+ {/* Show draft indicator */} + {draft && ( +
+ Draft saved at {new Date(draft.updatedAt).toLocaleString()} +
+ )} + + setPrUrl(e.target.value)} + placeholder="Pull Request URL" + /> + +