diff --git a/src/app/api/slack/interactive/route.ts b/src/app/api/slack/interactive/route.ts index a622ccd..08f0689 100644 --- a/src/app/api/slack/interactive/route.ts +++ b/src/app/api/slack/interactive/route.ts @@ -6,6 +6,8 @@ import { slackUserForParticipant, } from "@/lib/slack-verify"; import { nudgeBlocks, detailsModal, partnerNudgeBlocks } from "@/lib/slack-blocks"; +import { handleIntakeAction, isIntakeAction, parseActionValue } from "@/lib/intake-actions"; +import { pantryForwarder } from "@/lib/intake-pantry"; import { completeSplit } from "@/lib/splits"; import { RATINGS, @@ -73,6 +75,22 @@ export async function POST(req: NextRequest) { if (payload.type !== "block_actions") return new NextResponse(null, { status: 200 }); const action = payload.actions?.[0]; + + // Intake taps (plan ยง11 C5) act on a persisted batch, not a transaction, so + // their `value` is JSON `{batch_id, ...}` rather than `:`. They are + // dispatched on action_id before the order-nudge parse below, which would + // otherwise read the JSON as a non-numeric transaction id and give up. + if (isIntakeAction(action?.action_id)) { + const reply = await handleIntakeAction(action.action_id, parseActionValue(action?.value), { pantry: pantryForwarder() }); + await updateMessage( + payload.response_url, + reply.replace + ? { replace_original: true, blocks: reply.blocks, text: reply.text } + : { response_type: "ephemeral", replace_original: false, text: reply.text } + ); + return NextResponse.json({ blocks: reply.blocks, text: reply.text }); + } + // A button carries `value`; a select carries it on the chosen option. The // ratings moved to a select because four buttons became four full-width rows // on Slack mobile, so both shapes have to resolve or rating silently stops