From dc75003edcdf5b46b55b889b990498f978606e77 Mon Sep 17 00:00:00 2001 From: Dev Jaja Date: Wed, 25 Feb 2026 08:28:59 -0500 Subject: [PATCH 1/3] feat: implement submission draft system --- .../bounty-detail-submissions-card.tsx | 28 +++++- docs/SUBMISSION_DRAFTS_EXAMPLE.tsx | 76 +++++++++++++++++ hooks/__tests__/use-submission-draft.test.ts | 85 +++++++++++++++++++ hooks/use-submission-draft.ts | 47 ++++++++++ types/submission-draft.ts | 11 +++ 5 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 docs/SUBMISSION_DRAFTS_EXAMPLE.tsx create mode 100644 hooks/__tests__/use-submission-draft.test.ts create mode 100644 hooks/use-submission-draft.ts create mode 100644 types/submission-draft.ts diff --git a/components/bounty-detail/bounty-detail-submissions-card.tsx b/components/bounty-detail/bounty-detail-submissions-card.tsx index 40f95a4..407a442 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 } 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); @@ -55,8 +57,8 @@ export function BountyDetailSubmissionsCard({ useState(null); const [prUrl, setPrUrl] = useState(""); - const [submitComments, setSubmitComments] = useState(""); - const [reviewComments, setReviewComments] = useState(""); + const [submitComments, setSubmitComments] = useState(""); + const [reviewComments, setReviewComments] = useState(""); const [reviewStatus, setReviewStatus] = useState("APPROVED"); const [transactionHash, setTransactionHash] = useState(""); @@ -64,6 +66,20 @@ export function BountyDetailSubmissionsCard({ const reviewSubmission = useReviewSubmission(); const markSubmissionPaid = useMarkSubmissionPaid(); + // Load draft on mount + useEffect(() => { + if (draft?.formData) { + setPrUrl(draft.formData.githubPullRequestUrl); + setSubmitComments(draft.formData.comments); + } + }, [draft]); + + // Auto-save on form changes + useEffect(() => { + const cleanup = autoSave({ githubPullRequestUrl: prUrl, comments: submitComments }); + return cleanup; + }, [prUrl, submitComments, autoSave]); + const isOrgMember = (session?.user as ExtendedUser)?.organizations?.includes( bounty.organizationId, @@ -77,6 +93,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 +178,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..05fabf7 --- /dev/null +++ b/docs/SUBMISSION_DRAFTS_EXAMPLE.tsx @@ -0,0 +1,76 @@ +/** + * 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(""); + const [comments, setComments] = useState(""); + + // 1. Load draft when component mounts + useEffect(() => { + if (draft?.formData) { + setPrUrl(draft.formData.githubPullRequestUrl); + setComments(draft.formData.comments); + } + }, [draft]); + + // 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" + /> + +