Skip to main content

FavoriteLists

Displays a user's favorite lists with full CRUD support: inline renaming, delete confirmation modal, and a create modal. Lists can be limited to the most recently modified N items, making the component suitable for both dedicated pages and compact dashboard widgets.

Usage

Full favorites page

All lists visible, with edit/delete actions and a create button.

import FavoriteLists from '@/components/propeller/FavoriteLists';
import { useAuth } from '@/context/AuthContext';
import { graphqlClient } from '@/lib/graphql';
import { useRouter } from 'next/navigation';

export default function FavoritesPage() {
const { state: authState } = useAuth();
const router = useRouter();

return (
<FavoriteLists
user={authState.user}
graphqlClient={graphqlClient}
onListClick={(id) => router.push(`/account/favorites/${id}`)}
/>
);
}

Dashboard widget with limit

Show only the 3 most recently modified lists, read-only, without the create button.

<FavoriteLists
user={authState.user}
graphqlClient={graphqlClient}
limit={3}
showActions={false}
allowFavoriteListCreate={false}
onListClick={(id) => router.push(`/account/favorites/${id}`)}
/>

Custom labels (localization)

Override any label for internationalization or branding.

<FavoriteLists
user={authState.user}
graphqlClient={graphqlClient}
onListClick={(id) => router.push(`/account/favorites/${id}`)}
labels={{
lastModified: 'Laatst gewijzigd',
items: 'artikelen',
defaultBadge: 'Standaard',
createButton: 'Nieuwe lijst',
createTitle: 'Nieuwe lijst aanmaken',
deleteTitle: 'Lijst verwijderen',
deleteConfirm: 'Weet je zeker dat je wilt verwijderen',
deleteWarning: 'Deze actie kan niet ongedaan worden gemaakt.',
noLists: 'Geen favorieten lijsten',
noListsDescription: 'Maak een nieuwe lijst aan om je producten op te slaan.',
createFirstList: 'Maak je eerste lijst',
}}
/>

Custom action handlers

Delegate create, edit, and delete to parent logic instead of using the built-in SDK calls.

<FavoriteLists
user={authState.user}
graphqlClient={graphqlClient}
onCreate={(data) => {
// data: { name: string, isDefault: boolean }
myCustomCreateHandler(data);
}}
onEdit={(listId, data) => {
myCustomEditHandler(listId, data);
}}
onDelete={(listId) => {
myCustomDeleteHandler(listId);
}}
onListClick={(id) => router.push(`/account/favorites/${id}`)}
/>

Minimal read-only display

Strip all metadata and actions for a compact list-name-only view.

<FavoriteLists
user={authState.user}
graphqlClient={graphqlClient}
showDefaultIndicator={false}
showLastModified={false}
showItemsCount={false}
showActions={false}
allowFavoriteListCreate={false}
onListClick={(id) => router.push(`/account/favorites/${id}`)}
/>

Configuration

Required

PropTypeDescription
userContact | Customer | nullThe authenticated user. Lists are fetched for this user's contactId (B2B) or customerId (B2C).
graphqlClientGraphQLClientInitialized SDK GraphQL client used for all API calls.

Display options

PropTypeDefaultDescription
limitnumberundefinedWhen set, shows only the last N modified lists (sorted by updatedAt descending). undefined shows all.
showDefaultIndicatorbooleantrueShow a "Default" badge next to the default list.
showLastModifiedbooleantrueShow the last modified date on each list card.
showItemsCountbooleantrueShow the total number of products and clusters in each list.
showActionsbooleantrueShow edit and delete buttons on each list card.
allowFavoriteListCreatebooleantrueShow the "Create New List" button (top of list and in empty state).
classNamestringundefinedCustom CSS class applied to the root container.

Callbacks

PropTypeDescription
onListClick(listId: string | number) => voidCalled when a list card is clicked. Typically used for navigation.
onCreate(data: FavoriteListFormData) => voidOverride default create behavior. When provided, the component delegates to this callback instead of calling the SDK.
onEdit(listId: string, data: FavoriteListFormData) => voidOverride default edit behavior. When provided, the component delegates to this callback instead of calling the SDK.
onDelete(listId: string) => voidOverride default delete behavior. When provided, the component delegates to this callback instead of calling the SDK.
onListChanged() => voidCalled after any list mutation (create, edit, delete) succeeds. Wire this to refreshUser() to keep the user object's favoriteLists in sync.

Formatting and labels

PropTypeDefaultDescription
formatDate(dateString: string) => stringdd/mm/YYYYCustom date formatter. Receives the raw ISO date string, returns the display string.
labelsobjectEnglish defaultsLocalization overrides (see Labels section below).

Types

interface FavoriteListFormData {
name: string;
isDefault: boolean;
}

This is the shape passed to onCreate and onEdit callbacks and used internally for create/update API calls.

Labels

All labels are optional. Provide any subset to override the English defaults.

KeyDefaultUsed in
lastModified"Last modified"Date label on each list card
items"items"Item count label on each list card
products"products"Product count (if shown separately)
clusters"clusters"Cluster count (if shown separately)
defaultBadge"Default"Badge text on the default list
editSave"Save"Save button in inline edit form
editCancel"Cancel"Cancel button in inline edit form
makeDefault"Make default"Checkbox label in edit form
deleteTitle"Delete Favorite List"Delete modal heading
deleteConfirm"Are you sure you want to delete"Delete modal body text
deleteWarning"This action cannot be undone."Delete modal warning
deleteButton"Delete"Delete modal confirm button
cancelButton"Cancel"Cancel button in modals
createTitle"Create New List"Create modal heading
createButton"Create New List"Create button above list
createPlaceholder"Enter list name"Input placeholder in create modal
setAsDefault"Set as default favorite list"Checkbox label in create modal
saveButton"Save"Save button in create modal
noLists"No favorite lists"Empty state heading
noListsDescription"Start by creating a new list to save your items."Empty state description
createFirstList"Create your first list"Empty state create button
loading"Loading..."Loading state text

Behavior

Optimistic updates

The component updates local state immediately after user actions to provide instant feedback, without waiting for the API response:

  • Rename: The list name and default status update in-place instantly. On API error, the full list is refetched from the server.
  • Delete: The list is removed from the displayed array instantly. On API error, the full list is refetched.
  • Create: After the API call succeeds, the full list is refetched to obtain the server-assigned ID and timestamps (no optimistic insert).
  • Set as default: When a list is marked as default, the component clears the isDefault flag on the previously default list both in local state and via a separate API call before applying the new default.

Modals

  • Create modal: Opens from the "Create New List" button. Contains a name input and a "Set as default" checkbox. The save button is disabled when the name is empty. The modal closes on save or cancel.
  • Delete modal: Opens when the delete button is clicked on a list card. Shows a confirmation prompt with the list name and a warning that the action is irreversible. Confirm triggers deletion; cancel closes the modal.

Inline editing

Clicking the edit button on a list card replaces the list name and metadata with an inline form containing a name input, a "Make default" checkbox, and save/cancel buttons. Only one list can be edited at a time. Clicking the list card while in edit mode does not trigger onListClick.

Sorting and limiting

When the limit prop is set, the component sorts all fetched lists by updatedAt descending (most recently modified first) and displays only the first N. Without limit, lists are displayed in the order returned by the API.

Duplicate submission prevention

A saving flag prevents double-submissions during async create/edit/delete operations. Buttons are implicitly disabled while a mutation is in flight.

Hydration safety

The component uses an isMounted state guard to prevent server/client hydration mismatches. Content renders only after the component has mounted on the client; a skeleton loader is shown during SSR.

Loading state

While lists are being fetched, the component renders animated placeholder skeleton cards. The create button and empty state are hidden during loading.

Empty state

When the user has no favorite lists, the component shows a centered empty state with a heart icon, a heading, a description, and (if allowFavoriteListCreate is true) a "Create your first list" button that opens the create modal.

User type detection

The component determines whether the user is a B2B Contact or B2C Customer by checking for the presence of contactId on the user object. This determines which search field (contactId or customerId) is used when fetching and creating lists.

SDK Services

The component uses FavoriteListService from propeller-sdk-v2 for all CRUD operations.

Service initialization

import { FavoriteListService, GraphQLClient } from 'propeller-sdk-v2';

const service = new FavoriteListService(graphqlClient);

Data source

The component reads lists directly from props.user.favoriteLists.items — it does not call getFavoriteLists() from the API. This means the user object (from AuthContext) must already contain populated favorite lists from the authentication response.

Lists are re-read whenever props.user changes. After mutations (create/edit/delete), the component calls onListChanged so the parent page can call refreshUser() to update the user object, which in turn updates the lists.

If you are building your own component, you can fetch lists from the API directly:

import { FavoriteListsSearchInput } from 'propeller-sdk-v2';

// For B2B Contact users
const searchInput: FavoriteListsSearchInput = {
contactId: user.contactId,
};

// For B2C Customer users
const searchInput: FavoriteListsSearchInput = {
customerId: user.customerId,
};

const response = await service.getFavoriteLists(searchInput);
const lists = response.items; // FavoriteList[]

Creating a list

await service.createFavoriteList({
name: 'My new list',
isDefault: false,
contactId: user.contactId, // or customerId for B2C
});

Renaming / updating a list

await service.updateFavoriteList(listId, {
name: 'Renamed list',
isDefault: true,
});

Deleting a list

await service.deleteFavoriteList(listId);

GraphQL Queries and Mutations

The SDK service methods correspond to the following Propeller GraphQL operations.

Query: list favorite lists

query FavoriteLists($input: FavoriteListsSearchInput) {
favoriteLists(input: $input) {
itemsFound
items {
id
name
isDefault
updatedAt
products {
itemsFound
items {
productId
}
}
clusters {
itemsFound
items {
clusterId
}
}
}
}
}

Variables:

{
"input": {
"contactId": 123
}
}

Mutation: create a favorite list

mutation CreateFavoriteList($input: FavoriteListCreateInput!) {
favoriteListCreate(input: $input) {
id
name
isDefault
}
}

Variables:

{
"input": {
"name": "Weekend project parts",
"isDefault": false,
"contactId": 123
}
}

Mutation: update a favorite list

mutation UpdateFavoriteList($id: ID!, $input: FavoriteListUpdateInput!) {
favoriteListUpdate(id: $id, input: $input) {
id
name
isDefault
}
}

Variables:

{
"id": "456",
"input": {
"name": "Renamed list",
"isDefault": true
}
}

Mutation: delete a favorite list

mutation DeleteFavoriteList($id: ID!) {
favoriteListDelete(id: $id)
}

Variables:

{
"id": "456"
}