diff --git a/apps/web/app/api/bounties/route.ts b/apps/web/app/api/bounties/route.ts index 6562ebd..fdd17d0 100644 --- a/apps/web/app/api/bounties/route.ts +++ b/apps/web/app/api/bounties/route.ts @@ -30,7 +30,7 @@ export async function POST(req: NextRequest) { const did = await getSessionDid(); if (!did) return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); - const { title, description, reward_usd, store_id, store_name } = await req.json(); + const { title, description, url, reward_usd, store_id, store_name } = await req.json(); if (!title?.trim()) return NextResponse.json({ error: 'Title is required' }, { status: 400 }); const reward = parseFloat(reward_usd); @@ -43,7 +43,7 @@ export async function POST(req: NextRequest) { // Insert bounty as 'open' with a non-sequential public id for URLs. await db.sql` - INSERT INTO bounties (public_id, creator_did, store_id, store_name, title, description, reward_usd, status) + INSERT INTO bounties (public_id, creator_did, store_id, store_name, title, description, url, reward_usd, status) VALUES ( ${publicId}, ${did}, @@ -51,6 +51,7 @@ export async function POST(req: NextRequest) { ${store_name?.trim() ?? null}, ${title.trim()}, ${description?.trim() ?? null}, + ${url?.trim() || null}, ${reward}, 'open' ) diff --git a/apps/web/app/api/coupons/route.ts b/apps/web/app/api/coupons/route.ts index d99fb4e..814bdf3 100644 --- a/apps/web/app/api/coupons/route.ts +++ b/apps/web/app/api/coupons/route.ts @@ -95,12 +95,9 @@ export async function POST(req: NextRequest) { image_url, } = body; - if (!url) { - return NextResponse.json({ error: 'url is required' }, { status: 400 }); - } if (!title) { return NextResponse.json( - { error: 'title is required (scrape the URL first)' }, + { error: 'title is required' }, { status: 400 } ); } @@ -125,7 +122,7 @@ export async function POST(req: NextRequest) { ) VALUES ( ${storeId}, ${code?.trim() || null}, ${title}, ${description ?? null}, ${discount}, - ${type}, ${value}, ${expiry_date || null}, ${url}, ${image_url ?? null} + ${type}, ${value}, ${expiry_date || null}, ${url?.trim() || null}, ${image_url ?? null} ) `; return NextResponse.json({ success: true }, { status: 201 }); diff --git a/apps/web/app/bounties/[id]/page.tsx b/apps/web/app/bounties/[id]/page.tsx index d4f0ba2..65bd6b6 100644 --- a/apps/web/app/bounties/[id]/page.tsx +++ b/apps/web/app/bounties/[id]/page.tsx @@ -12,6 +12,7 @@ interface Bounty { public_id: string; title: string; description: string | null; + url: string | null; reward_usd: number; status: string; store_id: number | null; @@ -103,6 +104,18 @@ export default async function BountyPage({ params }: { params: Promise<{ id: str

{bounty.description}

)} + {/* Reference link */} + {bounty.url && ( + + {bounty.url} ↗ + + )} + {/* Claimed coupon */} {bounty.coupon_code && (
diff --git a/apps/web/app/bounties/new/page.tsx b/apps/web/app/bounties/new/page.tsx index a04a803..859b433 100644 --- a/apps/web/app/bounties/new/page.tsx +++ b/apps/web/app/bounties/new/page.tsx @@ -16,6 +16,7 @@ export default function NewBountyPage() { const [form, setForm] = useState({ title: '', description: '', + url: '', reward_usd: '0.10', store_id: '', store_name: '', @@ -43,6 +44,7 @@ export default function NewBountyPage() { body: JSON.stringify({ title: form.title.trim(), description: form.description.trim() || null, + url: form.url.trim() || null, reward_usd: reward, store_id: form.store_id ? parseInt(form.store_id) : null, store_name: !form.store_id ? form.store_name.trim() : null, @@ -138,6 +140,13 @@ export default function NewBountyPage() { rows={3} className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-orange-400 resize-none" /> + set('url', e.target.value)} + className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-orange-400" + />
diff --git a/apps/web/app/submit/page.tsx b/apps/web/app/submit/page.tsx index 4c163de..33e1168 100644 --- a/apps/web/app/submit/page.tsx +++ b/apps/web/app/submit/page.tsx @@ -79,12 +79,8 @@ export default function SubmitPage() { e.preventDefault(); setError(''); - if (!url.trim()) { - setError('A product URL is required.'); - return; - } if (!scraped || !scraped.title.trim()) { - setError('Fetch the listing details first (or add a title).'); + setError('Add a title (fetch it from a link, or enter details manually).'); return; } @@ -94,7 +90,7 @@ export default function SubmitPage() { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - url: url.trim(), + url: url.trim() || null, code: code.trim() || null, discount_type: discountValue.trim() ? discountType : null, discount_value: discountValue.trim() || null, @@ -137,13 +133,13 @@ export default function SubmitPage() {

Submit a Coupon

- Paste the product link and your coupon code — we'll pull the listing details for you. + Paste a product link to auto-fill the details — or enter them manually. The link is optional.

{/* Product URL + fetch */}
-

Product link

+

Product link (optional)

setUrl(e.target.value)} className={`flex-1 ${inputClass}`} - required /> + )}
{/* AI-scraped, editable preview */} diff --git a/apps/web/scripts/migrate.mjs b/apps/web/scripts/migrate.mjs index 37ae68d..3ba80b3 100644 --- a/apps/web/scripts/migrate.mjs +++ b/apps/web/scripts/migrate.mjs @@ -85,6 +85,7 @@ await db.sql` await addColumn(() => db.sql`ALTER TABLE coupons ADD COLUMN discount_type TEXT`); await addColumn(() => db.sql`ALTER TABLE coupons ADD COLUMN discount_value REAL`); await addColumn(() => db.sql`ALTER TABLE coupons ADD COLUMN image_url TEXT`); +await addColumn(() => db.sql`ALTER TABLE coupons ADD COLUMN url TEXT`); console.log(' coupons'); await db.sql` @@ -133,6 +134,7 @@ await db.sql` store_name TEXT, title TEXT NOT NULL, description TEXT, + url TEXT, reward_usd REAL NOT NULL, status TEXT NOT NULL DEFAULT 'open', payment_id TEXT, @@ -144,6 +146,8 @@ await db.sql` `; // public_id: non-sequential URL identifier (added after initial release). await addColumn(() => db.sql`ALTER TABLE bounties ADD COLUMN public_id TEXT`); +// url: optional reference link for the bounty (added after initial release). +await addColumn(() => db.sql`ALTER TABLE bounties ADD COLUMN url TEXT`); // Backfill any rows missing a public_id (existing bounties created pre-migration). const needIds = await db.sql`SELECT id FROM bounties WHERE public_id IS NULL OR public_id = ''`; for (const row of needIds) {