Open the Quest log sheet from the Quest log pill
CI / ci (push) Successful in 2m57s
CI / ci (pull_request) Successful in 2m39s

Tapping "Quest log · N" opens a bottom sheet listing the Quest log, each
dinner with its 48 px washed photo, name, difficulty and minutes. A row
opens /dinner/:id; its remove button asks to confirm, then archives the
dinner (DELETE /api/v1/dinners/{id}) and takes it off the count. An empty
Quest log says "Your Quest log is empty — swipe right on a side quest to
add one." The sheet replaces the pill's interim name list.

The Quest log is the dinners accepted and not yet scheduled, so GET
/api/v1/dinners?status=quest_log now leaves out a dinner on the plan for
the household's current week (by its timezone) or a later week, read
from week_plans and plan_days. The pill's count and the sheet both come
from that one read.

Task: 5ad0b188-176e-4107-91dd-5d667ca6a4d2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 04:12:05 -04:00
co-authored by Claude Opus 5.5
parent a866dfad06
commit f4075d15b8
14 changed files with 524 additions and 92 deletions
+9 -22
View File
@@ -1,8 +1,7 @@
import { useQueryClient } from '@tanstack/react-query'
import { Heart, X } from 'lucide-react'
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type PointerEvent, type Ref } from 'react'
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type PointerEvent, type Ref } from 'react'
import { useJob } from '../api/index.ts'
import { Card } from '../ui/Card.tsx'
import { EmptyState } from '../ui/EmptyState.tsx'
import { ErrorBanner } from '../ui/ErrorBanner.tsx'
import { Icon } from '../ui/Icon.tsx'
@@ -19,6 +18,7 @@ import {
useQuestLog,
useStartBatch,
type Choice,
type QuestLogDinner,
type SideQuest,
} from './api.ts'
import { DeckCard, DECK_DEPTH, type SwipeHandlers } from './DeckCard.tsx'
@@ -35,6 +35,7 @@ import {
unfollow,
type Exit,
} from './motion.ts'
import { QuestLogSheet } from './QuestLogSheet.tsx'
import styles from './DiscoverPage.module.css'
// SWIPE_THRESHOLD is how far, in px, the top card must be dragged left or
@@ -54,36 +55,22 @@ function isEditable(target: EventTarget | null): boolean {
)
}
// QuestLogPill is the "Quest log · N" pill. It shows and hides the names
// of the dinners in the Quest log; the Quest log sheet replaces this list.
// ref is the visible pill, which Try it bumps.
function QuestLogPill({ dinners, ref }: { dinners: { id: string; name: string }[]; ref: Ref<HTMLSpanElement> }) {
// QuestLogPill is the "Quest log · N" pill, which opens the Quest log
// sheet. ref is the visible pill, which Try it bumps.
function QuestLogPill({ dinners, ref }: { dinners: QuestLogDinner[]; ref: Ref<HTMLSpanElement> }) {
const [open, setOpen] = useState(false)
const listId = useId()
return (
<>
<button
type="button"
className={styles.questLogButton}
aria-haspopup="dialog"
aria-expanded={open}
aria-controls={open ? listId : undefined}
onClick={() => setOpen(!open)}
onClick={() => setOpen(true)}
>
<span ref={ref} className={styles.questLog}>Quest log · {dinners.length}</span>
</button>
{open && (
<Card id={listId} tone="muted" className={styles.questLogList}>
{dinners.length === 0 ? (
<p className={styles.questLogEmpty}>No dinners in the Quest log yet. Try it on a Side quest to add one.</p>
) : (
<ul aria-label="Quest log" className={styles.questLogNames}>
{dinners.map((dinner) => (
<li key={dinner.id}>{dinner.name}</li>
))}
</ul>
)}
</Card>
)}
<QuestLogSheet open={open} dinners={dinners} onClose={() => setOpen(false)} />
</>
)
}