Item Management
Item Management
Section titled “Item Management”Summary
Section titled “Summary”Item Management enables users to add specific gift items to their wishlists, including product names, images, URLs, prices, and mark favorites.
User Story
Section titled “User Story”As a wishlist owner,
I want to add items with details and links,
So that my Secret Santa match knows exactly what I want and where to buy it.
Acceptance Criteria
Section titled “Acceptance Criteria”- ✅ User can add custom items with name, URL, image, and price
- ✅ User can add items from trending suggestions
- ✅ User can mark items as favorites (priority)
- ✅ User can edit item details
- ✅ User can delete items from wishlist
- ✅ User can upload custom item images
- ✅ Items display with blurhash placeholder while loading
- ✅ External links open in new tab
Inputs and Outputs
Section titled “Inputs and Outputs”Inputs:
- Item name (string, required)
- Item URL (string, required, must be valid URL)
- Item image (File or URL, required)
- Price (number, optional)
- Is favorite (boolean, default: false)
Outputs:
- Created item with unique ID
- Updated wishlist with new item count
- Image uploaded to CDN with blurhash
1. Add Custom Item
Section titled “1. Add Custom Item”User navigates to /wishlist/:id/edit/items │ ▼Click "Add Item" │ ▼Fill form: - Name: "Wireless Headphones" - URL: "https://amazon.com/..." - Price: $99.99 - Upload image or provide URL - Mark as favorite: ☑️ │ ├─ Upload custom image ────────┐ │ │ │ ▼ │ Validate file: │ - Type: image/jpeg, image/png │ - Size: < 5 MB │ │ │ ▼ │ Preview image │ └─ Provide image URL ──────────┐ │ ▼ Validate URL format │ ▼Click "Add to Wishlist" │ ▼POST /wishlists/v2/:id/item FormData: { name, url, isFavorite, image } │ ├─ Success ────────────────────┐ │ │ │ ▼ │ Backend uploads image to CDN │ │ │ ▼ │ Generate blurhash for image │ │ │ ▼ │ Return item with CDN URLs │ │ │ ▼ │ Add item to Redux wishlist │ │ │ ▼ │ Show "Item added" toast │ │ │ ▼ │ Reset form for next item │ └─ Error ──────────────────────┐ │ ▼ Show error toast "Failed to add item"2. Add Item from Trending Suggestions
Section titled “2. Add Item from Trending Suggestions”User browses /items (trending items) │ ▼View curated gift suggestions: ┌─────────┬─────────┬─────────┬─────────┐ │ Item 1 │ Item 2 │ Item 3 │ Item 4 │ │ [image] │ [image] │ [image] │ [image] │ │ $25 │ $50 │ $100 │ $15 │ │ [+ Add] │ [+ Add] │ [+ Add] │ [+ Add] │ └─────────┴─────────┴─────────┴─────────┘ │ ▼Click "+ Add" on Item 2 │ ▼Show bottom sheet: "Add to wishlist" - Select wishlist: [dropdown] - Mark as favorite: ☐ │ ▼User selects wishlist and confirms │ ▼POST /wishlists/:wishlistId/item { name, url, image, isFavorite } │ ├─ Success ────────────────────┐ │ │ │ ▼ │ Add item to Redux │ │ │ ▼ │ Show "Added to wishlist" toast │ └─ Error ──────────────────────┐ │ ▼ Show error toast3. Edit Item
Section titled “3. Edit Item”User navigates to /wishlist/:id/edit/items │ ▼View items list with edit buttons │ ▼Click "Edit" on an item │ ▼Load item data into form: - Name: [current name] - URL: [current URL] - Price: [current price] - Image: [current image preview] - Is favorite: [current state] │ ▼User updates fields │ ▼Click "Save Changes" │ ▼PATCH /wishlists/v2/:wishlistId/item/:itemId FormData: { name, url, isFavorite, image? } │ ├─ Success ────────────────────┐ │ │ │ ▼ │ Update item in Redux │ │ │ ▼ │ Show "Item updated" toast │ └─ Error ──────────────────────┐ │ ▼ Show error toast4. Delete Item
Section titled “4. Delete Item”User clicks "Delete" on item │ ▼Show confirmation modal: "Remove this item from wishlist?" │ ▼User confirms │ ▼DELETE /wishlists/:wishlistId/item/:itemId │ ├─ Success ────────────────────┐ │ │ │ ▼ │ Remove item from Redux │ │ │ ▼ │ Show "Item removed" toast │ └─ Error ──────────────────────┐ │ ▼ Show error toast5. View Items (Match Perspective)
Section titled “5. View Items (Match Perspective)”Match navigates to /group/:groupId/wishlists/:wishlistId │ ▼GET /groups/:groupId/wishlists/:wishlistId │ ▼Display items grid: ┌──────────────────┬──────────────────┐ │ ⭐ Wireless HD... │ Gaming Mouse │ │ [image] │ [image] │ │ $99.99 │ $49.99 │ │ [View Product] │ [View Product] │ └──────────────────┴──────────────────┘ │ ▼User clicks "View Product" on item │ ▼Open item.url in new tab (e.g., https://amazon.com/product/...)Edge Cases
Section titled “Edge Cases”- Invalid URL: Show error “Please enter a valid URL (e.g., https://…)”
- Image upload fails: Fall back to placeholder image
- Duplicate item: Allow duplicates; no uniqueness check
- Item with no price: Display “Price not specified”
- Very long item name: Truncate with ellipsis after 50 characters
- Image too large: Reject files > 5 MB
- Network error during save: Retry automatically once, then manual
API/Contracts
Section titled “API/Contracts”Create Custom Item (With Image Upload)
Section titled “Create Custom Item (With Image Upload)”Request:
POST /wishlists/v2/1/itemAuthorization: Bearer {token}Content-Type: multipart/form-data
name=Wireless Headphonesurl=https://amazon.com/product/123isFavorite=trueimage=<binary file data>Response:
{ "success": true, "item": { "id": 10, "name": "Wireless Headphones", "url": "https://amazon.com/product/123", "price": 99.99, "image": "https://cdn.rnb.la/items/item10.jpg", "imageBlurhash": "L6PZfSi_.AyE_3t7t7R**0o#DgR4", "isFavorite": true, "wishlistId": 1, "createdAt": "2025-10-29T12:00:00Z" }}Create Item from URL (Trending Item)
Section titled “Create Item from URL (Trending Item)”Request:
POST /wishlists/1/itemAuthorization: Bearer {token}Content-Type: application/json
{ "name": "Smart Watch", "url": "https://store.com/watch", "price": 199.99, "image": "https://cdn.rnb.la/trending/watch.jpg", "isFavorite": false}Response:
{ "success": true, "item": { "id": 11, "name": "Smart Watch", "url": "https://store.com/watch", "price": 199.99, "image": "https://cdn.rnb.la/trending/watch.jpg", "imageBlurhash": "L6PZfSi_.AyE_3t7t7R**0o#DgR4", "isFavorite": false, "wishlistId": 1, "createdAt": "2025-10-29T12:05:00Z" }}Edit Item
Section titled “Edit Item”Request:
PATCH /wishlists/v2/1/item/10Authorization: Bearer {token}Content-Type: multipart/form-data
name=Updated Headphonesurl=https://amazon.com/product/456isFavorite=falseimage=<optional new image>Response:
{ "success": true, "item": { "id": 10, "name": "Updated Headphones", "url": "https://amazon.com/product/456", "isFavorite": false, ... }}Delete Item
Section titled “Delete Item”Request:
DELETE /wishlists/1/item/10Authorization: Bearer {token}Response:
{ "success": true, "message": "Item deleted"}Error Handling and Retries
Section titled “Error Handling and Retries”Client-side Errors
Section titled “Client-side Errors”- Empty name: Form validation prevents submission
- Invalid URL: Validate format before submission
- No image provided: Require either file upload or URL
- Image too large: Check file size before upload
Server-side Errors
Section titled “Server-side Errors”- 400 Bad Request: “Invalid item data”
- 404 Not Found: “Wishlist not found”
- 413 Payload Too Large: “Image file too large”
- 500 Server Error: “Failed to save item”
Retry Strategy
Section titled “Retry Strategy”- Create/update: Manual retry (user clicks save again)
- Image upload: Automatic retry once, then manual
- Delete: Manual retry only
Telemetry
Section titled “Telemetry”Metrics
Section titled “Metrics”item.created_custom: Incremented when custom item addeditem.created_from_trending: Incremented when trending item addeditem.edited: Incremented on item updateitem.deleted: Incremented on item deletionitem.marked_favorite: Incremented when favorite toggled onitem.link_clicked: Incremented when match clicks “View Product”
console.log('Item created:', itemId);console.log('Item added from trending:', trendingItemId);Sentry.addBreadcrumb({ category: 'item', message: 'User added item to wishlist', data: { wishlistId, itemId, source: 'custom' },});Rollout
Section titled “Rollout”Feature Flags
Section titled “Feature Flags”Assumption: No feature flags. Consider adding for:
- Beta testing new item creation UI
- A/B testing trending items integration
Migrations
Section titled “Migrations”- Database: Items table requires
imageBlurhashcolumn (added in migration v3.2) - Data: Existing items without blurhash get generated on first access
Example
Section titled “Example”'use client';
import { useState } from 'react';import { useForm } from 'react-hook-form';import { yupResolver } from '@hookform/resolvers/yup';import * as yup from 'yup';import { itemHandlers } from 'src/services/handlers/items/itemHandlers';import { toast } from 'react-toastify';
const schema = yup.object({ name: yup.string().required('Name is required'), url: yup.string().url('Must be a valid URL').required('URL is required'), price: yup.number().positive().optional(), isFavorite: yup.boolean(),});
export default function AddItemView({ wishlistId }: Props) { const [imageFile, setImageFile] = useState<File | null>(null); const { register, handleSubmit, formState: { errors, isSubmitting }, reset } = useForm({ resolver: yupResolver(schema), });
const onSubmit = async (data) => { if (!imageFile) { toast.error('Please upload an image'); return; }
try { await itemHandlers.createCustomItem( wishlistId, data.isFavorite || false, data.name, data.url, imageFile );
toast.success('Item added to wishlist!'); reset(); setImageFile(null); } catch (error) { toast.error('Failed to add item'); console.error(error); } };
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { if (file.size > 5 * 1024 * 1024) { toast.error('Image too large (max 5 MB)'); return; } setImageFile(file); } };
return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('name')} placeholder="Item name" /> {errors.name && <span>{errors.name.message}</span>}
<input {...register('url')} placeholder="Product URL" type="url" /> {errors.url && <span>{errors.url.message}</span>}
<input {...register('price')} placeholder="Price (optional)" type="number" step="0.01" />
<input type="file" accept="image/*" onChange={handleImageChange} /> {imageFile && <img src={URL.createObjectURL(imageFile)} alt="Preview" width={100} />}
<label> <input type="checkbox" {...register('isFavorite')} /> Mark as favorite </label>
<button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Adding...' : 'Add Item'} </button> </form> );}Further Reading
Section titled “Further Reading”- Wishlist Management - Creating wishlists for items
- Trending Items - Discovering suggested items
- Upload Components - Image upload UI patterns
- State Management - Items Redux slice