Previously, you set up the foundation of your app. You're now ready to add app components that can help merchants create their pre-orders. On the app page, you want to add functionality that enables merchants to do the following tasks: - Set the pre-order name - Configure the delivery, inventory, and billing policies for the pre-order - Select products that will have the pre-order You'll use [Polaris](https://polaris.shopify.com) and [App Bridge React](/docs/api/app-bridge/previous-versions/app-bridge-from-npm/using-react) to build the user interface. You'll use GraphQL Admin API mutations and queries to create and retrieve pre-orders. At the end of this tutorial, you'll have an understanding on how to create pre-orders. This app will be simple, but you’ll learn where to find resources to build more complex features on your own. > Note: > The sample app in this tutorial is assigned with a `PRE_ORDER` category. When you create your own app, you can assign a category based on the app's purpose. For more information, refer to [Selling plans category](/docs/apps/build/purchase-options/deferred#selling-plan-categories). ## What you'll learn In this tutorial, you'll learn how to do the following tasks: - Add new routes to your app's server that call the Shopify Admin API - Add UI to create a pre-order - Test your app on a development store This tutorial is not intended to give you a full example on how to build a pre-order application, but more a reference starter. ## Requirements - Complete the [Getting started](/docs/apps/build/purchase-options/deferred/create-deferred-purchase-app/start-building) tutorial to set up the foundation of your app. - Familiarize yourself with [selling plans](/docs/apps/build/purchase-options) and [pre-orders and try before you buy](/docs/apps/build/purchase-options/deferred). ## Step 1: Create the pre-order creation route Create a file `app.create.jsx` under `app/routes`. This file will create the route `/app/create` that will let you create a pre-order. ## Step 2: Create app components Create the following app components: - [name](#create-the-name-component) - [checkout charge](#create-the-checkout-charge-component) - [product picker](#create-the-product-picker-component) > Tip: > We recommend keeping your app component files in the `app/components` folder. ### Create the name component Create a component that enables merchants to set the pre-order name. The name component includes the following Polaris components: - [`Card`](https://polaris.shopify.com/components/layout-and-structure/card) - [`TextField`](https://polaris.shopify.com/components/selection-and-input/text-field) ![A screen capture showing the name component](/assets/api/purchase-options/app-name.png) In your `app/routes/app.create.jsx` file, add the following code: ```jsx import { useState } from "react"; import { Page, Card, BlockStack, Layout, TextField, FormLayout, } from "@shopify/polaris"; export default function Index() { const [sellingPlanName, setSellingPlanName] = useState(""); return ( <> ); } ``` ### Create the checkout charge component When merchants create a pre-order, they can decide the initial charge when customers check out. The type of charge can be set with a `PERCENTAGE` or `PRICE` type. Learn more about [selling plan checkout charge types](/docs/api/admin-graphql/latest/enums/SellingPlanCheckoutChargeType) and [selling plan checkout charge values](/docs/api/admin-graphql/2024-01/unions/SellingPlanCheckoutChargeValue). The checkout charge component includes the following Polaris components: - [`Card`](https://polaris.shopify.com/components/layout-and-structure/card) - [`TextField`](https://polaris.shopify.com/components/selection-and-input/text-field) - [`Date picker`](https://polaris.shopify.com/components/selection-and-input/date-picker) ![A screen capture showing the deposit component](/assets/api/purchase-options/app-deposit.png) In the `app/components` folder, create the file `checkoutCharge.jsx`. ```jsx import { Text, Card, BlockStack, DatePicker, TextField, } from "@shopify/polaris"; import { useState } from "react"; export default function CheckoutCharge({ selectedDates, setSelectedDates, initialCheckoutCharge, setInitialCheckoutCharge, }) { const today = new Date(); const [{ month, year }, setDate] = useState({ month: today.getMonth(), year: today.getFullYear(), }); const handleMonthChange = (month, year) => setDate({ month, year }); return ( {initialCheckoutCharge < 100 && ( Remaining balance charge date )} ); } ``` ### Create the product picker component Create a component that enables merchants to select products that will have the pre-order. The product selection component includes the [Resource Picker API](/docs/api/app-bridge-library/apis/resource-picker). ![A screen capture showing the product selection component](/assets/api/purchase-options/app-product.png) In the `app/components` folder, create the file `ProductPicker.jsx`. ```jsx import { Link } from "@remix-run/react"; import { Text, Card, BlockStack, Button, TextField, Icon, InlineStack, Thumbnail, Box, } from "@shopify/polaris"; import { SearchIcon, ImageIcon } from "@shopify/polaris-icons"; export default function ProductPicker({ selectedProducts, setSelectedProducts, }) { async function selectProducts(selectedProducts, searchQuery) { const selectedItems = await window.shopify.resourcePicker({ selectionIds: selectedProducts, multiple: true, query: searchQuery, type: "product", action: "select", }); if (selectedItems) { setSelectedProducts(selectedItems); } } return (
} type="search" id="productSearch" name="productSearch" placeholder="Search products" autoComplete="off" value={""} />
{selectedProducts && selectedProducts.length ? ( {selectedProducts.map( ({ id, images, variants, title, totalVariants }, index) => { const hasImage = images && images.length; return (
{title} {totalVariants !== undefined ? ( ({variants?.length || totalVariants} of{" "} {totalVariants} variants selected) ) : null}
selectProducts(selectedProducts)} > Edit
); }, )}
) : null}
); } ``` ## Step 3: Action to create the pre-order The next step is to create the `action` that will make it possible to create the pre-order with all the information we are gathering with the previous created components. Add the action inside your `app/routes/app.create.jsx` file. The following action is currently showing a working example on how to use the [sellingPlanGroupCreate](/docs/api/admin-graphql/latest/mutations/sellingPlanGroupCreate) mutation to create a pre-order, this example can also be updated to create a Try before you buy. ```jsx export const action = async ({ request }) => { const { admin } = await authenticate.admin(request); const form = await request.formData(); const sellingPlanName = form.get("sellingPlanName"); const initialCheckoutCharge = form.get("initialCheckoutCharge"); const selectedProducts = form.get("selectedProducts"); const selectedProductsArray = selectedProducts ? selectedProducts.split(",") : []; const selectedDates = form.get("selectedDates"); const haveRemainingBalance = Number(initialCheckoutCharge) < 100; const response = await admin.graphql( `#graphql mutation sellingPlanGroupCreate($input: SellingPlanGroupInput!, $resources: SellingPlanGroupResourceInput!) { sellingPlanGroupCreate(input: $input, resources: $resources) { sellingPlanGroup { id } userErrors { field message } } }`, { variables: { input: { name: sellingPlanName, merchantCode: "Pre-order", options: ["pre-order"], position: 1, sellingPlansToCreate: [ { name: "Pre-order with deposit", options: "Pre-order with deposit", category: "PRE_ORDER", billingPolicy: { fixed: { checkoutCharge: { type: "PERCENTAGE", value: { percentage: Number(initialCheckoutCharge), }, }, remainingBalanceChargeTrigger: haveRemainingBalance ? "EXACT_TIME" : "NO_REMAINING_BALANCE", remainingBalanceChargeExactTime: haveRemainingBalance ? new Date(selectedDates).toISOString() : null, }, }, deliveryPolicy: { fixed: { fulfillmentTrigger: "UNKNOWN", }, }, inventoryPolicy: { reserve: "ON_FULFILLMENT", }, }, ], }, resources: { productIds: selectedProductsArray, }, }, }, ); const responseJson = await response.json(); return json({ sellingPlanGroup: responseJson.data?.sellingPlanGroupCreate?.sellingPlanGroup?.id, }); }; ``` ## Step 4: Complete create page The following code illustrate the complete code for the `app/routes/app.create.jsx` file. We use the [Toast API](/docs/api/app-bridge-library/apis/toast) to add feedback when a merchant is creating a new pre-order. ```jsx import { useState, useEffect } from "react"; import { json } from "@remix-run/node"; import { useActionData, useNavigation, useSubmit } from "@remix-run/react"; import { Page, Card, BlockStack, Button, PageActions, Layout, TextField, FormLayout, } from "@shopify/polaris"; import { authenticate } from "../shopify.server"; import ProductPicker from "../components/ProductPicker"; import CheckoutCharge from "../components/CheckoutCharge"; export const action = async ({ request }) => { const { admin } = await authenticate.admin(request); const form = await request.formData(); const sellingPlanName = form.get("sellingPlanName"); const initialCheckoutCharge = form.get("initialCheckoutCharge"); const selectedProducts = form.get("selectedProducts"); const selectedProductsArray = selectedProducts ? selectedProducts.split(",") : []; const selectedDates = form.get("selectedDates"); const haveRemainingBalance = Number(initialCheckoutCharge) < 100; const response = await admin.graphql( `#graphql mutation sellingPlanGroupCreate($input: SellingPlanGroupInput!, $resources: SellingPlanGroupResourceInput!) { sellingPlanGroupCreate(input: $input, resources: $resources) { sellingPlanGroup { id } userErrors { field message } } }`, { variables: { input: { name: sellingPlanName, merchantCode: "Pre-order", options: ["pre-order"], position: 1, sellingPlansToCreate: [ { name: "Pre-order with deposit", options: "Pre-order with deposit", category: "PRE_ORDER", billingPolicy: { fixed: { checkoutCharge: { type: "PERCENTAGE", value: { percentage: Number(initialCheckoutCharge), }, }, remainingBalanceChargeTrigger: haveRemainingBalance ? "EXACT_TIME" : "NO_REMAINING_BALANCE", remainingBalanceChargeExactTime: haveRemainingBalance ? new Date(selectedDates).toISOString() : null, }, }, deliveryPolicy: { fixed: { fulfillmentTrigger: "UNKNOWN", }, }, inventoryPolicy: { reserve: "ON_FULFILLMENT", }, }, ], }, resources: { productIds: selectedProductsArray, }, }, }, ); const responseJson = await response.json(); return json({ sellingPlanGroup: responseJson.data?.sellingPlanGroupCreate?.sellingPlanGroup?.id, }); }; export default function Index() { const nav = useNavigation(); const actionData = useActionData(); const submit = useSubmit(); const [selectedProducts, setSelectedProducts] = useState([]); const [sellingPlanName, setSellingPlanName] = useState(""); const [initialCheckoutCharge, setInitialCheckoutCharge] = useState(0); const handleSellingPlanNameChange = (newValue) => setSellingPlanName(newValue); const isLoading = ["loading", "submitting"].includes(nav.state) && nav.formMethod === "POST"; useEffect(() => { if (actionData?.sellingPlanGroup) { shopify.toast.show("Pre-order created", { isError: false, }); } }, [actionData]); const today = new Date(); const [selectedDates, setSelectedDates] = useState({ start: today, end: today, }); const createPreorder = () => submit( { selectedProducts: selectedProducts.map((product) => product.id), sellingPlanName, initialCheckoutCharge, selectedDates: initialCheckoutCharge < 100 ? selectedDates.start : null, }, { replace: true, method: "POST" }, ); return ( <> createPreorder()} > Create } /> ); } ``` ## Next steps The code that you just created is only a first step to create a complete pre-order or Try before you buy app. You can use the following API object to improve your application: - [SellingPlanCheckoutCharge](docs/api/admin-graphql/2024-01/objects/SellingPlanCheckoutCharge) that will let you decide how customers are charged with pre-order and TBYB. - [SellingPlanFixedBillingPolicy](/docs/api/admin-graphql/2024-01/objects/SellingPlanFixedBillingPolicy) that will let you decide how customers will handle the remain balance customer will need to pay for a pre-order and TBYB - [SellingPlanFixedDeliveryPolicy](/docs/api/admin-graphql/2024-01/objects/SellingPlanFixedDeliveryPolicy) that will let you decide how fulfillment will work with you pre-order and TBYB. - [SellingPlanInventoryPolicy](/docs/api/admin-graphql/2024-01/objects/SellingPlanInventoryPolicy) that will let you decide how the inventory will update with you pre-order and TBYB.