User settings form
Interval makes it easy to build a basic form for updating any resource. This example sketches out an admin tool for managing a user's settings and information. It supports updating a variety of data types with confirmation steps and detailed context for fields, all while providing you the flexibility to customize data access controls and form logic form your use-case.
Get started with this exampleTry it out
To create a fresh project based off this example, run:
- npm
- yarn
npx create-interval-app --template=user-settings
yarn create interval-app --template=user-settings
How it works
Our user settings tool will first provide way way to search for the user to operate on. We'll use the io.search
I/O method to provide a way to query for users by name or email (though io.search
allows customization to search by anything). This example will utilize faker to seed an array of sample users and simply search, but typically you would query your database.
import "dotenv/config";
import Interval, { io } from "@interval/sdk";
import { faker } from "@faker-js/faker";
const users = [...Array(10)].map((_, i) => {
return {
id: faker.datatype.uuid(),
name: faker.name.findName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
address: faker.address.streetAddress(),
subscriptionPlan: faker.helpers.arrayElement(["free", "pro", "enterprise"]),
emailVerified: faker.datatype.boolean(),
};
});
const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
actions: {
user_settings: async () => {
const user = await io.search("Search for user by name or email", {
initialResults: users,
renderResult: user => ({
label: user.name,
description: user.email,
imageUrl: user.avatar,
}),
onSearch: async query => {
return users.filter(user => {
return (
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase())
);
});
},
});
ctx.log(`User selected: ${user.name}`);
},
},
});
interval.listen();

Once we've selected the specific user, we can now construct a form for any of the details or actions we'd like to provide functionality for. We'll request inputs for the fields we're interested in (and ignore any that we dont want to expose via our admin tool—we won't expose address
in this example).
Here's we'll utilize io.group
to display all potential updates in a single form.
import "dotenv/config";
import Interval, { io } from "@interval/sdk";
import { faker } from "@faker-js/faker";
const users = [...Array(10)].map((_, i) => {
return {
id: faker.datatype.uuid(),
name: faker.name.findName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
address: faker.address.streetAddress(),
subscriptionPlan: faker.helpers.arrayElement(["free", "pro", "enterprise"]),
emailVerified: faker.datatype.boolean(),
};
});
const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
actions: {
user_settings: async () => {
const user = await io.search("Search for user by name or email", {
initialResults: users,
renderResult: user => ({
label: user.name,
description: user.email,
imageUrl: user.avatar,
}),
onSearch: async query => {
return users.filter(user => {
return (
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase())
);
});
},
});
ctx.log(`User selected: ${user.name}`);
const [_, name, email, subscriptionPlan, resetPassword] = await io.group([
io.display.heading("Update user details"),
io.input.text("Name", {
defaultValue: user.name,
}),
io.input.email("Email", {
defaultValue: user.email,
helpText: `Existing email (${user.email}) is ${
user.emailVerified ? "verified" : "not verified"
}`,
}),
io.select.single("Subscription plan", {
options: ["free", "pro", "enterprise"],
defaultValue: user.subscriptionPlan,
}),
io.input.boolean("Reset password?", {
helpText: "This will log the user out of all current sessions",
}),
]);
},
},
});
interval.listen();

Interval is unopinionated regarding the structure of your action functions—you can incorporate any custom logic into your tools. To make our tool safer and easier to use, we'll introduce a confirmation step for certain updates.
import "dotenv/config";
import Interval, { io } from "@interval/sdk";
import { faker } from "@faker-js/faker";
const users = [...Array(10)].map((_, i) => {
return {
id: faker.datatype.uuid(),
name: faker.name.findName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
address: faker.address.streetAddress(),
subscriptionPlan: faker.helpers.arrayElement(["free", "pro", "enterprise"]),
emailVerified: faker.datatype.boolean(),
};
});
const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
actions: {
user_settings: async () => {
const user = await io.search("Search for user by name or email", {
initialResults: users,
renderResult: user => ({
label: user.name,
description: user.email,
imageUrl: user.avatar,
}),
onSearch: async query => {
return users.filter(user => {
return (
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase())
);
});
},
});
ctx.log(`User selected: ${user.name}`);
const [_, name, email, subscriptionPlan, resetPassword] = await io.group([
io.display.heading("Update user details"),
io.input.text("Name", {
defaultValue: user.name,
}),
io.input.email("Email", {
defaultValue: user.email,
helpText: `Existing email (${user.email}) is ${
user.emailVerified ? "verified" : "not verified"
}`,
}),
io.select.single("Subscription plan", {
options: ["free", "pro", "enterprise"],
defaultValue: user.subscriptionPlan,
}),
io.input.boolean("Reset password?", {
helpText: "This will log the user out of all current sessions",
}),
]);
let confirmed = false
if (resetPassword || user.email !== email) {
const messages = [];
const helpTexts = [];
if (resetPassword) {
messages.push("reset this user's password");
helpTexts.push("log the user out all current sessions");
}
if (user.email !== email) {
messages.push("change their email");
helpTexts.push("send an email to verifiy the new email address");
}
confirmed = await io.confirm(
`Are you sure you want to ${messages.join(" and ")}?`,
{
helpText: `This will ${helpTexts.join(" and ")}.`,
}
);
}
},
},
});
interval.listen();

Finally we'll have our action function execute the requested updates to the user. Here we're just updating the user in memory and calling helper functions to do any additional required work.
import "dotenv/config";
import Interval, { io } from "@interval/sdk";
import { faker } from "@faker-js/faker";
import { resetUserPassword, sendVerificationEmail } from "./utils";
const users = [...Array(10)].map((_, i) => {
return {
id: faker.datatype.uuid(),
name: faker.name.findName(),
email: faker.internet.email(),
avatar: faker.image.avatar(),
address: faker.address.streetAddress(),
subscriptionPlan: faker.helpers.arrayElement(["free", "pro", "enterprise"]),
emailVerified: faker.datatype.boolean(),
};
});
const interval = new Interval({
apiKey: process.env.INTERVAL_KEY,
actions: {
user_settings: async () => {
const user = await io.search("Search for user by name or email", {
initialResults: users,
renderResult: user => ({
label: user.name,
description: user.email,
imageUrl: user.avatar,
}),
onSearch: async query => {
return users.filter(user => {
return (
user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase())
);
});
},
});
ctx.log(`User selected: ${user.name}`);
const [_, name, email, subscriptionPlan, resetPassword] = await io.group([
io.display.heading("Update user details"),
io.input.text("Name", {
defaultValue: user.name,
}),
io.input.email("Email", {
defaultValue: user.email,
helpText: `Existing email (${user.email}) is ${
user.emailVerified ? "verified" : "not verified"
}`,
}),
io.select.single("Subscription plan", {
options: ["free", "pro", "enterprise"],
defaultValue: user.subscriptionPlan,
}),
io.input.boolean("Reset password?", {
helpText: "This will log the user out of all current sessions",
}),
]);
let confirmed = false
if (resetPassword || user.email !== email) {
const messages = [];
const helpTexts = [];
if (resetPassword) {
messages.push("reset this user's password");
helpTexts.push("log the user out all current sessions");
}
if (user.email !== email) {
messages.push("change their email");
helpTexts.push("send an email to verifiy the new email address");
}
confirmed = await io.confirm(
`Are you sure you want to ${messages.join(" and ")}?`,
{
helpText: `This will ${helpTexts.join(" and ")}.`,
}
);
}
const userIndex = users.findIndex(u => u.id === user.id);
const updatedUser = {
...user,
name,
email,
subscriptionPlan,
};
users[userIndex] = updatedUser;
if (confirmed && resetPassword) resetUserPassword(updatedUser);
if (confirmed && user.email !== email) sendVerificationEmail(updatedUser);
return updatedUser;
},
},
});
interval.listen();

API methods used
- io.search
- ctx.log
- io.group
- io.display.heading
- io.input.text
- io.input.email
- io.select.single
- io.input.boolean
- io.confirm