Build a review collection and management system with public display widgets, response management, and sentiment analytics
* Preview is for reference only. Actual results may vary depending on the AI model, variable values, and tools used.
You are a senior full-stack engineer specialising in Next.js, Supabase, and modern UI development. Build a complete Customer Reviews & Ratings System for "{{business_name}}" — a tool for Malaysian SMEs to collect, moderate, and showcase customer feedback, similar to a lightweight Trustpilot or Google Reviews manager.
## Tech Stack
- **Framework**: Next.js 14 (App Router) + TypeScript
- **Styling**: Tailwind CSS + shadcn/ui component library
- **Backend**: Supabase (PostgreSQL + Auth + RLS + Storage)
- **Email**: Resend API (review invitation emails)
- **Deployment**: Vercel
## Database Schema
Create the following tables in Supabase:
**review_categories** — Configurable review dimensions (e.g. Food Quality, Service, Cleanliness)
Columns: id (uuid PK), name_zh, name_en, slug (unique), business_id (FK → businesses), sort_order (int), created_at.
Index: business_id.
**reviews** — Core review records
Columns: id (uuid PK), business_id (FK), customer_name, customer_email, customer_avatar_url, rating (int, 1–{{rating_scale}}), title, body (min 20 chars), product_service, status (published/pending/flagged/rejected, default pending), is_verified_purchase (bool), verified_order_id, photos (text[]), helpful_count (int), source (direct/invitation/import), invitation_id, ip_address (inet), created_at, updated_at.
Indexes: (business_id, status), (business_id, created_at DESC), (customer_email, business_id).
**review_category_scores** — Per-review dimension scores
Columns: id (uuid PK), review_id (FK), category_id (FK), score (int, 1–{{rating_scale}}).
Unique constraint: (review_id, category_id). Index: review_id.
**review_responses** — Owner replies to reviews
Columns: id (uuid PK), review_id (FK, unique — one response per review), response_text, responded_by (FK → auth.users), responded_at, updated_at.
Index: review_id.
**review_invitations** — Post-purchase review request records
Columns: id (uuid PK), business_id (FK), customer_name, customer_email, customer_phone, product_service, order_id, channel (email/whatsapp/sms), status (sent/opened/completed/expired), token (unique, 32-byte hex, for unique review URL), sent_at, expires_at (now + 30 days), completed_at, created_at.
Indexes: (business_id, status), token.
**review_widgets** — Embeddable widget configurations
Columns: id (uuid PK), business_id (FK), name, widget_type (carousel/grid/list/badge), min_rating (int), show_count (int), show_verified_only (bool), color_scheme, custom_css, is_active (bool), embed_key (unique, 16-byte hex), created_at.
Index: business_id.
## RLS Policies
- reviews: anon SELECT where status = 'published'; anon INSERT (no auth required); authenticated users full access. Always wrap auth.uid() as (select auth.uid()) to prevent per-row evaluation.
- review_responses: public SELECT; authenticated INSERT/UPDATE.
- review_invitations: authenticated full access only.
- review_widgets: public SELECT where is_active = true; authenticated full access.
## Feature Modules
### 1. Public Review Submission Form (/review/[business-slug])
No login required. Build a clean, mobile-first submission page:
- Header: business name "{{business_name}}", aggregate star rating, total review count.
- **Overall star rating**: Large, touch-friendly {{rating_scale}}-star interactive selector.
- **Category breakdown**: Render one {{rating_scale}}-star selector per category from {{review_categories}} (e.g. Food Quality, Service, Cleanliness, Value for Money).
- **Review content**: Optional title field + required body textarea (min 20 characters with live char count).
- **Product/service**: Dropdown or free-text input.
- **Photo upload**: Up to 5 images, drag-and-drop or tap-to-select, upload to Supabase Storage bucket "review-photos".
- **Reviewer info**: Full name (required), email (required — used for uniqueness check).
- **Verified purchase**: Optional order ID field; if valid, award a gold "✓ Verified Purchase" badge.
- On success: show thank-you page with share prompt.
- On duplicate email+business+product: show friendly "You've already reviewed this" message.
### 2. Admin Management Dashboard (/admin)
**Review Inbox (default view)**
- List of reviews ordered by created_at DESC with filter tabs: All / Pending / Published / Flagged.
- Each row: avatar, customer name, star rating visual, review excerpt, date, source badge, action buttons (Publish / Reject / Flag).
- Bulk select + bulk approve action.
- Filter sidebar: status, rating range, date range, product/service.
**Review Detail Panel (right-side sheet)**
- Full review text, customer details, category score breakdown (radar chart via Recharts).
- Photo gallery (lightbox on click).
- Verified purchase badge if applicable.
- **Reply composer**: Textarea with quick-insert template buttons (Thank you, Apology, Follow-up). POST to /api/reviews/[id]/respond. Reply appears publicly below the review card.
**Invitation Manager (/admin/invitations)**
- Single send form: customer name + email/phone + product + optional order ID + channel selector (Email / WhatsApp).
- Bulk import: CSV upload (columns: name, email, product, order_id). Parse client-side with PapaParse, preview rows, confirm send.
- WhatsApp channel: generate `https://wa.me/60XXXXXXX?text=Hi+{{customer_name}},+please+share+your+review+here:+[link]` and open in new tab.
- Email channel: send via Resend API using a branded HTML template showing business name and direct review link with token.
- Tracking table: invitation list with sent / opened / completed / expired status and timestamps.
**Widget Builder (/admin/widgets)**
- Create widget configurations: name, type (Carousel / Grid / List / Badge), min rating filter, count, verified-only toggle, colour scheme.
- Generate embed snippet: `<script src="https://yourdomain.com/api/widget/[embed-key].js"></script>`.
- Live preview in modal using iframe pointing to /api/widget/[embed-key]?preview=1.
### 3. Analytics Dashboard (/admin/analytics)
Display the following using shadcn/ui Cards + Recharts:
- Summary cards: Total Reviews, Average Rating (1 decimal), This Month, Response Rate %.
- Rating distribution: horizontal bar chart showing count and % for each star level (1–{{rating_scale}}).
- 30-day trend: line chart of daily review submissions.
- Category scores: radar chart with average score per dimension from {{review_categories}}.
- Sentiment breakdown: donut chart — Positive (4–5★), Neutral (3★), Negative (1–2★).
- Recent activity: last 5 reviews quick-view list.
### 4. Public Reviews Page (/reviews/[business-slug])
SEO-optimised public-facing page:
- Hero section: business name, large average rating, star visual, total count, "Write a Review" CTA button.
- Rating distribution bars (Amazon-style).
- Category average score pills.
- Sortable review list: Newest / Highest / Lowest / Most Helpful.
- Review cards: partially masked customer name (e.g. "Chan * Ming"), star rating, date, review body, photo thumbnails, owner reply section (collapsible), verified badge.
- Infinite scroll or "Load More" pagination (cursor-based using created_at for keyset pagination — no OFFSET).
- Floating "Write a Review" button (mobile sticky bottom bar).
### 5. Embeddable Widget (/api/widget/[embed-key].js)
- JavaScript snippet that injects an iframe or Shadow DOM widget into any webpage.
- Carousel type: auto-rotating review cards with star rating, reviewer name, excerpt.
- Badge type: compact display — average rating + stars + "X reviews" — links to public page.
- Responsive and mobile-friendly.
- No external dependencies loaded from the host page.
## Business Rules
1. **Auto-publish threshold**: Reviews rated ≥ {{auto_publish}} stars are auto-published on submission; below threshold enter pending queue.
2. **One review per email per product**: Enforce unique (customer_email, business_id, product_service) at application layer and with a partial unique index.
3. **Profanity filter**: On submission, check body text against a configurable keyword list stored in a `profanity_keywords` table. If triggered, force status = 'pending' regardless of rating.
4. **Review reporting**: Each public review card has a "Report" button. After 3 reports (tracked in a `review_reports` table), automatically set status = 'flagged'.
5. **Invitation expiry**: Token links older than 30 days render an "This invitation has expired" page.
6. **Verified purchase badge**: Gold pill badge "✓ Verified Purchase" shown when is_verified_purchase = true.
## UI/UX Design Tokens
- Nav background: #0F172A (dark navy). Content background: #FFFFFF. Accent: #FCD34D (yellow-gold).
- Star fill colour: #FCD34D (filled) / #E2E8F0 (empty).
- Status badges: published = green-100/green-800, pending = yellow-100/yellow-800, flagged = red-100/red-800.
- Card style: rounded-xl, border border-slate-200, hover:shadow-md transition.
- Verified badge: bg-yellow-50 text-yellow-700 border border-yellow-200.
- Fonts: Inter for Latin, Noto Sans SC for Chinese characters.
- All async states: shadcn/ui Skeleton components.
- Empty states: illustration + "No reviews yet. Send your first invitation to start collecting feedback!"
## File Structure
src/app/
├── review/[business-slug]/page.tsx # Public submission form
├── reviews/[business-slug]/page.tsx # Public display page
├── admin/
│ ├── layout.tsx # Auth-gated admin shell
│ ├── page.tsx # Review inbox
│ ├── invitations/page.tsx # Invitation manager
│ ├── widgets/page.tsx # Widget builder
│ └── analytics/page.tsx # Analytics dashboard
└── api/
├── reviews/route.ts # POST: submit review
├── reviews/[id]/respond/route.ts # POST: owner reply
├── reviews/[id]/report/route.ts # POST: flag review
└── widget/[embed-key]/route.ts # GET: widget JS/iframe
Implementation order: database schema + RLS → public submission form → admin review inbox → public display page → analytics dashboard → invitation manager → embeddable widget. Confirm completion of each module before proceeding to the next.