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 before enacting the user update.

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",
}),
]);

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"
}
},
},
});

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 } 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",
}),
]);

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;
},
},
});

interval.listen();

API methods used

Did this section clearly explain what you wanted to learn?

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.