> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craveup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React Quickstart

> Build a direct-to-Crave storefront with React, Vite, and the Storefront SDK.

Build a browser storefront with React and `@craveup/storefront-sdk`. Public catalog reads are anonymous. Cart mutations use the short-lived capability returned when the cart is created; no API key or BFF is required.

## Prerequisites

* Node.js 20+ and a package manager
* A published Crave merchant and location
* The public Crave API origin for your environment
* The exact deployed browser origin registered with Crave

## 1. Create the app and install the SDK

```bash theme={null}
npm create vite@latest my-storefront -- --template react-ts
cd my-storefront
npm install --save-exact @craveup/storefront-sdk@2.0.1
```

## 2. Configure public identifiers

```env filename=".env" theme={null}
VITE_CRAVEUP_API_URL=https://api.craveup.com
VITE_LOCATION_ID=loc_your_location_id
VITE_MERCHANT_SLUG=your-restaurant-slug
```

These values identify a public API and published resources. Do not put Crave admin, partner, payment-provider, or integration secrets in a `VITE_` variable.

## 3. Create a tab-scoped session store

```ts filename="src/lib/storefront.ts" theme={null}
import {
  createStorefrontClient,
  type StorefrontCartSession,
  type StorefrontSessionStore,
} from "@craveup/storefront-sdk";

const apiUrl = import.meta.env.VITE_CRAVEUP_API_URL;
const merchantSlug = import.meta.env.VITE_MERCHANT_SLUG;
if (!apiUrl || !merchantSlug) throw new Error("Missing Storefront configuration");

const apiOrigin = new URL(apiUrl).origin;
const storageKeyPart = (value: string) =>
  encodeURIComponent(value).replaceAll(".", "%2E");
const environmentNamespace = storageKeyPart(apiOrigin);
const scope = `${environmentNamespace}.${storageKeyPart(merchantSlug)}`;
const storageKey = (
  kind: "cart" | "customer" | "receipt",
  resourceId: string,
) => `storefront.${kind}.v1.${scope}.${storageKeyPart(resourceId)}`;
const cartKey = (locationId: string) => storageKey("cart", locationId);
const customerKey = storageKey("customer", "jwt");
const receiptKey = (receiptId: string) => storageKey("receipt", receiptId);

function readCartSession(locationId: string): StorefrontCartSession | null {
  const key = cartKey(locationId);
  const raw = sessionStorage.getItem(key);
  if (!raw) return null;

  try {
    const value = JSON.parse(raw) as Partial<StorefrontCartSession>;
    if (
      value.locationId !== locationId ||
      typeof value.cartId !== "string" ||
      typeof value.revision !== "number" ||
      !Number.isInteger(value.revision) ||
      value.revision < 0 ||
      (value.accessToken !== undefined && typeof value.accessToken !== "string")
    ) {
      throw new Error("Invalid cart session");
    }
    return value as StorefrontCartSession;
  } catch {
    sessionStorage.removeItem(key);
    return null;
  }
}

export const sessionStore: StorefrontSessionStore = {
  get(locationId) {
    return readCartSession(locationId);
  },
  set(session) {
    sessionStorage.setItem(
      cartKey(session.locationId),
      JSON.stringify(session),
    );
  },
  clear(locationId) {
    sessionStorage.removeItem(cartKey(locationId));
  },
};

export const customerTokenStore = {
  get: () => sessionStorage.getItem(customerKey),
  set: (token: string) => sessionStorage.setItem(customerKey, token),
  clear: () => sessionStorage.removeItem(customerKey),
};

export const receiptTokenStore = {
  get: (receiptId: string) => sessionStorage.getItem(receiptKey(receiptId)),
  set: (receiptId: string, token: string) =>
    sessionStorage.setItem(receiptKey(receiptId), token),
  clear: (receiptId: string) =>
    sessionStorage.removeItem(receiptKey(receiptId)),
};

export const storefront = createStorefrontClient({
  baseUrl: apiOrigin,
  sessionStore,
  getAuthToken: customerTokenStore.get,
});
```

The SDK attaches `X-Cart-Token`, `If-Match`, and `Idempotency-Key` where required and updates the stored cart revision from response ETags. The canonical API environment, merchant, and resource scopes prevent credentials from crossing storefronts. Save the verified customer JWT with `customerTokenStore.set(token)`; save a fragment-delivered receipt capability only with `receiptTokenStore.set(receiptId, token)` and remove it after terminal display.

## 4. Fetch the published merchant

```tsx filename="src/App.tsx" theme={null}
import { useEffect, useState } from "react";
import type { MerchantApiResponse } from "@craveup/storefront-sdk";
import { storefront } from "./lib/storefront";

export default function App() {
  const [merchant, setMerchant] = useState<MerchantApiResponse | null>(null);

  useEffect(() => {
    storefront.merchant
      .getBySlug(import.meta.env.VITE_MERCHANT_SLUG)
      .then(setMerchant);
  }, []);

  if (!merchant) return <p>Loading...</p>;
  return <h1>{merchant.name}</h1>;
}
```

## 5. Start a cart and add an item

```ts theme={null}
const locationId = import.meta.env.VITE_LOCATION_ID;
const session = await storefront.orderingSessions.start(locationId, {
  marketplaceId: "web",
  fulfillmentMethod: "takeout",
});

const cart = await storefront.cart.addItem(locationId, session.cart.id, {
  productId: "prod_margherita",
  quantity: 1,
  selections: [],
  itemUnavailableAction: "remove_item",
});
```

Keep the cart capability in tab-scoped storage. On `CART_CONFLICT`, refresh the cart and ask the user to retry; do not replay the mutation automatically. Clear the cart capability after claim, deletion, expiry, or terminal checkout handling, and clear the merchant-scoped JWT on logout.

`marketplaceId` is an optional source-attribution label, such as `"web"`; it is
not a location ID. The route already scopes the cart to `locationId`.

## 6. Register the browser origin

Register the exact deployed origin—including scheme, hostname, and any non-default port—with Crave before testing. Register stable preview origins separately; wildcard origins are not accepted.

## Next steps

<CardGroup cols={2}>
  <Card title="Display Menu" icon="utensils" href="/guides/display-menu">
    Fetch the published menu and product data.
  </Card>

  <Card title="Manage Cart" icon="cart-shopping" href="/guides/manage-cart">
    Handle capabilities, revisions, and idempotent mutations.
  </Card>

  <Card title="Checkout Flow" icon="credit-card" href="/guides/checkout-flow">
    Create a payment intent and poll the authoritative order result.
  </Card>

  <Card title="SDK Reference" icon="code" href="/getting-started/storefront-sdk">
    See the complete direct API client.
  </Card>
</CardGroup>

<Snippet file="need-help.mdx" />
