/** Daily check-in in plain English; maps calm labels onto backend enums. */ import { X } from "lucide-preact"; import { useEffect, useState } from "preact/hooks"; import { getDaily, putDaily, type DailyRow, type DailyUpsert, type PrimaryBrick, type ProofBrickKind, } from "../api"; import { Button } from "../components/Button"; import { Pressable } from "../components/Pressable"; import { useToast } from "../components/Toast"; import { isoDate } from "../dates"; const EMPTY: DailyUpsert = { primary_brick: "none", brick_done: false, corn_sessions: 0, delay_ok: true, daydream: "none", rerun: "clean", court: "closed", stayed_indoors_all_day: false, left_room: false, left_home: false, movement_minutes: 0, interrupt_walk_minutes: 0, fantasy_walk_minutes: 0, headphones_on_walk: false, music_cinematic_on_walk: false, proof_brick_done: false, proof_brick_kind: null, fantasy_minutes_scheduled: 0, fantasy_minutes_unplanned: 0, note: "", win: "", }; const PROOF_KINDS: { value: ProofBrickKind; label: string }[] = [ { value: "interrupt_walk", label: "Interrupt walk" }, { value: "body_or_room", label: "Body or room" }, { value: "trip_admin", label: "Trip admin" }, { value: "earn", label: "Earn" }, { value: "boundary", label: "Boundary" }, { value: "food", label: "Food" }, { value: "survive", label: "Survive" }, { value: "other", label: "Other" }, ]; const BRICK_OPTIONS: { value: PrimaryBrick; label: string }[] = [ { value: "A", label: "Admin / trip task" }, { value: "B", label: "Body or room care" }, { value: "C", label: "Work / skill" }, { value: "D", label: "Home exit plan" }, { value: "E", label: "Held a boundary" }, { value: "S", label: "Just survived the day" }, { value: "none", label: "Nothing yet" }, ]; const DAYDREAM_OPTIONS: { value: DailyUpsert["daydream"]; label: string }[] = [ { value: "none", label: "No" }, { value: "done", label: "Yes, and I still did one real step" }, { value: "fc", label: "Yes, and I didn’t do a real step" }, ]; const RERUN_OPTIONS: { value: DailyUpsert["rerun"]; label: string }[] = [ { value: "clean", label: "I cut it short or didn’t go there" }, { value: "R", label: "I stayed stuck in it" }, ]; const COURT_OPTIONS: { value: DailyUpsert["court"]; label: string }[] = [ { value: "closed", label: "I closed it" }, { value: "court", label: "I kept prosecuting the case" }, ]; const TIP_KEY = "daily-tip-dismissed"; function ChoiceList({ options, value, onChange, }: { options: { value: T; label: string }[]; value: T; onChange: (v: T) => void; }) { return (
{options.map((option) => ( onChange(option.value)} > {option.label} ))}
); } function Stepper({ value, onChange, }: { value: number; onChange: (v: number) => void; }) { return (
onChange(Math.max(0, value - 1))} > − {value >= 3 ? `${value}` : value} = 10} onClick={() => onChange(Math.min(10, value + 1))} > +
); } function friendlyDate(day: string): string { return new Date(`${day}T00:00:00`).toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric", }); } function breakdown(row: DailyRow): { label: string; earned: boolean; pts: number }[] { const cornOk = row.corn_sessions === 0 || (row.corn_sessions <= 1 && row.delay_ok); return [ { label: "Did the main action", earned: row.brick_done, pts: 2 }, { label: "No escapes — or just one, after waiting", earned: cornOk, pts: 1, }, { label: "Fantasy didn’t replace a real step", earned: row.daydream !== "fc", pts: 1, }, { label: "Cut the mental replay short", earned: row.rerun === "clean", pts: 1 }, { label: "Kept the case closed", earned: row.court === "closed", pts: 1 }, ]; } export function Daily() { const toast = useToast(); const [day, setDay] = useState(isoDate(new Date())); const [form, setForm] = useState(EMPTY); const [saved, setSaved] = useState(null); const [busy, setBusy] = useState(false); const [tipHidden, setTipHidden] = useState( () => localStorage.getItem(TIP_KEY) === "1", ); const load = async (date: string) => { setSaved(null); try { const row = await getDaily(date); setForm({ primary_brick: row.primary_brick, brick_done: row.brick_done, corn_sessions: row.corn_sessions, delay_ok: row.delay_ok, daydream: row.daydream, rerun: row.rerun, court: row.court, stayed_indoors_all_day: Boolean(row.stayed_indoors_all_day), left_room: Boolean(row.left_room), left_home: Boolean(row.left_home), movement_minutes: row.movement_minutes ?? 0, interrupt_walk_minutes: row.interrupt_walk_minutes ?? 0, fantasy_walk_minutes: row.fantasy_walk_minutes ?? 0, headphones_on_walk: Boolean(row.headphones_on_walk), music_cinematic_on_walk: Boolean(row.music_cinematic_on_walk), proof_brick_done: Boolean(row.proof_brick_done), proof_brick_kind: row.proof_brick_kind ?? null, fantasy_minutes_scheduled: row.fantasy_minutes_scheduled ?? 0, fantasy_minutes_unplanned: row.fantasy_minutes_unplanned ?? 0, note: row.note, win: row.win ?? "", }); setSaved(row); } catch { setForm(EMPTY); } }; useEffect(() => { void load(day); }, [day]); const save = async () => { setBusy(true); try { const row = await putDaily(day, form); setSaved(row); toast.show("Saved"); } catch (e) { toast.show(e instanceof Error ? e.message : "Save failed", "error"); } finally { setBusy(false); } }; return (

Today

{friendlyDate(day)}
{saved ? `${saved.points}/6` : "–/6"}
{!tipHidden && (

This page is your day’s scoreboard. Use Log for what happened and what you tried.

{ localStorage.setItem(TIP_KEY, "1"); setTipHidden(true); }} >
)}
What was today’s one real thing? setForm({ ...form, primary_brick })} />
Today’s win

One thing that went well — a real step, a kind moment, anything. Optional. This is the part that compounds.

setForm({ ...form, win: (e.target as HTMLInputElement).value }) } />
Escapes How many times did you use porn/masturbation to escape today? setForm({ ...form, corn_sessions })} /> {form.corn_sessions >= 1 && ( <> Before that, did you wait about 10 minutes and do one small real task first? setForm({ ...form, delay_ok: v === "yes" })} /> )}

Rough day? Just save the basics below — the extra detail can wait.

More detail (optional)
Did you drift into fantasy today? setForm({ ...form, daydream })} />
Replaying painful scenes? setForm({ ...form, rerun })} /> Arguing the family case in your head? setForm({ ...form, court })} />
Movement Did you stay indoors the whole day? setForm({ ...form, stayed_indoors_all_day: v === "yes" }) } /> Did you leave the bedroom / room? setForm({ ...form, left_room: v === "yes" })} /> Did you leave the house/compound? setForm({ ...form, left_home: v === "yes" })} /> Headphones on a walk? setForm({ ...form, headphones_on_walk: v === "yes" })} /> Cinematic / movie music on a walk? setForm({ ...form, music_cinematic_on_walk: v === "yes" }) } />

Compound laps and short walks count — no spend needed. Total moving:{" "} {form.interrupt_walk_minutes + form.fantasy_walk_minutes}m

Proof brick Did you do one real proof step today? setForm({ ...form, proof_brick_done: v === "yes", proof_brick_kind: v === "yes" ? form.proof_brick_kind : null, }) } /> {form.proof_brick_done && ( <> What kind? setForm({ ...form, proof_brick_kind }) } /> )}
Movie / fantasy time

Containment beats guilt. Log honestly.

{saved && (
Points: {saved.points}/6
    {breakdown(saved).map((item) => (
  • {item.label} {item.earned ? `+${item.pts}` : "0"}
  • ))}
)}
How points work
  • Doing your one real thing earns 2 points.
  • No escape sessions — or a single one after the 10-minute wait — earns 1 point.
  • Fantasy is fine if you still did a real step: 1 point.
  • Cutting mental replays short earns 1 point.
  • Keeping the family case closed earns 1 point.
); }