Skip to main content

Items Management

The Items UI is the primary content management interface for work entries. It provides a tabbed layout with four views -- Browse Items, Categories, Tags, and Collections -- along with search, filtering, virtualized rendering for large lists, and full CRUD operations.

Component Hierarchy

ItemsPageClient
|
+-- ItemsProvider (context)
|
+-- Page header (title + "Add Item" button)
|
+-- Tab navigation (items | categories | tags | collections)
|
+-- Tab: Items
| +-- ItemsList
| +-- Search input + Category filter + View toggle (grid/list)
| +-- Items count display
| +-- VirtualizedItemsList
| | +-- ItemCard (memoized, per item)
| +-- ItemsEmptyState (if no matches)
|
+-- Tab: Categories
| +-- CategoriesTab
| +-- Search + "Add Category" button
| +-- Categories table (name, description, items count, priority, actions)
| +-- CategoryModal (create/edit form)
|
+-- Tab: Tags
| +-- TagsTab
| +-- Search + "Add Tag" button
| +-- Tags table (name, items count, actions)
| +-- TagModal (create/edit form)
|
+-- Tab: Collections
| +-- CollectionsTab
| +-- Search + "Add Collection" button
| +-- Collections table (name, description, items count, priority, actions)
| +-- CollectionModal (create/edit form)
|
+-- AddItemModal
+-- AddItemForm
+-- Source URL + Extract button
+-- Name, Description fields
+-- CategoriesField
+-- TagsField
+-- Slug, Brand, Brand Logo URL fields
+-- ImagesField + Screenshot capture
+-- Featured checkbox, Update with PR checkbox

ItemsContext

File: apps/web/src/components/works/detail/items/ItemsContext.tsx

A React context that shares common state across all items components:

interface ItemsContextType {
workId: string;
canEdit: boolean;
workWebsite?: string;
screenshotAvailable: boolean;
}

screenshotAvailable is determined by calling checkScreenshotAvailability() on mount -- this checks whether a screenshot plugin (e.g., ScreenshotOne) is configured.

Key Components

ItemsPageClient

File: apps/web/src/components/works/detail/items/ItemsPageClient.tsx

The top-level client component managing tab state and coordinating all sub-views.

interface ItemsPageClientProps {
items: ItemData[];
workId: string;
categories?: Category[];
tags?: Tag[];
collections?: Collection[];
}

Tab Types: 'items' | 'categories' | 'tags' | 'collections'

Each tab is rendered with an icon from lucide-react:

  • Items: Package
  • Categories: FolderTree
  • Tags: Tags
  • Collections: Bookmark

ItemsList (Virtualized)

File: apps/web/src/components/works/detail/items/ItemsList.tsx

The main items browsing view with search, filtering, and virtualized rendering using @tanstack/react-virtual.

interface ItemsListProps {
items: ItemData[];
addItemRef?: React.RefObject<((item: ItemData) => void) | null>;
}

Features:

  • Search: Filters by item name and description
  • Category filter: Dropdown populated from unique item categories
  • View modes: Grid (responsive 1-3 columns) or List view
  • Virtualization: Uses useVirtualizer with scroll-margin awareness for the main content area
  • Sorting: Items sorted by featured status (first), then by order field, then alphabetically

Virtualization Config:

ParameterGrid ModeList Mode
Row height~200px + 16px gap~80px + 16px gap
Overscan5 rows5 rows
Columns1 (mobile), 2 (sm), 3 (lg)1

Responsive column detection uses a useColumnCount hook that listens to window resize events.

AddItemForm

File: apps/web/src/components/works/detail/items/AddItemForm.tsx

A comprehensive form for creating new work items with AI-powered extraction.

interface ItemFormData {
name: string;
description: string;
source_url: string;
categories: string[];
tags: string[];
featured: boolean;
pay_and_publish_now: boolean;
slug: string;
brand: string;
brand_logo_url: string;
images: string[];
}

AI Extraction: Entering a URL and clicking "Extract" calls extractItemDetails(url, categories) which uses AI to populate name, description, tags, categories, brand, and images from the web page.

Screenshot Capture: If a screenshot plugin is configured, the "Capture Screenshot" button calls captureScreenshot(url) to generate an image of the source URL and add it to the images list.

CategoriesTab

File: apps/web/src/components/works/detail/items/CategoriesTab.tsx

Manages work categories with a searchable table and CRUD modal.

interface CategoriesTabProps {
workId: string;
initialCategories: Category[];
items: ItemData[]; // used to compute item counts per category
canEdit: boolean;
}

Table Columns: Name (with icon), Description, Items Count, Priority, Actions (edit/delete)

Constraints: Categories with assigned items cannot be deleted -- an error toast is shown.

TagsTab

File: apps/web/src/components/works/detail/items/TagsTab.tsx

Manages work tags with a searchable table and CRUD modal.

interface TagsTabProps {
workId: string;
initialTags: Tag[];
items: ItemData[];
canEdit: boolean;
}

Table Columns: Name (pill badge), Items Count, Actions (edit/delete)

CollectionsTab

File: apps/web/src/components/works/detail/items/CollectionsTab.tsx

Manages work collections with a searchable table and CRUD modal.

interface CollectionsTabProps {
workId: string;
initialCollections: Collection[];
items: ItemData[];
canEdit: boolean;
}

Table Columns: Name (with icon), Description, Items Count, Priority, Actions (edit/delete)

State Management Patterns

ItemsPageClient
|-- activeTab: TabType // which tab is shown
|-- isAddModalOpen: boolean // add item modal
|-- screenshotAvailable: boolean // from plugin check
|
+-- ItemsList
| |-- items: ItemData[] // local, supports add/delete/update
| |-- searchQuery: string
| |-- selectedCategory: string | null
| |-- viewMode: 'grid' | 'list'
|
+-- CategoriesTab
| |-- categories: Category[] // local CRUD state
| |-- searchQuery: string
| |-- isModalOpen: boolean
| |-- editingCategory: Category | null
|
+-- TagsTab / CollectionsTab // same pattern as CategoriesTab

All taxonomy operations (create, update, delete) are performed via server actions and the local state is updated optimistically.

ActionServer Action FunctionHTTP Method
Extract item detailsextractItemDetails(url, categories)POST
Capture screenshotcaptureScreenshot(url)POST
Check screenshot availabilitycheckScreenshotAvailability()GET
Create categorycreateCategory(workId, data)POST
Update categoryupdateCategory(workId, categoryId, data)PATCH
Delete categorydeleteCategory(workId, categoryId)DELETE
Create tagcreateTag(workId, data)POST
Update tagupdateTag(workId, tagId, data)PATCH
Delete tagdeleteTag(workId, tagId)DELETE
Create collectioncreateCollection(workId, data)POST
Update collectionupdateCollection(workId, collectionId, data)PATCH
Delete collectiondeleteCollection(workId, collectionId)DELETE

Internationalization

All strings use next-intl under these namespaces:

  • dashboard.workDetail.items -- page-level labels, tab names, search, counts
  • dashboard.workDetail.items.addModal -- add item form labels and messages
  • dashboard.workDetail.items.taxonomy -- categories, tags, collections CRUD

Cross-References