Skip to main content

Reels Live Commerce gated

Short-form vertical video, Instagram-style. Mobile-first; web has a viewer but no upload (web app shows a referral gate then "use the mobile app to post").

Commerce mode is gated. Product/store tagging is hidden by default — controlled by REELS_COMMERCE_ENABLED env flag. See Feature Flags.

1. Overview

A consumer scrolls a vertical feed of 60-second videos. Tap to play, double-tap to like, swipe down for comments. Creators (consumers and sellers) upload from mobile only — must have 2 referrals to unlock posting (see Referral Gate). Each reel optionally has up to 5 product tags that link to store products or restaurants (currently disabled at launch). Engagement metrics are deduplicated per fingerprint to prevent inflation.

2. User journey

Viewer

  1. Open Reels tab (mobile) or /reels (web)
  2. Vertical scroll between reels (TikTok-style)
  3. Tap = pause; double-tap = like; tap "💬" = comments
  4. Tap creator avatar → their Reels Profile (read-only view)
  5. If commerce enabled: tap a product tag chip → opens product detail (or restaurant menu)

Creator (mobile only)

  1. Reels tab → "+" button
  2. Referral gate: must have 2+ verified referrals to unlock. If not, see "Invite friends to unlock" screen.
  3. Pick video from camera roll (max 100MB, max 60s)
  4. Edit video: trim with start/end markers
  5. Edit cover: pick frame, app generates 8 thumbnail candidates
  6. Details: caption (max 200 chars), tag products (gated by flag), select category
  7. Post: uploads to GCS via signed URL, creates Reel row, optionally creates ReelTag[]
  8. Visible in feed immediately

3. Web ↔ Mobile parity

CapabilityWebMobile
View feed/reels (vertical scroll)✅ Reels tab (TikTok-style)
Like
Comment
Share (WhatsApp deep link)
Report a reel
Upload❌ "Use mobile app" prompt✅ Full edit/cover/trim flow
Cover frame picker✅ 8 candidates from expo-video-thumbnails
Video trim✅ Start/end ms markers
Product tag in viewer🟡 Gated by REELS_COMMERCE_ENABLED🟡 Same gate
Tag products in post flow🟡 "Tag products" entry hidden when flag off

4. Key components

Web

  • src/app/(consumer)/reels/page.tsx — feed + viewer (single-component, ~1800 lines)
  • src/app/(consumer)/profile/reels/page.tsx — Reels Profile (own + others via ?consumerId=)

Mobile

  • mobile/app/(tabs)/reels.tsx — feed + viewer + full post flow (~3500 lines, the largest mobile file)
  • mobile/app/reels-profile.tsx — Reels Profile screen

Shared backend

  • src/app/api/reels/** — list, create, like, comment, view-track, tags, redirect-track

5. APIs

EndpointMethodPurpose
/api/reelsGETFeed (paginated, supports cursor, limit, search)
/api/reelsPOSTCreate reel (after video uploaded to GCS)
/api/reels/[id]GETSingle reel detail
/api/reels/[id]DELETEDelete own reel
/api/reels/[id]/likeGET, POSTGet like status / toggle like (deduplicated by fingerprint)
/api/reels/[id]/commentsGET, POSTComments
/api/reels/[id]/viewPOSTTrack view (deduplicated, weighted)
/api/reels/[id]/tagsGET, PUTGet tags / replace tags. PUT returns 503 when REELS_COMMERCE_ENABLED=false
/api/reels/[id]/redirectPOSTTrack product-redirect attribution (for creator earnings)
/api/upload-videoPOSTGet signed GCS upload URL

6. Database touchpoints

  • Reel — main entity. consumerId OR sellerId (creator), videoUrl, thumbnailUrl, caption, category, productId (legacy single-tag), restaurantId (legacy)
  • ReelTag — many-to-many: a reel can tag up to 5 products / menu items. Position field for ordering.
  • ReelLike, ReelComment, ReelView — engagement, all dedupe by fingerprint + consumerId to prevent inflation
  • ReelProductRedirect — every tap on a tagged product is logged here for creator earnings attribution
  • ReelEarning — accrued earnings; confirmed when the linked store order moves to delivered, reversed on cancelled

7. Business logic

Referral gate (creator unlock)

  • Constant REFERRAL_THRESHOLD = 2 in src/lib/constants.ts (changed from 3 on 2026-04-04 — see CHANGELOG)
  • Mobile checks referralInfo.canUpload before showing the post UI
  • Web shows the same gate before allowing upload (which is unsupported but the messaging is consistent)

Engagement deduplication

  • fingerprint is computed client-side (src/lib/fingerprint.ts) — combination of UA, screen, fonts, timezone
  • Same fingerprint can like once, view once (per N seconds)
  • Even if the same user is logged out then in, fingerprint persists → no inflation

Creator earnings

  • 1% commission on attributed orders (constant REEL_COMMISSION_RATE)
  • Attribution window: 24 hours from view-tap (REEL_ATTRIBUTION_WINDOW_MS)
  • Min product value: ₹100 (REEL_MIN_PRODUCT_VALUE_PAISE = 10000)
  • Max 5 tags per reel (REEL_MAX_TAGS)
  • Earnings are PENDING until the order is delivered, then CONFIRMED. Cancelled orders REVERSE the pending earnings.

Auto-moderation

  • Auto-hide a reel after 3 user reports (REEL_MAX_REPORTS_AUTO_HIDE)
  • Caption max 200 chars (REEL_MAX_CAPTION_LENGTH)
  • Duration max 180s (REEL_MAX_DURATION_SECONDS)

8. Feature flags / env vars

  • REELS_COMMERCE_ENABLED — gates product/store tagging UI + /api/reels/[id]/tags PUT (returns 503 when off). Default false. See Feature Flags.
  • Env: GCS_VIDEO_BUCKET for video storage (signed URL upload)

9. Tests

  • tests/build-integrity.test.ts — Reels API surface, schema fields
  • tests/offline-only-launch.test.ts — REELS_COMMERCE_ENABLED gate (server + viewer + post flow)
  • tests/web-mobile-parity-requests.test.ts — pattern reused for reels
  • mobile/tests/mobile-code-integrity.test.ts — Reels post flow features (cover picker, trim, etc.)

10. Known gotchas

  • Reel tags survive the flag toggle. If commerce gets disabled while reels have tags, the tags stay in DB — we just hide the UI and reject new writes. So flipping the flag back ON makes existing tags visible again.
  • Mobile viewer also filters out menuItem tags (Eats was archived). Code path: tags = allTags.filter(t => !t.menuItem).
  • Legacy single-product reels: older reels have Reel.productId instead of ReelTag[]. The viewer renders both forms (legacy single-product card OR multi-tag carousel).
  • GCS direct upload: video upload is from the mobile client directly to GCS (not through our API). Means the API doesn't see the video bytes — only the resulting URL.