"""
end
```
The existing `Lightbox` JS hook works unchanged — it's already generic.
### Moderation
Photos shown to admin alongside review text during moderation. Admin approves/rejects the whole review (no separate photo moderation).
---
## Review submission flows
### 1. From product page (email verification)
**UI on product page:**
```
+-------------------------------------+
| Write a review |
| |
| [email@example.com ] [Continue] |
| |
| We'll send a link to verify your |
| purchase and let you leave a review |
+-------------------------------------+
```
**Flow:**
1. User enters email
2. System checks: has this email purchased this product?
- No -> "We couldn't find a matching order for this product"
- Yes -> send verification email with review link
3. Email contains link: `/reviews/new?token=xxx`
4. Token encodes: `{email, product_id}`
5. On click -> set email session cookie -> show review form
**If they already have a valid email session:**
- Skip email entry, show review form directly
- Or show "Leave a review as email@example.com" with form
**If they've already reviewed this product:**
- Show their existing review with "Edit" option
### 2. From order lookup page
On `/orders`, for each delivered order line item:
```
+-------------------------------------+
| Mountain Sunrise Canvas |
| Delivered 15 Jan 2024 |
| |
| [Write a review] or [Edit review] |
+-------------------------------------+
```
Since they're already verified (have email session), clicking goes straight to review form.
### 3. Via email request (post-delivery)
**Oban job:** `lib/berrypod/reviews/review_request_job.ex`
Scheduled X days after order marked delivered:
- Check if customer has already reviewed all products in order
- Send email with links to review each unreviewed product
- Each link is a signed token for that email+product
**Email template:**
```
Subject: How was your order from {shop_name}?
Hi {name},
Your order #{ref} was delivered {days} days ago. We'd love to hear what you think!
{for each product}
[{product_title}] - [Leave a review]
{end}
Thanks for shopping with us!
```
**Admin setting:** Days after delivery to send request (default: 7)
---
## Review form
**Route:** `GET /reviews/new?token=xxx` or `GET /products/:slug/review`
**LiveView:** `lib/berrypod_web/live/shop/review_form.ex`
**Form fields:**
- Rating (required) — 5 star selector
- Title (optional) — text input, max 100 chars
- Body (optional) — textarea, max 2000 chars
- Name (required) — pre-filled from order if available, max 50 chars
- Photos (optional) — file upload, max 3 images
**Photo upload UI:**
- "Add photos (optional)" button with camera icon
- Shows thumbnail previews after selection
- Easy remove button on each thumbnail
- Accepts jpg, png, webp, heic
**Validation:**
- Rating 1-5
- Title max 100 chars
- Body max 2000 chars
- Name max 50 chars
- Max 3 photos, max 10MB each
**On submit:**
- Upload and process photos via existing image pipeline
- Create review with status: pending
- Show confirmation: "Thanks! Your review will appear once approved."
- Redirect to product page
**Edit mode:**
- Same form, pre-filled with existing review
- Existing photos shown with remove option
- Submit updates review, resets status to pending
- Show: "Your updated review will appear once approved."
---
## Reviews display
### Product page
**Update `reviews_section` component** to accept real data:
```elixir
# In product page init
reviews = Reviews.list_reviews_for_product(product.id, status: :approved)
{avg, count} = Reviews.average_rating_for_product(product.id)
assign(socket,
reviews: reviews,
average_rating: avg,
review_count: count
)
```
**Component changes:**
- Show "Verified purchase" badge if `review.order_id` present
- Show review date as relative time
- Show review photos as clickable thumbnails (using `review_photos` component)
- Pagination if > 10 reviews
### Product cards (optional enhancement)
Show average rating + count on collection/home pages:
```
****- (24)
```
Requires preloading aggregate data — consider caching in products table:
- `rating_avg` decimal
- `rating_count` integer
- Updated via Oban job or on review approval
---
## Admin moderation
**Route:** `/admin/reviews`
**LiveView:** `lib/berrypod_web/live/admin/reviews.ex`
**Features:**
- Tabs: Pending | Approved | Rejected
- List with: product, author, rating, excerpt, photos (thumbnails), date
- Click to expand full review with full-size photos
- Approve / Reject buttons
- Bulk actions: approve selected, reject selected
**Counts in nav:**
- Show pending count badge on Reviews nav item
**Optional:** Email notification to admin on new review
---
## Schema markup
**Update product JSON-LD** in `lib/berrypod_web/components/seo_components.ex`:
```json
{
"@type": "Product",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.5",
"reviewCount": "24",
"bestRating": "5",
"worstRating": "1"
},
"review": [
{
"@type": "Review",
"author": {"@type": "Person", "name": "Jane D."},
"datePublished": "2024-01-15",
"reviewRating": {
"@type": "Rating",
"ratingValue": "5"
},
"reviewBody": "..."
}
]
}
```
Only include if product has approved reviews.
---
## Task breakdown
### Phase 1: Email session foundation (~2h) ✓
1. **Email session module** (1h) ✓
- Create `Berrypod.EmailSession` module
- Create plug to load into assigns
- Add to endpoint/router
2. **Update existing flows** (1h) ✓
- Order lookup -> set email session on verify
- Orders page -> read from email session
- Checkout success -> set email session (via /checkout/complete controller)
### Phase 2: Reviews schema and context (~2.5h) ✓
3. **Reviews migration and schema** (1h) ✓
- Create migration with image_ids array
- Create `Review` schema with changeset
4. **Reviews context** (1.5h) ✓
- CRUD functions
- Query helpers (by product, by email, pending)
- Purchase verification
- Image preloading helpers
### Phase 3: Review submission (~4h)
5. **Product page review section** (1.5h)
- Email entry form (if no session)
- Review form (if session + can review)
- Edit existing review
- Verification email sending
6. **Review form LiveView** (2h)
- Token verification
- Form with star rating
- Photo upload with LiveView uploads (max 3)
- Create/update handling
- Photo processing via existing pipeline
7. **Orders page integration** (0.5h)
- "Write a review" / "Edit review" buttons
- Link to review form
### Phase 4: Display and components (~2.5h)
8. **Extract image_lightbox component** (0.5h)
- Generic lightbox from product_lightbox
- Same JS hook, just cleaner component interface
9. **Review photos component** (0.5h)
- Inline thumbnails
- Opens image_lightbox on click
10. **Product page display** (1.5h)
- Update reviews_section to use real data
- Verified purchase badge
- Review photos display
- Pagination
### Phase 5: Admin moderation (~2h)
11. **Admin reviews list** (1.5h)
- Reviews list with tabs (pending/approved/rejected)
- Photo thumbnails in list
- Expand to see full review + photos
- Approve/reject actions
12. **Nav and notifications** (0.5h)
- Pending count badge in admin nav
- Optional: email to admin on new review
### Phase 6: Automation and SEO (~2h)
13. **Review request emails** (1h)
- Oban job for post-delivery requests
- Email template
- Admin setting for delay
14. **Schema markup** (0.5h)
- AggregateRating on product pages
- Individual Review markup
15. **Rating cache on products** (0.5h)
- Add rating_avg, rating_count to products
- Update on review approval
- Display on product cards
---
## Files to create
- `lib/berrypod/email_session.ex` — email session token handling
- `lib/berrypod_web/plugs/email_session.ex` — plug to load session into assigns
- `priv/repo/migrations/xxx_create_reviews.exs` — reviews table
- `lib/berrypod/reviews/review.ex` — Review schema
- `lib/berrypod/reviews.ex` — Reviews context
- `lib/berrypod/reviews/review_notifier.ex` — review verification emails
- `lib/berrypod/reviews/review_request_job.ex` — Oban job for post-delivery emails
- `lib/berrypod_web/live/shop/review_form.ex` — review submission form
- `lib/berrypod_web/live/admin/reviews.ex` — admin moderation
## Files to modify
- `lib/berrypod_web/router.ex` — new routes
- `lib/berrypod_web/controllers/order_lookup_controller.ex` — set email session
- `lib/berrypod_web/live/shop/pages/orders.ex` — review buttons
- `lib/berrypod_web/live/shop/pages/product.ex` — review section with real data
- `lib/berrypod_web/components/shop_components/product.ex` — extract image_lightbox
- `lib/berrypod_web/components/shop_components/content.ex` — reviews_section updates, review_photos component
- `lib/berrypod_web/components/seo_components.ex` — review schema markup
- `lib/berrypod/pages/block_types.ex` — real data loader for reviews
- `lib/berrypod_web/components/admin_components.ex` — nav badge for pending reviews
- `lib/berrypod/products/product.ex` — optional rating_avg, rating_count fields
---
## Open questions
1. **Delay for review request email** — 7 days after delivery? Make configurable in admin settings?
2. **Review replies** — can shop owner reply to reviews publicly? (later feature)
3. **Import reviews** — any need to import from other platforms like Etsy?
4. **Incentives** — discount code for leaving a review? (later feature)
5. **Photo size limit** — 10MB per photo reasonable? Or smaller?
---
## Total estimate
~15 hours across 6 phases. Can be broken into sessions:
- Session 1: Phases 1-2 (email session + schema) ~4.5h
- Session 2: Phase 3 (submission flows) ~4h
- Session 3: Phases 4-5 (display + admin) ~4.5h
- Session 4: Phase 6 (automation + SEO) ~2h