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

# Build a store-setup checklist

> Render an onboarding progress checklist on the vendor store-setup surface with a seller.setup widget — the seller is handed to you as data, no fetching required.

The vendor **store-setup / onboarding** surface is a widget zone (`seller.setup`)
that renders the full `seller` object as its `data`. That makes it the perfect
seam for an onboarding **checklist**: derive completion from the seller you're
handed, show what's left, and link each step to the page that completes it — all
from a single widget file, with nothing fetched and nothing forked.

<Info>
  **This is the pattern the built-in store-setup card uses.** The shipped
  "Complete store profile" card is itself a `seller.setup` widget that reads the
  `seller` and renders a progress list. This tutorial rebuilds it so you can
  shape your own onboarding steps.
</Info>

## What you'll build

A collapsible progress card on the store-setup surface that checks four parts of
the seller profile (details, address, company, payment), draws a progress bar,
and routes the vendor to the settings page for each incomplete step. When every
step is done, the card removes itself.

## Register the typed targets (once)

Widget zones are typed ids the vendor panel generates from its own pages and
ships as `@mercurjs/vendor/extension-targets`. Reference them once so `seller.setup`
autocompletes and a wrong zone fails `tsc`:

```typescript apps/vendor/src/extension-targets.d.ts theme={null}
/// <reference types="@mercurjs/vendor/extension-targets" />
```

## Build the checklist widget

<Steps>
  <Step title="Derive the steps from the seller">
    The `data` prop is the full `SellerDTO`. Compute each step's `completed` flag
    from it — no request needed. Give every step a `path` to the settings page
    that finishes it:

    ```tsx apps/vendor/src/widgets/store-setup-checklist.tsx theme={null}
    import "@mercurjs/vendor/extension-targets"
    import { SellerDTO } from "@mercurjs/types"

    type ProfileStep = {
      key: string
      label: string
      completed: boolean
      path: string
    }

    function getProfileSteps(seller: SellerDTO): ProfileStep[] {
      const hasStoreDetails = !!(seller.name && seller.email && seller.description)
      const hasAddress = !!(
        seller.address?.address_1 &&
        seller.address?.city &&
        seller.address?.country_code
      )
      const hasCompanyDetails = !!seller.professional_details?.corporate_name
      const hasPaymentDetails = !!(
        seller.payment_details?.holder_name &&
        seller.payment_details?.country_code
      )

      return [
        { key: "store_details", label: "Add store details", completed: hasStoreDetails, path: "/settings/store/edit" },
        { key: "address", label: "Add address", completed: hasAddress, path: "/settings/store/address" },
        { key: "company_details", label: "Add company details", completed: hasCompanyDetails, path: "/settings/store/professional-details" },
        { key: "payment_details", label: "Add payment details", completed: hasPaymentDetails, path: "/settings/store/payment-details" },
      ]
    }
    ```
  </Step>

  <Step title="Render the collapsible progress card">
    Use `@medusajs/ui` primitives and `@medusajs/icons` only. Draw a progress bar
    from the completed ratio, list each step with a status icon, and navigate on
    click. Return `null` when everything is done so the card disappears:

    ```tsx apps/vendor/src/widgets/store-setup-checklist.tsx theme={null}
    import { useMemo, useState } from "react"
    import { useNavigate } from "react-router-dom"
    import { Container, Text, clx } from "@medusajs/ui"
    import { CheckCircleSolid, TriangleDownMini, CircleDottedLine } from "@medusajs/icons"
    import { Collapsible as RadixCollapsible } from "radix-ui"

    const StoreSetupChecklist = ({ data: seller }: { data?: SellerDTO }) => {
      const navigate = useNavigate()
      const [open, setOpen] = useState(true)

      const steps = useMemo(
        () => (seller ? getProfileSteps(seller) : []),
        [seller]
      )

      if (!seller) {
        return null
      }

      const completedCount = steps.filter((s) => s.completed).length
      const totalCount = steps.length
      const progressPercent = (completedCount / totalCount) * 100

      if (completedCount === totalCount) {
        return null
      }

      return (
        <RadixCollapsible.Root open={open} onOpenChange={setOpen}>
          <Container className="overflow-hidden p-0">
            <div
              className="h-1 bg-ui-tag-green-icon transition-all duration-500"
              style={{ width: `${progressPercent}%` }}
            />
            <div className="p-6">
              <RadixCollapsible.Trigger asChild>
                <button className="flex w-full items-center justify-between">
                  <Text size="large" weight="plus" leading="compact">
                    Complete store profile
                  </Text>
                  <TriangleDownMini
                    className={clx(
                      "text-ui-fg-muted transition-transform duration-200",
                      !open && "-rotate-90"
                    )}
                  />
                </button>
              </RadixCollapsible.Trigger>

              <RadixCollapsible.Content>
                <div className="mt-4 flex flex-col gap-y-3">
                  {steps.map((step) => (
                    <button
                      key={step.key}
                      className="flex items-center gap-x-3 text-left"
                      onClick={() => !step.completed && navigate(step.path)}
                      disabled={step.completed}
                    >
                      {step.completed ? (
                        <CheckCircleSolid className="text-ui-tag-green-icon shrink-0" />
                      ) : (
                        <CircleDottedLine className="text-ui-fg-muted shrink-0" />
                      )}
                      <Text size="small" leading="compact" className="text-ui-fg-base">
                        {step.label}
                      </Text>
                    </button>
                  ))}
                </div>
              </RadixCollapsible.Content>
            </div>
          </Container>
        </RadixCollapsible.Root>
      )
    }
    ```
  </Step>

  <Step title="Attach it to the store-setup zone">
    Export the component as the **default** and a `config` with a `seller.setup`
    zone. `before` renders it above the built-in card; use `after` to place it
    below:

    ```tsx apps/vendor/src/widgets/store-setup-checklist.tsx theme={null}
    import { defineWidgetConfig } from "@mercurjs/dashboard-sdk"

    export const config = defineWidgetConfig({
      zone: "seller.setup.before",
    })

    export default StoreSetupChecklist
    ```
  </Step>
</Steps>

## Where it renders

`seller.setup` is hosted in two places, both passing the same `seller` as `data`:

| Host                                     | When it shows                                                |
| ---------------------------------------- | ------------------------------------------------------------ |
| The vendor shell (above the page outlet) | On top-level routes — the dashboard "home" onboarding banner |
| The store settings detail page           | Always, above the store status banner                        |

A single widget file covers both. Multiple `seller.setup.before` / `.after`
widgets stack in registration order, so your checklist and the built-in card can
coexist — or hide the built-in one by rendering your own and letting theirs
complete.

<Note>
  **No data fetching.** Because the zone hands you the resolved `seller`, the
  widget is pure render — you never call the SDK. To *collect and persist* a new
  onboarding value (e.g. a Tax ID) rather than just link to existing pages, see
  [Extend the onboarding flow](/rc/resources/tutorials/extend-onboarding), which
  carries a value through `additional_data` to a workflow hook.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Extend the onboarding flow" href="/rc/resources/tutorials/extend-onboarding">
    Collect a new value and persist it through a workflow hook.
  </Card>

  <Card title="Add a widget" href="/rc/resources/tutorials/add-a-widget">
    The full widget model and the published zone registry.
  </Card>
</CardGroup>
