Collections
Group related designs for organization, sharing, and client presentations.
Overview
Organize
Group designs by theme, project, or client
Cover Images
Set featured image for each collection
Share
Copy public collection URLs
Creating Collections
- Navigate to Collections in sidebar
- Click “New Collection” button
- Enter collection name
- Collection created with empty state
// Create collection
const createCollection = async (name: string) => {
const { data } = await supabase
.from('design_collections')
.insert({
user_id: userId,
name: name,
})
.select()
.single();
return data;
};
Database Schema
-- Design collections table
create table public.design_collections (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) on delete cascade,
name text not null,
cover_url text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- Collection membership (many-to-many)
create table public.collection_generations (
collection_id uuid references public.design_collections(id) on delete cascade,
generation_id uuid references public.generations(id) on delete cascade,
added_at timestamptz default now(),
primary key (collection_id, generation_id)
);
-- Enable RLS
alter table public.design_collections enable row level security;
alter table public.collection_generations enable row level security;
-- Users can only access their own collections
create policy "Users can manage own collections"
on public.design_collections
for all using (auth.uid() = user_id);
Adding Designs
From the Gallery or Generation detail:
- Select one or more designs
- Click “Add to Collection”
- Choose existing collection or create new
- Designs linked instantly
// Add to collection
const addToCollection = async (collectionId: string, generationIds: string[]) => {
await supabase
.from('collection_generations')
.insert(
generationIds.map(id => ({
collection_id: collectionId,
generation_id: id,
}))
);
// Update state
refreshCollections();
};
Collection Detail Page
/collections/[id] shows:
- Collection name (editable)
- Cover image (settable from any design)
- Grid of included designs
- Multi-select for bulk actions
- Share URL button
Setting Cover Image
- Open collection
- Click any design’s menu
- Select “Set as Cover”
- Cover updates instantly
// Set cover image
const setCoverImage = async (collectionId: string, imageUrl: string) => {
await supabase
.from('design_collections')
.update({ cover_url: imageUrl })
.eq('id', collectionId);
};
Sharing Collections
Collections can be shared via URL:
- Click “Copy Link” on any collection
- URL copied to clipboard
- Recipients can view (if public sharing enabled)
// Copy shareable URL
const copyCollectionUrl = (collectionId: string) => {
const url = `${window.location.origin}/collections/${collectionId}`;
navigator.clipboard.writeText(url);
toast.success("Link copied!");
};
Multi-Select Actions
Within a collection:
Remove from Collection
Remove selected designs (doesn’t delete the generation)
Send to Production
Create production order with selected designs
State Management
Collections sync with AppProvider:
// In providers.tsx
interface AppContextType {
designCollections: DesignCollection[];
createDesignCollection: (name: string) => Promise<DesignCollection | null>;
updateDesignCollection: (id: string, name: string, coverUrl?: string) => Promise<void>;
deleteDesignCollection: (id: string) => Promise<void>;
addToDesignCollection: (collectionId: string, generationIds: string[]) => Promise<void>;
removeFromDesignCollection: (collectionId: string, generationIds: string[]) => Promise<void>;
}
// Collection type
interface DesignCollection {
id: string;
name: string;
coverUrl: string | null;
generations: Generation[];
createdAt: string;
}
Empty States
No Collections
“Create your first collection to organize designs”
Empty Collection
“Add designs from the gallery to get started”
UI Components
Collection Grid
// Collections list
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{collections.map(collection => (
<CollectionCard
key={collection.id}
collection={collection}
onClick={() => router.push(`/collections/${collection.id}`)}
/>
))}
</div>
Collection Card
Shows:
- Cover image or placeholder
- Collection name
- Design count
- Created date
- Edit/delete actions
// CollectionCard component
function CollectionCard({ collection, onClick }) {
return (
<div
onClick={onClick}
className="group cursor-pointer rounded-lg border bg-card hover:border-primary transition"
>
<div className="aspect-video relative overflow-hidden rounded-t-lg bg-muted">
{collection.coverUrl ? (
<Image src={collection.coverUrl} alt={collection.name} fill className="object-cover" />
) : (
<div className="flex items-center justify-center h-full">
<Folder className="h-12 w-12 text-muted-foreground" />
</div>
)}
</div>
<div className="p-4">
<h3 className="font-medium">{collection.name}</h3>
<p className="text-sm text-muted-foreground">
{collection.generations.length} designs
</p>
</div>
</div>
);
}
Best Practices
Naming conventions: Use descriptive names like “Summer 2024 Drop” or “Client: Acme Co” for easy organization.
Cover images: Set a cover that represents the collection’s theme—it’s the first thing people see.