spb/worthdoing Public
Autonomous investigation agent that discovers, challenges, and ranks things genuinely worth doing — Claude + Firecrawl, Next.js 16, PostgreSQL
TypeScript 91.5%
SQL 5.8%
CSS 2.2%
1/**2 * WorthDoing.ai3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: src/components/wd/ObjectiveForm.tsx6 * Description: Discover-page objective input — creates an investigation and routes to the live view.7 */8"use client";910import { useState } from "react";11import { useRouter } from "next/navigation";1213const SUGGESTIONS = [14 "Find things worth doing in local AI",15 "What's worth building for independent booksellers?",16 "Underexplored opportunities in home energy monitoring",17 "What should exist for parents of kids with ADHD?",18];1920export function ObjectiveForm() {21 const router = useRouter();22 const [objective, setObjective] = useState("");23 const [submitting, setSubmitting] = useState(false);24 const [error, setError] = useState<string | null>(null);2526 const start = async (text: string) => {27 if (submitting || text.trim().length < 8) return;28 setSubmitting(true);29 setError(null);30 try {31 const res = await fetch("/api/investigations", {32 method: "POST",33 headers: { "Content-Type": "application/json" },34 body: JSON.stringify({ objective: text.trim() }),35 });36 const data = await res.json();37 if (!res.ok) throw new Error(data.error ?? "Failed to start the investigation.");38 router.push(`/investigate/${data.id}`);39 } catch (e) {40 setError(e instanceof Error ? e.message : "Something went wrong.");41 setSubmitting(false);42 }43 };4445 return (46 <div className="w-full">47 <form48 onSubmit={(e) => {49 e.preventDefault();50 void start(objective);51 }}52 className="flex flex-col gap-2 sm:flex-row"53 >54 <input55 value={objective}56 onChange={(e) => setObjective(e.target.value)}57 placeholder="What domain should the agent investigate?"58 maxLength={500}59 className="min-h-[52px] flex-1 rounded-xl border border-line bg-card px-4 text-[15px] text-ink shadow-sm outline-none transition-colors placeholder:text-ink-soft/60 focus:border-verdict focus:ring-2 focus:ring-verdict/20"60 />61 <button62 type="submit"63 disabled={submitting || objective.trim().length < 8}64 className="min-h-[52px] rounded-xl bg-ink px-6 text-[15px] font-medium text-paper transition-opacity hover:opacity-85 disabled:opacity-40"65 >66 {submitting ? "Launching…" : "Investigate"}67 </button>68 </form>69 {error && <p className="mt-2 text-sm text-rust">{error}</p>}70 <div className="mt-4 flex flex-wrap gap-2">71 {SUGGESTIONS.map((s) => (72 <button73 key={s}74 type="button"75 disabled={submitting}76 onClick={() => {77 setObjective(s);78 void start(s);79 }}80 className="min-h-[44px] rounded-full border border-line bg-card px-4 py-2 text-left text-[13px] text-ink-soft transition-colors hover:border-verdict/50 hover:text-ink disabled:opacity-40"81 >82 {s}83 </button>84 ))}85 </div>86 </div>87 );88}89