From d07230a0c4e641976fc34fee7aae5fe6f564ef65 Mon Sep 17 00:00:00 2001 From: Jared Schoeny Date: Sat, 22 Aug 2026 10:26:29 -0600 Subject: [PATCH] Require hack report details and collect optional contact email (#75) Details are now required for every report type. Non-stolen reports can include an optional email so staff can follow up; stolen reports still require email. Co-authored-by: Cursor Agent --- src/app/hack/[slug]/actions.ts | 32 +++++------ src/components/Hack/ReportModal.tsx | 86 ++++++++++++++--------------- 2 files changed, 57 insertions(+), 61 deletions(-) diff --git a/src/app/hack/[slug]/actions.ts b/src/app/hack/[slug]/actions.ts index e08a270..c643802 100644 --- a/src/app/hack/[slug]/actions.ts +++ b/src/app/hack/[slug]/actions.ts @@ -356,8 +356,8 @@ export async function submitHackReport(data: { return { error: "Hack not found" }; } - // Validate email if provided (for stolen reports) - if (data.reportType === "stolen" && data.email) { + // Validate email if provided + if (data.email?.trim()) { const emailLower = data.email.trim().toLowerCase(); const { error: emailError } = validateEmail(emailLower); if (emailError) { @@ -366,17 +366,12 @@ export async function submitHackReport(data: { } // Validate required fields - if (data.reportType === "misleading" && !data.details?.trim()) { - return { error: "Details are required for misleading reports" }; + if (data.reportType === "stolen" && !data.email?.trim()) { + return { error: "Email is required for stolen hack reports" }; } - if (data.reportType === "stolen") { - if (!data.email?.trim()) { - return { error: "Email is required for stolen hack reports" }; - } - if (!data.details?.trim()) { - return { error: "Details are required for stolen hack reports" }; - } + if (!data.details?.trim()) { + return { error: "Details are required for hack reports" }; } // Build hack URL @@ -417,14 +412,15 @@ export async function submitHackReport(data: { }); } + if (data.email?.trim()) { + fields.push({ + name: "Contact Email", + value: data.email.trim().toLowerCase(), + inline: false, + }); + } + if (data.reportType === "stolen") { - if (data.email) { - fields.push({ - name: "Contact Email", - value: data.email.trim().toLowerCase(), - inline: false, - }); - } if (data.isImpersonating !== null) { fields.push({ name: "Is Uploader Impersonating?", diff --git a/src/components/Hack/ReportModal.tsx b/src/components/Hack/ReportModal.tsx index a8400da..6f34a25 100644 --- a/src/components/Hack/ReportModal.tsx +++ b/src/components/Hack/ReportModal.tsx @@ -45,22 +45,15 @@ const ReportModal: React.FC = ({ slug, onClose }) => { const canSubmit = () => { if (!reportType) return false; - if (reportType === "stolen") { - // Stolen requires email and details - if (!email.trim() || !details.trim()) return false; + if (!details.trim()) return false; + + if (email.trim()) { // Basic email validation (matches server-side validation pattern) const emailRegex = /^(?!\.)(?!.*\.\.)([a-z0-9_'+\-\.]*)[a-z0-9_'+\-]@([a-z0-9][a-z0-9\-]*\.)+[a-z]{2,}$/; if (!emailRegex.test(email.trim().toLowerCase())) return false; - return true; } - if (reportType === "misleading") { - // Misleading requires details - return details.trim().length > 0; - } - - // Hateful and Harassment are optional, so can always submit - return true; + return reportType !== "stolen" || !!email.trim(); }; const handleSubmit = async () => { @@ -74,7 +67,7 @@ const ReportModal: React.FC = ({ slug, onClose }) => { slug, reportType, details: details.trim() || null, - email: reportType === "stolen" ? email.trim() : null, + email: email.trim() || null, isImpersonating: reportType === "stolen" ? isImpersonating : null, }); @@ -174,9 +167,6 @@ const ReportModal: React.FC = ({ slug, onClose }) => { const renderDetailsPage = () => { const isStolen = reportType === "stolen"; - const isMisleading = reportType === "misleading"; - const requiresDetails = isStolen || isMisleading; - const isOptional = reportType === "hateful" || reportType === "harassment"; return (
@@ -190,45 +180,46 @@ const ReportModal: React.FC = ({ slug, onClose }) => {

{isStolen ? "Please provide your contact information and details about the stolen hack." - : isOptional - ? "Please provide additional details (optional)." : "Please provide additional details."}

{isStolen && ( - <> -
- -
- -
- +
+
- + Is the uploader impersonating you? + +
)}
+ setEmail(e.target.value)} + placeholder="your@email.com" + required={isStolen} + className="w-full px-3 py-2 rounded-md border border-[var(--border)] bg-[var(--surface-1)] text-sm focus:outline-none focus:ring-2 focus:ring-[var(--accent)]" + /> +
+ +
+