Skip to content

10 — Submission Detail & Moderation (Screen 6, `/app/testimonials/:id`)

Cursor-ready plan: single-submission admin view with media preview, metadata, consent, actions, publish toggles, and merchandising controls.

10 — Submission Detail & Moderation (Screen 6)

Section titled “10 — Submission Detail & Moderation (Screen 6)”

Source: 02-Implementation-Blueprint.mdScreen 6 - Submission Detail (/app/testimonials/:id).

Product alignment: 01-Post-Purchase-Video-Testimonial-Collector-Plan.md§6 MVP (moderation queue, edit before publish, product mapping).

This document is a build spec only. No code changes are implied until a task references this file.

Prerequisite UI: 09-submissions-inbox-screen-5.md (list + navigation to this page).

Related: 08-security-compliance-and-privacy.md (moderation log, PII masking, consent), 05-storefront-widgets-and-read-api.md (only published content is public).


For one TestimonialSubmission id, the merchant sees full media preview, technical metadata, customer and order context, editable text/rating, consent evidence, runs approve / reject / flag / publish actions, and adjusts merchandising (product links, featured, sort) before content goes or stays live on the storefront.


ItemValue
Suggested fileapp/routes/app.testimonial-submission.$id.jsx (matches blueprint §6app.testimonial-submission.$id.jsx”).
URL/app/testimonials/:id where :id is TestimonialSubmission.id (UUID).

Auth: authenticate.admin(request); every loader/action must verify submission.shopId === currentShop.id404 if mismatch (do not leak existence across shops).

Navigation: list page (09) links here; optional breadcrumbs “Submissions → Detail”.


  • TestimonialSubmission by id + shopId, including:
    • status, mediaType, rating, headline, reviewText, displayName, displayEmail
    • consentAccepted, consentAcceptedAt, consentVersion
    • published, publishedAt (if columns exist per §5.7)
    • featured, sortOrder
    • shopifyProductId, shopifyVariantId, shopifyCustomerId, shopifyOrderId, requestId
  • TestimonialMediaAsset for this submission (include playbackUrl, thumbnailUrl, durationSec, width, height, fileSizeBytes, mimeType, processingStatus).
  • If multiple assets (future): list them; v1 assumes one primary asset per submission.
  • If not stored explicitly, derive from request payload metadata or add optional captureSource on submission/media in a migration — document chosen source. Placeholder: infer video + user agent from submission meta JSON if you add submission.metadata Json.
  • Join TestimonialRequest via requestId when present to show human-readable order reference (Shopify order name/number if stored, or shopifyOrderId).
  • TestimonialProductLink rows (§5.9) for “Assign products (multi)” secondary merchandising.
  • Recent TestimonialModerationLog rows for this submissionId (ordered createdAt desc), for an audit sidebar (08).

Use a two-column layout on desktop; stack on mobile.

  • Video: HTML5 <video> with controls, playsInline, poster={thumbnailUrl}; src = playbackUrl when processingStatus === ready; otherwise show processing Banner + thumbnail only.
  • Photo: <img> with src={playbackUrl || thumbnailUrl}.
  • Metadata card (from §5.8 + submission):
    • Duration (format mm:ss), resolution width×height, file size (human-readable), MIME type, processing status, Source (upload | record).
  • Customer: display name, email with optional masking (e.g. s***@domain.com) — configurable boolean from env or shop setting (08).
  • Order: string from request/order join; if missing, “—”.
  • Rating: Rating component (read-only in v1 or editable — see §4).
  • Headline / Quote: TextField multiline for quote; single line for headline.
  • Consent block (read-only):
    • Checkbox state (visual), Accepted at datetime, Consent version string.

Group Moderation actions in a Page primaryAction / secondaryActions or ActionList:

  • Approve
  • Reject (opens modal; reason required text)
  • Flag — set internal state for follow-up: either status transition to a flagged value or submission.flaggedAt + flagReason (add columns if not in §5.7; document migration).
  • Publish / Unpublish — toggles published + publishedAt (only allowed when status === approved if you enforce a state machine).

State machine (document in code):

  • Suggested: pendingapproved | rejected | flagged | archived; only approved can be published.

  • Headline, review text, rating — merchants may fix typos before approval.
  • Pattern: single “Save draft” secondary action that PATCHes only text fields while keeping status=pending, or auto-save with debounce (Phase 2). v1: explicit Save button.
  • Max lengths for headline/quote (align with public 06 limits).
  • Rating nullable vs required — match Screen 9 global rules when that exists; else optional.

Each destructive or state-changing intent must:

  1. Re-fetch submission with shopId lock.
  2. Apply transition rules (no illegal jumps).
  3. Create TestimonialModerationLog row (§5.10, 08): action, reason (reject), actorType=merchant, actorEmail from session.
IntentBehavior
approveSet status=approved; optionally set published per product rule (see §6 in 09 doc — recommend not auto-publish here if Screen 7 owns go-live).
rejectSet status=rejected; require reason; optional rejectionReason on submission for inbox display.
flagSet flagged state (see §3.3).
toggle_publishFlip published; when turning on, set publishedAt=now(); when off, clear publishedAt or keep history — pick one (usually keep publishedAt for analytics).
save_contentUpdate headline, reviewText, rating only.
save_merchandisingUpdate featured, sortOrder, primary shopifyProductId if allowed, and sync TestimonialProductLink (replace set).

Return toast + redirect or revalidate.


  • Default binding is TestimonialSubmission.shopifyProductId from the token (06). Allow reassign only if blueprint permits override — Screen 6 lists Assign products (multi).

Implementation:

  • Multi-select of products: use Shopify Resource Picker (App Bridge) or admin search against local Product table.
  • Persist:
    • Primary: keep first selected or explicit “primary” radio — store on submission shopifyProductId.
    • Additional: TestimonialProductLink rows (§5.9).
  • Featured — boolean featured on submission.
  • Sort prioritysortOrder integer; lower first or higher first — document sort direction used by 05 public API (align).

  • Storefront API (05): only shows submissions where published === true (and processing ready). Changing Publish here affects live site after cache/revalidation (usually immediate).
  • Screen 7 (Published Content): if you implement Screen 7 separately, avoid duplicate truth — either Screen 7 is the only place for placement toggles (PDP/home) or duplicate fields on submission — blueprint shows both; recommended: placement visibility lives on Screen 7 only; Screen 6 handles approve + publish + product assignment + featured.

Document in ticket: single source of truth for “visibility by placement” (Screen 7 vs fields on submission).


  • 404 — wrong id or other shop.
  • Processing media — disable Publish until TestimonialMediaAsset.processingStatus === ready (or allow publish with poster only — product decision).
  • Concurrent edits — optional updatedAt optimistic check; v1 can last-write-wins.

9) Testing checklist (acceptance criteria)

Section titled “9) Testing checklist (acceptance criteria)”
  • Cross-shop access returns 404.
  • Video plays when playbackUrl present; processing state shows banner when not ready.
  • Reject requires reason; creates moderation log; status becomes rejected.
  • Approve creates log; optional publish toggle works and affects 05 API visibility.
  • Save content updates text without changing status incorrectly.
  • Merchandising save updates featured, sortOrder, and product links.
  • Email masking honors setting when displaying displayEmail.

  1. Route file + loader with all includes (submission, media, request, links, logs).
  2. Page layout: media + metadata + customer/order + consent (read-only).
  3. Text fields + save_content action.
  4. Approve / reject / flag actions + modals + moderation logs.
  5. Publish toggle + merchandising (picker + featured + sort).
  6. Polish: breadcrumbs, back link to 09 list with preserved query string.

  • 02-Implementation-Blueprint.mdScreen 6, §5.7–5.10, §6 route map.
  • 09-submissions-inbox-screen-5.md — list navigation.
  • 08-security-compliance-and-privacy.md — audit log, PII.

Prior plans in this folder include 0509. This file is 10-….