Skip to main content

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 "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

Was this section useful?

548 Market St PMB 31323
San Francisco, CA 94104

© 2022

Join our mailing list

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