Build your first custom dashboard app in minutes.
In this example, we will create a generic app that lists some resources: customers, markets, shipments, and SKUs. From the list, you will be able to click through to the detail page of each item. It is a simple but powerful way to understand the structure of a custom app and prepare for more advanced scenarios.
What you need before starting
To follow along, make sure you have:
- A Commerce Layer organization with some existing data (markets, customers, or SKUs).
- Node.js with pnpm installed.
- Basic knowledge of React and TypeScript.
Create your repository from the template
We maintain a repository called dashboard-apps
, which hosts the current dashboard apps released as open source project. Use it as a template to create your own repository by leveraging GitHub’s Create from template tool:
Once your repository is created, install the dependencies with the command below:
pnpm install
Inside the /apps
folder, you will find a starter template called my_sample_app
, which will serve as the foundation for our project alongside the other available apps.
Set up your app
The starter already includes the essentials as you can see checking the minimal structure provided withing the my_sample_app
folder. Its entry point (main.tsx
) mounts the TokenProvider
component, which manages authentication and reads the access token. The provider is already configured with kind="generic"
. This allows the app to fetch multiple types of resources instead of being restricted to just one set of permissions (as explain in the previous article):
You are free to rename the app folder and change the appSlug
value. This slug defines the URL path where your app will be accessible, in this example:
https://your-domain/my_sample_app
Add routes and pages
Next we will configure navigation. Open /src/data/routes.ts
and add the routes for your application. Besides the default home page, you will want to include a list view and a details view.
The starter app, along with all other apps, uses wouter for routing, but you are free to replace it with any library of your choice.
Create the pages
In the /src/pages
folder, create a new file for each of the routes you just defined and import them into App.tsx
:
For the home page we will keep things simple. Using components like HomePageLayout
, Spacer
, List
, and ListItem
you can display a grid of resource types such as customers, markets, shipments, and SKUs.
If you want to dive deeper into the components we used, check out the App Elements library documentation. It is packed with examples and explanations.
import { appRoutes } from '#data/routes'
import {
formatResourceName,
HomePageLayout,
Icon,
List,
ListItem,
Spacer
} from '@commercelayer/app-elements'
import type { ListableResourceType } from '@commercelayer/sdk'
import type { FC } from 'react'
import { useLocation } from 'wouter'
const resources: ListableResourceType[] = [
'customers',
'markets',
'shipments',
'skus'
]
const Page: FC = () => {
const [, setLocation] = useLocation()
return (
<HomePageLayout title='My Sample App'>
<Spacer top='14'>
<List title='Resources'>
{resources.map((resourceType) => (
<ListItem
key={resourceType}
onClick={() => {
setLocation(appRoutes.list.makePath({ resourceType }))
}}
>
{formatResourceName({
resource: resourceType,
format: 'title',
count: 'plural'
})}
<Icon name='caretRight' />
</ListItem>
))}
</List>
</Spacer>
</HomePageLayout>
)
}
export default Page
The list page goes one step further. For this page, we will combine simple UI elements like PageLayout
and ListItem
with the useResourceList
hook, which takes care of fetching data and even supports infinite scrolling out of the box.
import { appRoutes } from '#data/routes'
import {
Icon,
ListItem,
PageLayout,
type PageProps,
Text,
formatResourceName,
useResourceList
} from '@commercelayer/app-elements'
import type { FC } from 'react'
import { useLocation } from 'wouter'
const Page: FC<PageProps<typeof appRoutes.list>> = ({ params }) => {
const { resourceType } = params
const [, setLocation] = useLocation()
const { ResourceList, isLoading } = useResourceList({
type: resourceType,
query: {
pageSize: 25
}
})
if (isLoading) {
return null
}
return (
<PageLayout
title={formatResourceName({
resource: resourceType,
format: 'title',
count: 'plural'
})}
navigationButton={{
label: 'Select type',
onClick: () => {
setLocation(appRoutes.home.makePath({}))
}
}}
>
<ResourceList
title='All'
ItemTemplate={({ resource }) => {
if (resource == null) {
return null
}
return (
<ListItem
onClick={() => {
setLocation(
appRoutes.details.makePath({
resourceType,
resourceId: resource.id
})
)
}}
>
<div>
<Text tag='div' weight='semibold'>
#{resource.id}
</Text>
<Text tag='div' variant='info'>
{/* for customers */}
{'email' in resource && resource.email}
{/* for other markets and skus */}
{'name' in resource && resource.name}
{/* for other shipments */}
{'number' in resource && resource.number}
</Text>
</div>
<Icon name='caretRight' />
</ListItem>
)
}}
/>
</PageLayout>
)
}
export default Page
Finally, the details page will use the useCoreApi
hook to fetch a single resource. To display the information we’ll use components such as ResourceDetails
and ResourceMetadata
from App Elements.
import { appRoutes } from '#data/routes'
import {
EmptyState,
PageLayout,
type PageProps,
ResourceDetails,
ResourceMetadata,
Spacer,
formatResourceName,
useCoreApi
} from '@commercelayer/app-elements'
import type { FC } from 'react'
import { useLocation } from 'wouter'
const Page: FC<PageProps<typeof appRoutes.details>> = ({ params }) => {
const { resourceType, resourceId } = params
const [, setLocation] = useLocation()
const { data, isLoading, mutate } = useCoreApi(resourceType, 'retrieve', [
resourceId
])
return (
<PageLayout
title={formatResourceName({
resource: resourceType,
format: 'title',
count: 'singular'
})}
navigationButton={{
label: 'Back',
onClick: () => {
setLocation(appRoutes.list.makePath({ resourceType }))
}
}}
>
{isLoading ? (
<div>Loading...</div>
) : data == null ? (
<EmptyState title='Resource not found' />
) : (
<>
<Spacer bottom='14'>
<ResourceDetails
resource={data}
onUpdated={async () => {
void mutate()
}}
/>
</Spacer>
<ResourceMetadata
resourceType={resourceType}
resourceId={resourceId}
/>
</>
)}
</PageLayout>
)
}
export default Page
Run it locally
To test your work, start the dev server:
pnpm dev
Go to http://localhost:5173 and you will see a list of all the apps included in this project. You are free to remove any apps from the /apps
directory that you do not need, or keep them if you want to modify them later:
Before navigating the apps, you will need an access token in query string, as described in the second step when we introduced the TokenProvider
in main.tsx
.
Keep in mind that each app requires its own specific access token. Luckily, during development, you can use a single integration token. This token can be generated either via the API or by using the Commerce Layer CLI.
Once you have your development token, simply append it to the URL to open your app:
http://localhost:5173/?accessToken=your_token_here
Deploy and connect to your dashboard
When you are ready, deploy your app to the hosting service of your choice. Then simply add its URL to your Commerce Layer dashboard. From that moment, your entire team can use it as if it was part of the default environment:
Unlocking new possibilities
The custom app we just built is intentionally simple. Yet it shows how easy it is to extend the dashboard and adapt it to your needs.
With App Elements and our starter repo, you have everything you need to create powerful tools that improve your workflows.
Custom apps unlock a new level of flexibility. Whether you are cloning and tweaking an existing app or starting from scratch, you now have the freedom to shape the dashboard around your business.