Skip to content

Wishlist Management

Wishlist Management allows users to create multiple wishlists, add items they want to receive as gifts, and control which wishlists are visible to each Secret Santa group.

As a gift recipient,
I want to create wishlists and assign them to specific groups,
So that my Secret Santa match knows what gifts I’d like to receive.

  • ✅ User can create multiple wishlists with names and images
  • ✅ User can add/remove items to wishlists
  • ✅ User can assign wishlists to one or more groups
  • ✅ User can hide wishlists from specific groups
  • ✅ User can edit wishlist name and image
  • ✅ User can delete wishlists
  • ✅ Group members can view each other’s wishlists after raffle
  • ✅ Wishlists show item count and preview images

Inputs:

  • Wishlist name (string, required)
  • Wishlist image (file, optional)
  • Group assignments (array of group IDs)
  • Visibility toggles per group

Outputs:

  • Created wishlist with unique ID
  • List of user’s wishlists with item counts
  • Wishlist assignment status per group
User navigates to /wishlist/create
Enter wishlist name
(e.g., "Christmas 2025", "Birthday Wishes")
Upload cover image (optional)
Click "Create Wishlist"
POST /wishlists
├─ Success ────────────────────┐
│ │
│ ▼
│ Store in Redux wishlists slice
│ │
│ ▼
│ Navigate to /wishlist/:id/edit/items
│ │
│ ▼
│ Show "Add items to your wishlist"
└─ Error ──────────────────────┐
Show error toast
User navigates to /settings/profile/wishlists
View all wishlists with group assignment toggles
For each group, toggle visibility:
- "Office Secret Santa" ☑️ Visible
- "Family Exchange" ☐ Hidden
Click "Save Changes"
For each change:
POST /wishlists/:id/visibility
{ groupId: X, visible: true/false }
├─ Success ────────────────────┐
│ │
│ ▼
│ Update Redux groupWishlists slice
│ │
│ ▼
│ Show "Wishlist visibility updated" toast
└─ Error ──────────────────────┐
Show error toast
User navigates to /settings/profile/wishlists
GET /wishlists/me/visibility
Display wishlists grid:
┌─────────────┬─────────────┬─────────────┐
│ Wishlist 1 │ Wishlist 2 │ Wishlist 3 │
│ [image] │ [image] │ [image] │
│ 5 items │ 3 items │ 0 items │
│ Groups: 2 │ Groups: 1 │ Groups: 0 │
│ [Edit] [Del]│ [Edit] [Del]│ [Edit] [Del]│
└─────────────┴─────────────┴─────────────┘
Click "Edit" on Wishlist 1
Navigate to /wishlist/1/edit
User navigates to /wishlist/:id/edit
Load current wishlist data
Edit name and/or upload new image
Click "Save"
├─ Name changed ───────────────┐
│ │
│ ▼
│ PATCH /wishlists/:id
│ { name: "New Name" }
└─ Image changed ──────────────┐
PUT /wishlists/:id/picture
FormData: { image: File }
├─ Success ────────────────────┐
│ │
│ ▼
│ Update Redux state
│ │
│ ▼
│ Show "Wishlist updated" toast
└─ Error ──────────────────────┐
Show error toast
User clicks "Delete" on wishlist
Show confirmation modal:
"Delete this wishlist? All items will be removed."
User confirms
DELETE /wishlists/:id
├─ Success ────────────────────┐
│ │
│ ▼
│ Remove from Redux state
│ │
│ ▼
│ Show "Wishlist deleted" toast
└─ Error ──────────────────────┐
Show error toast
User in group clicks "View Match's Wishlist"
GET /groups/:groupId/wishlists
Display match's wishlists assigned to this group
User clicks on a wishlist
Navigate to /group/:groupId/wishlists/:wishlistId
Display all items in wishlist with:
- Item name
- Item image
- Price
- Purchase link (opens in new tab)
  1. Wishlist with no items: Show “Add items to get started”
  2. Wishlist not assigned to any group: Show warning icon
  3. User tries to view own match: Not allowed (backend returns 403)
  4. Delete wishlist assigned to groups: Confirmation warns about visibility loss
  5. Upload image exceeds size limit: Show error “Image too large (max 5 MB)”
  6. Network error during save: Retry automatically or show retry button

Request:

POST /wishlists
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Christmas 2025"
}

Response:

{
"success": true,
"wishlist": {
"id": 1,
"name": "Christmas 2025",
"image": null,
"imageBlurhash": null,
"userId": 123,
"isVisible": true,
"items": [],
"createdAt": "2025-10-29T12:00:00Z"
}
}

Request:

GET /wishlists/me/visibility
Authorization: Bearer {token}

Response:

{
"success": true,
"wishlists": [
{
"id": 1,
"name": "Christmas 2025",
"image": "https://cdn.rnb.la/wishlist1.jpg",
"imageBlurhash": "L6PZfSi_.AyE_3t7t7R**0o#DgR4",
"isVisible": true,
"itemsCount": 5,
"groupsWishlists": [
{
"id": 10,
"wishlistId": 1,
"groupId": 5,
"userId": 123
}
],
"items": [ /* array of items */ ]
}
]
}

Request:

POST /wishlists/1/visibility
Authorization: Bearer {token}
Content-Type: application/json
{
"groupId": 5,
"visible": true
}

Response:

{
"success": true,
"message": "Wishlist visibility updated"
}

Request:

PATCH /wishlists/1
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Updated Name"
}

Response:

{
"success": true,
"wishlist": {
"id": 1,
"name": "Updated Name",
...
}
}

Request:

PUT /wishlists/1/picture
Authorization: Bearer {token}
Content-Type: multipart/form-data
image=<binary file data>

Response:

{
"success": true,
"image": "https://cdn.rnb.la/wishlist1_new.jpg",
"imageBlurhash": "L6PZfSi_.AyE_3t7t7R**0o#DgR4"
}

Request:

DELETE /wishlists/1
Authorization: Bearer {token}

Response:

{
"success": true,
"message": "Wishlist deleted"
}
  • Empty name: Form validation prevents submission
  • Image too large: Validate file size before upload (max 5 MB)
  • No groups available: Disable group assignment section
  • 400 Bad Request: “Invalid wishlist data”
  • 404 Not Found: “Wishlist not found”
  • 403 Forbidden: “You don’t have permission to edit this wishlist”
  • 500 Server Error: “Failed to save wishlist”
  • Create/update: Manual retry (user clicks save again)
  • Image upload: Automatic retry once, then manual
  • Fetch wishlists: Automatic retry with exponential backoff
  • wishlist.created: Incremented on wishlist creation
  • wishlist.deleted: Incremented on wishlist deletion
  • wishlist.assigned_to_group: Incremented when visibility set to true
  • wishlist.unassigned_from_group: Incremented when visibility set to false
  • wishlist.viewed_by_match: Incremented when match views wishlist
console.log('Wishlist created:', wishlistId);
console.log('Wishlist assigned to group:', groupId);
Sentry.addBreadcrumb({
category: 'wishlist',
message: 'User updated wishlist visibility',
data: { wishlistId, groupId, visible },
});

Assumption: No feature flags. Consider adding for:

  • Beta testing new visibility UI
  • A/B testing wishlist creation flow
  • Database: wishlists table requires imageBlurhash column (added in migration v3.1)
  • Data: Existing wishlists default to isVisible: true
src/sections/wishlist/create-wishlist-view.tsx
'use client';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { wishlistHandlers } from 'src/services/handlers/wishlist/wishlistHandlers';
import { useRouter } from 'next/navigation';
import { paths } from 'src/routes/paths';
import { toast } from 'react-toastify';
export default function CreateWishlistView() {
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
try {
setIsSubmitting(true);
const result = await wishlistHandlers.createWishlist(data.name);
toast.success('Wishlist created!');
router.push(paths.home.wishlist.edit.editItems(result.wishlist.id));
} catch (error) {
toast.error('Failed to create wishlist');
console.error(error);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('name', { required: 'Name is required' })}
placeholder="Wishlist name"
/>
{errors.name && <span>{errors.name.message}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Creating...' : 'Create Wishlist'}
</button>
</form>
);
}
// Component for toggling visibility
const handleToggleVisibility = async (wishlistId: number, groupId: number, visible: boolean) => {
try {
await wishlistHandlers.setWishlistToGroup(wishlistId, groupId, visible);
toast.success(`Wishlist ${visible ? 'shown' : 'hidden'} in group`);
// Refresh wishlists
dispatch(fetchWishlists());
} catch (error) {
toast.error('Failed to update visibility');
console.error(error);
}
};
return (
<div>
<h3>{wishlist.name}</h3>
{userGroups.map((group) => (
<label key={group.id}>
<input
type="checkbox"
checked={isWishlistVisibleInGroup(wishlist, group.id)}
onChange={(e) => handleToggleVisibility(wishlist.id, group.id, e.target.checked)}
/>
{group.name}
</label>
))}
</div>
);