Skip to main content
Big news! Interval has been acquired by Meter. Learn more →

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 example
interval.com

Try it out

To create a fresh project based off this example, run:

npx create-interval-app --template=user-settings
The full source code for this tool can also be found in our examples repo.

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 { Action, io, ctx } 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(),
};
});

export default new Action(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}`);
});

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).

Next we'll utilize io.group to display all potential updates in a single form.

import { Action, io, ctx } 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(),
};
});

export default new Action(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 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 before enacting the user update.

import { Action, io, ctx } 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(),
};
});

export default new Action(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",
}),
]);

const messages = ["update the user"];
let helpText;
if (resetPassword) {
messages.push("reset their password");
helpText = "This will log the user out all current sessions";
}
const confirmed = await io.confirm(
`Are you sure you want to ${messages.join(" and ")}?`,
{
helpText,
}
);

if (!confirmed) {
return "No confirmation, did not update the user";
}
});

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 { Action, io, ctx } 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(),
};
});

export default new Action(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",
}),
]);

const messages = ["update the user"];
let helpText;
if (resetPassword) {
messages.push("reset their password");
helpText = "This will log the user out all current sessions";
}
const confirmed = await io.confirm(
`Are you sure you want to ${messages.join(" and ")}?`,
{
helpText,
}
);

if (!confirmed) {
return "No confirmation, did not update the user";
}

const userIndex = users.findIndex(u => u.id === user.id);
const updatedUser = {
...user,
name,
email,
subscriptionPlan,
};
users[userIndex] = updatedUser;

if (resetPassword) resetUserPassword(updatedUser);

return updatedUser;
});

API methods used

Was this section useful?

548 Market St PMB 31323
San Francisco, CA 94104

© 2023

Join our mailing list

Get 1-2 emails per month with a roundup of product updates, SDK releases, and more.