import {useCallback, useState} from 'react' import { ContentVisibility, Content, ContentCreateUserErrors, } from '@shopify/shop-minis-platform' import {useHandleAction} from '../../internal/useHandleAction' import {useShopActions} from '../../internal/useShopActions' import {useImageUpload} from '../storage/useImageUpload' export interface CreateImageContentParams { /** * The image file to upload. */ image: File /** * The title for the content entry. */ contentTitle: string /** * Visibility options for the content. Use `['DISCOVERABLE']` to appear in * recommendations, `['LINKABLE']` to enable shareable URLs, or both. Pass * `null` or `[]` to keep content private within your Mini. */ visibility?: ContentVisibility[] | null /** * A unique identifier from your own system that lets you look up content * later via `ContentWrapper` using an ID you already know. * If not provided, the content can only be retrieved by its `publicId`. * Each `externalId` must be unique per Mini — creating content with a * duplicate returns a `DUPLICATE_EXTERNAL_ID` error. */ externalId?: string /** * A text description for the content. Displayed alongside the image in Shop * surfaces such as feeds and content detail views. Use this to provide * context about the image, such as a caption or review text. */ description?: string /** * An array of Shopify product GIDs (e.g. `'gid://shopify/Product/123'`) to * associate with the content. Maximum 20 products. Associated products * appear alongside the content, enabling shoppable content experiences. */ productIds?: string[] } export interface UseCreateImageContentReturns { /** * Upload an image and create content. */ createImageContent: ( params: CreateImageContentParams ) => Promise<{data: Content; userErrors?: ContentCreateUserErrors[]}> /** * Whether the content is being created. */ loading: boolean } export const useCreateImageContent = (): UseCreateImageContentReturns => { const {createContent} = useShopActions() const {uploadImage} = useImageUpload() const [loading, setLoading] = useState(false) const createImageContent = useCallback( async (params: CreateImageContentParams) => { setLoading(true) const { image, contentTitle, visibility, externalId, description, productIds, } = params if (!image.type) { throw new Error('Unable to determine file type') } if (!image.type.startsWith('image/')) { throw new Error('Invalid file type: must be an image') } const [uploadImageResult] = await uploadImage(image) const uploadImageUrl = uploadImageResult.imageUrl if (!uploadImageUrl) { throw new Error('Image upload failed') } const createContentResult = await createContent({ title: contentTitle, imageUrl: uploadImageUrl, visibility, externalId, description, productIds, }) setLoading(false) return createContentResult }, [createContent, uploadImage] ) return { createImageContent: useHandleAction(createImageContent), loading, } } /** * The `useCreateImageContent` hook combines image upload with content creation to generate user-generated content entries. Built on top of `useImageUpload`, it extends the basic upload functionality with content management features including titles, descriptions, visibility controls, product associations, and unique IDs for sharing and discovery. * * ### Parameters * * The `createImageContent` function accepts an object with the following fields: * * - **`image`** (required): A `File` object representing the image to upload. * - **`contentTitle`** (required): The title for the content entry. * - **`visibility`** (optional): An array of `ContentVisibility` values (see below). * - **`externalId`** (optional): A unique identifier from your own system that lets you look up content later via `ContentWrapper` using an ID you already know. If not provided, the content can only be retrieved by its `publicId`. Each `externalId` must be unique per Mini — creating content with a duplicate `externalId` returns a `DUPLICATE_EXTERNAL_ID` error. * - **`description`** (optional): A text description for the content. Displayed alongside the content in Shop surfaces such as feeds and content detail views. Use this to provide context about the image, such as a caption or review text. * - **`productIds`** (optional): An array of Shopify product GIDs (e.g. `'gid://shopify/Product/123'`) to associate with the content. Maximum 20 products. Associated products appear alongside the content, enabling shoppable content experiences. If any product IDs are ineligible, the mutation will return an `INELIGIBLE_PRODUCTS` error. * * ### Visibility Options * * The `visibility` parameter accepts an optional array of `ContentVisibility` values: * * - **`DISCOVERABLE`**: Makes content eligible for Shop's recommendation and discovery systems. Your content can appear in feeds and recommendations across the Shop app. * - **`LINKABLE`**: Enables shareable URLs for the content. When set, the created `Content` object includes a `shareableUrl` field containing a URL that can be shared externally. The shareable page renders OpenGraph meta tags (`og:title`, `og:image`, `og:description`) using the content's `contentTitle`, uploaded image, and `description` — so link previews in social media, messaging apps, and other platforms will display a rich card with the content's title, image, and description. Use the `useShare` hook to trigger the native share sheet with this URL. * * ### Error Handling * * The hook may return `userErrors` in the response with the following codes: * * - **`DUPLICATE_EXTERNAL_ID`**: Returned when an `externalId` is provided that already exists for this Mini. Each `externalId` must be unique per content entry. * - **`INELIGIBLE_PRODUCTS`**: Returned when one or more `productIds` refer to products that are not eligible for Shop. The error message includes the specific ineligible product GIDs. * * ### Use Cases * * - **Shareable content with rich link previews**: Include `LINKABLE` in the `visibility` array and provide a `contentTitle`, `description`, and image. The returned `shareableUrl` points to a page with OpenGraph tags, so when shared via social media or messaging apps the link preview displays the content's title, image, and description as a rich card. Pass the `shareableUrl` to the `useShare` hook to open the native share sheet. * - **User reviews with photos**: Provide `description` for the review text, `productIds` to link the reviewed products, and `['DISCOVERABLE', 'LINKABLE']` visibility for maximum reach. * - **Shoppable lookbooks**: Upload styled images with associated `productIds` so users can shop the look. * - **Content lookup by your own ID**: Set `externalId` to an identifier from your system (e.g. a database row ID or slug) so you can retrieve the content later via `ContentWrapper` without storing the `publicId`. * * The hook handles both the image upload pipeline and content entity creation in a single operation, automatically associating content with your Mini and integrating with Shop's content systems. * * > Caution: You must run the [`setup`](/docs/api/shop-minis/commands/setup) CLI command before using this hook so the content can be associated with the Mini. * @publicDocs */ export type UseCreateImageContentGeneratedType = () => UseCreateImageContentReturns