Skip to main content

Organizing your dashboard

We now having a working Interval action! In this section we'll add additional structure and organization to our Interval dashboard, adding some hierarchical navigation and grouping related actions within a page.

info

The Pages API is available on SDK versions v0.35.0 and higher.

Pages are still in beta. The API names and functionality may change.

Creating a page

By default all the Interval actions you create will live at the top-level in your dashboard. We provide ways to filter and search for the tool you're looking for, but you'll probably want to create pages to better organize your actions however you see fit.

To create a page, define and pass in a Page class in when you instantiate Interval.

src/index.ts
import Interval, { Page, io, ctx } from "@interval/sdk";
import "dotenv/config"; // loads environment variables from .env
import { getCharges, refundCharge } from "./payments";

const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
routes: {
refunds: new Page({
name: "Refunds",
}),
refund_user: async () => {
const customerEmail = await io.input.email(
"Email of the customer to refund:"
);

console.log("Email:", customerEmail);

const charges = await getCharges(customerEmail);

const chargesToRefund = await io.select.table(
"Select one or more charges to refund",
{
data: charges,
}
);

await ctx.loading.start({
title: "Refunding charges",
// Because we specified `itemsInQueue`, Interval will render a progress bar versus an indeterminate loading indicator.
itemsInQueue: chargesToRefund.length,
});

for (const charge of chargesToRefund) {
await refundCharge(charge.id);
await ctx.loading.completeOne();
}

// Values returned from actions are automatically stored with Interval transaction logs
return { chargesRefunded: chargesToRefund.length };
},
},
});

// Establishes a persistent connection between Interval and your app.
interval.listen();

Pages may be passed in under routes alongside any actions you want to create. The provided slug configures the URL path for the page, just as it does with actions, and must be unique at that level of navigation.

Once you've defined your page, if you revisit the Interval dashboard, you'll now see top-level navigation for reaching your page.

The created page can also act as a "sub-folder" for grouping actions. Simply pass actions in to the page's routes property, just as you would do at the top level. Here we'll move our refund tool to our new "Refunds" page, and also provide an explicit name for a cleaner display:

src/index.ts
import Interval, { Page, io, ctx } from "@interval/sdk";
import "dotenv/config"; // loads environment variables from .env
import { getCharges, refundCharge } from "./payments";

const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
routes: {
refunds: new Page({
name: "Refunds",
routes: {
refund_user: {
name: "Create refund",
handler: async () => {
const customerEmail = await io.input.email(
"Email of the customer to refund:"
);

console.log("Email:", customerEmail);

const charges = await getCharges(customerEmail);

const chargesToRefund = await io.select.table(
"Select one or more charges to refund",
{
data: charges,
}
);

await ctx.loading.start({
title: "Refunding charges",
// Because we specified `itemsInQueue`, Interval will render a progress bar versus an indeterminate loading indicator.
itemsInQueue: chargesToRefund.length,
});

for (const charge of chargesToRefund) {
await refundCharge(charge.id);
await ctx.loading.completeOne();
}

// Values returned from actions are automatically stored with Interval transaction logs
return { chargesRefunded: chargesToRefund.length };
},
},
},
}),
},
});

// Establishes a persistent connection between Interval and your app.
interval.listen();

You can repeat this process to add as many pages as you like to your dashboard, grouping related tools and information together on separate pages to help organize your dashboard and make it easier to navigate. Pages can also be nested arbitrarily!

Rendering content

Currently our page simply acts as a sub-folder for grouping actions. You can also add content to each page to provide more information and context for the tools and data on that page. This could include text descriptions, metadata cards, tables, images, code snippets, and any other content supported via Interval display I/O methods.

We'll flesh out our new page with a description, stats on the number of recent refunds, and a table listing the refunds we've made.

To accomplish this, we'll define a handler function when instantiating the Page class. This function should load any required data and return a Layout class that defines the content of the page.

src/index.ts
import Interval, { Page, io, ctx } from "@interval/sdk";
import "dotenv/config"; // loads environment variables from .env
import { getCharges, refundCharge, getRefunds } from "./payments";

const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
routes: {
refunds: new Page({
name: "Refunds",
handler: async () => {
const refunds = await getRefunds();

return new Layout({
title: "Refunds",
description: "View and create refunds for our customers.",
children: [
io.display.metadata("", {
layout: "card",
data: [
{
label: "Total refunds",
value: refunds.length,
},
],
}),
io.display.table("Refunds", {
data: refunds,
}),
],
});
},
routes: {
refund_user: {
name: "Create refund",
handler: async () => {
const customerEmail = await io.input.email(
"Email of the customer to refund:"
);

console.log("Email:", customerEmail);

const charges = await getCharges(customerEmail);

const chargesToRefund = await io.select.table(
"Select one or more charges to refund",
{
data: charges,
}
);

await ctx.loading.start({
title: "Refunding charges",
// Because we specified `itemsInQueue`, Interval will render a progress bar versus an indeterminate loading indicator.
itemsInQueue: chargesToRefund.length,
});

for (const charge of chargesToRefund) {
await refundCharge(charge.id);
await ctx.loading.completeOne();
}

// Values returned from actions are automatically stored with Interval transaction logs
return { chargesRefunded: chargesToRefund.length };
},
},
},
}),
},
});

// Establishes a persistent connection between Interval and your app.
interval.listen();

Adding menu items

While actions nested within a page provide organizational structure within your dashboard, you can also customize the UI of pages to provide quick access to useful actions via the menuItems property.

menuItems add easy-to-access buttons at the top of any page that can link to any actions within your Interval dashboard or to external sites (menuItems elements have the same signature as Interval links).

For actions that are set as menu items, you can avoid showing a duplicate link in the page sidebar by setting the unlisted property on the action definition.

src/index.ts
import Interval, { Page, io, ctx } from "@interval/sdk";
import "dotenv/config"; // loads environment variables from .env
import { getCharges, refundCharge, getRefunds } from "./payments";

const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
routes: {
refunds: new Page({
name: "Refunds",
handler: async () => {
const refunds = await getRefunds()

return new Layout({
title: 'Refunds',
description: 'View and create refunds for our customers.',
menuItems: [
{
label: 'Create refund',
route: 'refunds/refund_user',
},
],
children: [
io.display.metadata('', {
layout: 'card',
data: [
{
label: 'Total refunds',
value: refunds.length,
},
],
}),
io.display.table('Refunds', {
data: refunds,
}),
],
})
},
routes: {
refund_user: {
unlisted: true,
name: "Create refund",
handler: async () => {
const customerEmail = await io.input.email(
"Email of the customer to refund:"
);

console.log("Email:", customerEmail);

const charges = await getCharges(customerEmail);

const chargesToRefund = await io.select.table(
"Select one or more charges to refund",
{
data: charges,
}
);

await ctx.loading.start({
title: "Refunding charges",
// Because we specified `itemsInQueue`, Interval will render a progress bar versus an indeterminate loading indicator.
itemsInQueue: chargesToRefund.length,
});

for (const charge of chargesToRefund) {
await refundCharge(charge.id);
await ctx.loading.completeOne();
}

// Values returned from actions are automatically stored with Interval transaction logs
return { chargesRefunded: chargesToRefund.length };
},
},
handler: async () => {
const customerEmail = await io.input.email(
"Email of the customer to refund:"
);

console.log("Email:", customerEmail);

const charges = await getCharges(customerEmail);

const chargesToRefund = await io.select.table(
"Select one or more charges to refund",
{
data: charges,
}
);

await ctx.loading.start({
title: "Refunding charges",
// Because we specified `itemsInQueue`, Interval will render a progress bar versus an indeterminate loading indicator.
itemsInQueue: chargesToRefund.length,
});

for (const charge of chargesToRefund) {
await refundCharge(charge.id);
await ctx.loading.completeOne();
}

// Values returned from actions are automatically stored with Interval transaction logs
return { chargesRefunded: chargesToRefund.length };
},
},
},
}),
},
});

// Establishes a persistent connection between Interval and your app.
interval.listen();

✅ Recap

In Part 2, we extended our Interval dashboard by creating our first page to group actions and render additional content.

  • Pages can be created by instantiating the Page class alongside actions.
  • To render content on a page, define the handler function and utilize I/O display methods.
  • Actions can be nested within a page's routes property or linked to via menuItems.
Did this section clearly explain what you wanted to learn?

548 Market St PMB 31323
San Francisco, CA 94104

© 2023

Join our mailing list

Every Friday we send an email with the latest from Interval, including events, product updates, SDK releases, and more.