Spaces:
Sleeping
Sleeping
File size: 7,381 Bytes
8da7616 a733514 8da7616 a733514 8da7616 a733514 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | ---
title: Grabby Voice
colorFrom: green
colorTo: yellow
sdk: docker
app_port: 7860
fullWidth: true
pinned: false
---
# Matcha Moments β frontend
Cafe-aesthetic, mobile-first PWA that walks a customer through a 5-clip guided
video review and, on submit, hands them a matcha redemption code.
This repo is a **standalone Next.js app**. It calls Humeo's deployed public
review APIs (`https://humeo.app/api/public/reviews/*`) β no backend changes
needed in the Humeo monorepo for v1.
The original Humeo codebase lives at `reference/` for type / pattern lookups.
It's gitignored, so it never ships with this repo.
---
## Tech stack
- **Next.js 14** (App Router) + TypeScript + Tailwind CSS
- **`@ffmpeg/ffmpeg`** (ffmpeg.wasm) β client-side concatenation of the 5 recorded clips into one video before upload
- **`getUserMedia` + `MediaRecorder`** β standard browser camera APIs (no native install required)
- **`zod`** β shared validation schemas, mirrors the ones in Humeo's `src/lib/reviews/types.ts`
Why a PWA over Expo: the customer-facing flow has to start in a tabletop QR scan
in a cafe. Asking the customer to install an app kills conversion. Browser-based
flow opens in 2 seconds, no install, works on iOS Safari and Android Chrome.
---
## What it does (5 screens)
1. `/` β QR landing context screen (dev-only; in prod, the cafe's QR deep-links straight to `/c/[slug]`)
2. `/c/[slug]` β Cafe landing: brand, big "Free matcha, on the house" headline, consent copy, primary CTA
3. `/c/[slug]/record` β Guided 5-clip recorder (video preview β prompt card β record button β auto-advance)
4. `/preview` β ffmpeg.wasm stitches the clips, uploads to Humeo, polls submission status, shows the rendered preview
5. `/reward` β Confetti, reward code in a dark card, "show this screen to your server"
The `[slug]` route is a real Next.js dynamic segment that fetches its campaign
from `${NEXT_PUBLIC_HUMEO_API_URL}/api/public/reviews/campaign/[slug]`, the same
endpoint Humeo's existing public review flow already uses
(`reference/src/app/api/public/reviews/campaign/[slug]/route.ts`).
---
## Getting started
```bash
cp .env.example .env.local # then edit NEXT_PUBLIC_HUMEO_API_URL if needed
npm install
npm run dev # http://localhost:3000
```
### Test on your phone (recommended)
`getUserMedia` only works on `https://` (or `http://localhost`). Easiest path:
```bash
npx ngrok http 3000
```
Then open the `https://*.ngrok-free.app` URL on your phone, scan the QR or load
`/c/sageandstone` directly. iOS Safari and Android Chrome will prompt for
camera and mic. Allow both.
---
## How it talks to Humeo
```
matcha-moments PWA Humeo backend (deployed)
----------------- ------------------------
GET /c/[slug] βββββββΊ GET /api/public/reviews/campaign/[slug]
βββββββ { id, slug, restaurantName, rulesConfig, ... }
stitch clips locally (ffmpeg.wasm)
POST /preview submit βββββββΊ POST /api/public/reviews/submit
FormData: video, slug, consentAccepted,
deviceKey, durationSeconds, tableId
βββββββ { submissionId, status, decision, reward }
poll every 6s βββββββΊ GET /api/public/reviews/submission/[id]?slug=...
βββββββ { status, decision, feedback, reward }
```
`src/lib/humeoApi.ts` is the only place that fetches from `humeo.app`. If
Humeo's `review_campaigns` row doesn't yet have `mode` / `prompts` / `theme`
columns, `getCampaign()` augments the response with a hardcoded fallback
prompts list β flagged with `TODO` so we drop it once the BE migration ships.
### Fields Humeo's BE will eventually need
To remove the fallback, Humeo's `review_campaigns` schema would add:
- `mode text` β `'single_take' | 'guided_clips'`
- `prompts jsonb` β array of `{ step, title, tip, camera, maxSeconds }`
- `theme text` β `'default' | 'cafe-cream'`
Until then the matcha-moments app silently injects the cafe defaults.
---
## Why client-side ffmpeg.wasm?
Humeo's `/api/public/reviews/submit` accepts a single video file. We want a
multi-clip guided UX without forking Humeo's submit flow. Stitching the 5
recordings in the browser solves that with zero backend changes.
Trade-offs:
- 8MB WASM download, lazy-loaded only after the customer finishes recording
- 3-6 seconds of stitch time on a modern phone for ~50s of total video
- `next.config.js` sets COOP/COEP headers (required for `SharedArrayBuffer`)
If cafe staff start hearing complaints about phone heat, swap to a
multi-clip upload + server-side ffmpeg endpoint. Humeo's worker
(`reference/src/lib/server/processInterview.ts`) already uses ffmpeg, so the
migration is mostly a new submit endpoint.
---
## Project layout
```
src/
app/
layout.tsx Fonts (Fraunces / DM Sans / DM Mono), global CSS
page.tsx QR landing (dev-only)
c/[slug]/
page.tsx Server component β fetches campaign
LandingClient.tsx Cafe landing screen
record/
page.tsx Server component β fetches campaign
GuidedRecordingClient.tsx 5-clip guided recorder
preview/page.tsx Stitch + upload + preview
reward/page.tsx Reward code reveal
globals.css
components/ Button, MatchaCircle, ProgressPips, PromptCard,
RecordButton, RecordingBadge, RenderShimmer,
Confetti, RewardCard
hooks/
useGuidedRecording.ts getUserMedia + MediaRecorder + hard cap
useSubmissionPolling.ts Mirrors Humeo's PublicReviewRecordingClient polling
lib/
humeoApi.ts Typed fetch wrapper for /api/public/reviews/*
ffmpeg.ts ffmpeg.wasm concatClips() helper
recordingStore.ts In-tab clip store, useRecordingStore() hook
utils.ts cn(), ensureDeviceKey()
reviews/
types.ts Zod schemas + types (mirrors Humeo's)
public.ts Display helpers (verbatim from Humeo)
reference/ Humeo's repo (gitignored, read-only library)
matcha-moments-prototype_2.html Original wireframe (visual spec)
```
---
## Out of scope (v1)
- Per-clip re-record (current "re-record" wipes all 5 β flagged as a known
product call in `matcha-moments-prototype_2.html` dev notes)
- Email-me-a-copy of the final video
- Staff-side redemption screen
- Reward expiry / redemption tracking
- Native iOS/Android wrapping (Humeo can ship this as a Capacitor or PWA-installed shortcut later)
---
## Deploying
Vercel β connect this repo, set `NEXT_PUBLIC_HUMEO_API_URL=https://humeo.app`,
done. The COOP/COEP headers in `next.config.js` carry over on Vercel.
CORS: confirm `humeo.app` allow-lists this app's deploy domain
(e.g. `matcha.humeo.app`) on the `/api/public/reviews/*` routes.
|