# Types: BundleItem to PersonalizationIssue (/themes/sdk/reference/product/types-6)

```ts
import { … } from '@poltura/theme-sdk/product';
```

## `BundleItem` [#bundleitem]

One item a bundle holds.

<TypeTable type={{"id":{"description":"The bundle item's id; quotes and selections name it.","type":"string","required":true},"productId":{"description":"The item's product id.","type":"string","required":true},"productHandle":{"description":"The item's product URL segment, or null.","type":"string | null","required":true},"variantId":{"description":"The item's variant id.","type":"string","required":true},"title":{"description":"The item's product title.","type":"string","required":true},"variantTitle":{"description":"The variant's title, or null for a single-variant product.","type":"string | null","required":true},"image":{"description":"The item's image, or null.","type":"MediaImageRef | null","required":true},"quantity":{"description":"How many of it the bundle holds.","type":"number","required":true},"groupId":{"description":"The group the item is chosen in, or null for an ungrouped item.","type":"string | null","required":true},"optional":{"description":"Whether the shopper may leave it out.","type":"boolean","required":true}}} />

## `BundleQuote` [#bundlequote]

The price of a bundle with the shopper's choice in it.

<TypeTable type={{"accepted":{"description":"The bundle was priced.","type":"true","required":true}}} />

## `BundleQuoteInput` [#bundlequoteinput]

What to price: one bundle with the shopper's choice in it.

<TypeTable type={{"bundleId":{"description":"The bundle's id.","type":"string","required":true},"variantId":{"description":"The variant of the product the bundle is offered with, when it has several.","type":"string","required":false},"items":{"description":"The chosen items; all of them when omitted.","type":"readonly { readonly itemId: string; readonly variantId?: string; readonly quantity?: number; }[]","required":false},"quantity":{"description":"How many bundles; one by default.","type":"number","required":false}}} />

## `BundleQuoteRefusal` [#bundlequoterefusal]

Why a bundle could not be priced.

```ts
type BundleQuoteRefusal = 'stale' | 'expired' | 'currencyMismatch' | 'missingPrice' | 'invalidSelection' | 'inactive' | 'invalidPolicy' | 'unavailable'
```

## `BundleRecommendation` [#bundlerecommendation]

A bundle recommended for a product, with the choice that suits the shopper and its price.

<TypeTable type={{"bundleId":{"description":"The bundle's id.","type":"string","required":true},"title":{"description":"The bundle's title.","type":"string","required":true},"reason":{"description":"Why it is recommended.","type":"BundleRecommendationReason","required":true},"items":{"description":"The recommended items and their quantities, by bundle item id.","type":"readonly { readonly itemId: string; readonly variantId: string; readonly quantity: number; }[]","required":true},"total":{"description":"The price of the recommended choice.","type":"Money","required":true},"compareAt":{"description":"The price of the same items bought alone.","type":"Money","required":true}}} />

## `BundleRecommendationReason` [#bundlerecommendationreason]

Why a bundle is recommended with the product.

```ts
type BundleRecommendationReason = 'completeTheSet' | 'boughtTogether' | 'bundleAndSave' | 'rewardOffer'
```

## `WishlistButtonProps` [#wishlistbuttonprops]

Props of [`WishlistButton`](/themes/sdk/reference/product/parts-2#wishlistbutton).

<TypeTable type={{"productId":{"description":"The id of the product the heart saves.","type":"string","required":true},"title":{"description":"The product's title; it names the button for assistive technology and the saved product in analytics.","type":"string","required":true},"categoryLabel":{"description":"The product's category name, recorded with the save in analytics.","type":"string | null","required":false},"price":{"description":"The price recorded with the save in analytics; a card's `price`.","type":"PriceView","required":false},"placement":{"description":"`card` draws a round heart over a card's top-right image corner; `page` a square bordered one for a buy row.","type":"'card' | 'page'","required":false},"className":{"description":"Classes added to the button.","type":"string","required":false}}} />

## `WishlistLinkProps` [#wishlistlinkprops]

Props of [`WishlistLink`](/themes/sdk/reference/product/parts-2#wishlistlink).

<TypeTable type={{"showCount":{"description":"Whether the number of saved products shows on the heart; true by default.","type":"boolean","required":false},"className":{"description":"Classes added to the link.","type":"string","required":false}}} />

## `WishlistProduct` [#wishlistproduct]

The product a `toggle` saves or removes.

<TypeTable type={{"id":{"description":"The product's id.","type":"string","required":true},"title":{"description":"The product's title, recorded with a save in analytics.","type":"string","required":true}}} />

## `WishlistState` [#wishliststate]

The shopper's favourites.

<TypeTable type={{"enabled":{"description":"Whether the store has favourites turned on; when false nothing about favourites should show.","type":"boolean","required":true},"count":{"description":"How many products are saved.","type":"number","required":true},"isSaved":{"description":"Whether the product is saved.","type":"(productId: string) => boolean","required":true},"toggle":{"description":"Saves a product that is not saved and removes one that is.","type":"(product: WishlistProduct) => Promise<boolean>","required":true}}} />

## `GiftCardDraft` [#giftcarddraft]

What the shopper fills in to send a gift card.

<TypeTable type={{"recipient":{"description":"Whether the card is for someone else or for the shopper.","type":"'someone' | 'self'","required":true},"recipientName":{"description":"The recipient's name.","type":"string","required":true},"recipientEmail":{"description":"The address the card is sent to.","type":"string","required":true},"senderName":{"description":"The name the card comes from.","type":"string","required":true},"message":{"description":"The message on the card.","type":"string","required":true},"delivery":{"description":"Whether the card goes out at once or on `sendOn`.","type":"'now' | 'schedule'","required":true},"sendOn":{"description":"The day the card goes out, as `YYYY-MM-DD`, when delivery is scheduled.","type":"string","required":true}}} />

## `GiftCardIssue` [#giftcardissue]

Why a gift card field is not accepted.

```ts
type GiftCardIssue = 'required' | 'invalidEmail' | 'messageTooLong' | 'schedulingDisabled' | 'sendInPast' | 'sendTooFar'
```

## `GiftCardState` [#giftcardstate]

A gift card product's amount, recipient and delivery as the shopper fills them in.

<TypeTable type={{"kind":{"description":"Whether the card is loaded with an amount or redeems for a set item.","type":"GiftCardKind","required":true},"draft":{"description":"What the shopper has filled in.","type":"GiftCardDraft","required":true},"setDraft":{"description":"Changes parts of the draft.","type":"(patch: Partial<GiftCardDraft>) => void","required":true},"issues":{"description":"What is wrong with a field; shown once the shopper tries to add the card.","type":"Readonly<Partial<Record<'recipientEmail' | 'message' | 'sendOn', GiftCardIssue>>>","required":true},"messageMax":{"description":"The longest message accepted.","type":"number","required":true},"canSchedule":{"description":"Whether the merchant lets cards be sent on a later day.","type":"boolean","required":true},"amounts":{"description":"The amounts to choose from, priced in the shopper's currency; empty for a voucher.","type":"readonly { readonly variantId: string; readonly amount: Money; readonly label: string; }[]","required":true},"selectedVariantId":{"description":"The variant of the chosen amount, or null.","type":"string | null","required":true},"selectAmount":{"description":"Chooses an amount.","type":"(variantId: string) => void","required":true},"voucher":{"description":"The item a voucher redeems for, or null for an amount card.","type":"{ readonly title: string; readonly href: string | null; } | null","required":true}}} />

## `PersonalizationIssue` [#personalizationissue]

Why a personalisation text is not accepted.

```ts
type PersonalizationIssue = 'required' | 'tooLong'
```
