In the second episode of "How to Build an AI", we take crucial steps to prepare Jobot, the AI we previously created, for its real-world debut. After thorough internal testing, we're ready to introduce Jobot to users and ensure a seamless and secure experience.
Key steps in this episode:
- Adding user login: We integrate a user login system to our site, providing personalized access to Jobot.
- Implementing two-factor verification: To prevent spam and enhance security, we introduce a two-factor verification process for user accounts.
- Deploying the application to the cloud: We move our application to a cloud-based infrastructure, ensuring scalability and accessibility for users worldwide.
- Tracking usage: We develop an easy-to-use tracking system to monitor usage and gather valuable data for future improvements.
By the end of this episode, Jobot will be fully equipped to interact with users in the real world. What challenges and opportunities will Jobot encounter as it expands its capabilities? Keep watching to find out!
In the second episode of "How to Build an AI", we take crucial steps to prepare Jobot, the AI we previously created, for its real-world debut. After thorough internal testing, we're ready to introduce Jobot to users and ensure a seamless and secure experience.
The following topics are covered in this tutorial:
-
Setting up a cloud database using Supabase for storing user data
-
Letting users create an account to chat with Jobot without an API key
-
Setting up an API endpoint that connects securely to OpenAI APIs
-
Configuring environment variables for secure production deployment
The best way to learn these skills is to follow along step-by-step and type out all the code yourself.
Problem Statement
PROBLEM: Make the following enhancements to the chatbot UI developed in the previous tutorial:
Replace the "Enter API key" input in the navbar with a "Sign In" buttons that opens a login dialog.
Allow the user to sign in with their email (using a one-time login code sent)
Once signed in, allow the user to chat with Jobot without requiring their OpenAI API key
Implement some rate limits to ensure that users can't send too many messages too quickly
Prerequisites
The tutorial builds on the previous tutorial assumes basic knowledge of the following:
-
HTML, CSS, and JavaScript
-
Git, GitHub, and VS Code
-
ReactJS, NextJS, and Tailwind CSS
Links to beginner-friendly resources for learning these skills are included at the end. Try to learn just enough so that you can understand and build on top of the code in this tutorial, and use ChatGPT (or Jobot) for help.
Here's the site we built in the previous tutorial:

Code & Finished Site
The code for this tutorial can be found here:
-
Finished code: https://github.com/jovianhq/jobot
-
Finished site: https://jobot.dev
-
Starter code: https://github.com/JovianHQ/jobot/tree/e1-first-contact-with-jobot
-
Starter site: https://jobot-50denx539-jovian.vercel.app/
Starting a GitHub Codespace
We'll use GitHub codespaces for development. Here are the steps to open up a repository in Codespaces and launch it within VS Code:
-
Navigate to the GitHub repository that you want to work on.
-
Click on the "Code" button, and select "Open with Codespaces" from the dropdown menu.
-
Select the Codespace configuration that you want to use, or create a new one if necessary.
-
Wait for the Codespace to be created, which may take a few minutes, if done for the first time.
-
Once the Codespace is ready, click on the "Open in Visual Studio Code" button
You can either work with the browser-based version of VS Code, or you can connect remotely to the codespace using your installation of VS Code.
Learn more about using VS Code with Codespaces here: https://code.visualstudio.com/docs/remote/codespaces
NOTE: GitHub provides 120 hours of free codespaces usage for personal accounts. Learn more about the pricing here.
Creating a Development Branch
To ensure that we can test our changes before deploying them to production, we can create a development branch:
git checkout -b user-login-flow
Later we'll create a pull request with this branch, preview the changes, and then merge it to the main branch
Refactoring the Navbar
Create a file src/components/Navbar.js with the following code:
import { useUser } from "@supabase/auth-helpers-react";
import Link from "next/link";
export default function Navbar() {
const user = useUser();
return (
<nav className="shadow px-4 py-2 flex flex-row justify-between items-center">
<div className="text-xl font-bold">Jobot</div>
<div>
{user === null && (
<Link
href="/login"
className="text-gray-500 hover:text-blue-500 text-sm"
>
Log In
</Link>
)}
{user && (
<Link
href="/logout"
className="text-gray-500 hover:text-blue-500 text-sm"
>
Log Out
</Link>
)}
</div>
</nav>
);
}
Now you can just use the <Navbar /> component in index.js
Create a file src/pages/login.js with the following code:
import Navbar from "@/components/Navbar";
import { useSupabaseClient } from "@supabase/auth-helpers-react";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import toast, { Toaster } from "react-hot-toast";
export default function Login() {
const supabaseClient = useSupabaseClient();
const [email, setEmail] = useState("");
const [code, setCode] = useState("");
const router = useRouter();
async function sendCode() {
const { data, error } = await supabaseClient.auth.signInWithOtp({
email: email,
});
if (data) {
toast.success("Verification code sent to email");
console.log("Verification code sent to email", data);
}
if (error) {
toast.error("Failed to send verification code.");
console.error("Failed to send verification code.", error);
}
}
async function submitCode() {
const { data, error } = await supabaseClient.auth.verifyOtp({
email,
token: code,
type: "magiclink",
});
if (data) {
toast.success("Logged in");
console.log("Logged in successfully", data);
router.push("/");
}
if (error) {
console.error("Login failed", error);
const { data: d2, error: e2 } = await supabaseClient.auth.verifyOtp({
email,
token: code,
type: "signup",
});
if (d2) {
toast.success("Signed up");
console.log("Signed up successfully", data);
} else {
toast.error("Failed to log in / sign up");
console.error("Failed to log in / sign up", e2);
}
}
}
return (
<>
<Head>
<title>Jobot - Your friendly neighborhood AI</title>
</Head>
<Toaster />
<div className="flex flex-col h-screen">
<Navbar />
<div className="mx-auto max-w-md">
<div className="border self-center rounded-lg my-8 p-4 m-4">
<div className="text-center text-xl font-bold text-gray-800">
Sign In - Jobot
</div>
<div className=" flex flex-col my-4">
<label className="font-medium text-gray-600">Email</label>
<input
type="email"
className="border p-2 rounded-md mt-1"
placeholder="john@doe.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button
className="w-40 border text-sm font-medium px-4 py-2 mt-2 rounded-md bg-gray-50 hover:bg-gray-100"
onClick={sendCode}
>
Send Code
</button>
</div>
<div className=" flex flex-col my-4">
<label className="font-medium text-gray-600">
Verification Code
</label>
<input
type="password"
className="border p-2 rounded-md mt-1"
placeholder="123456"
onChange={(e) => setCode(e.target.value)}
value={code}
/>
<button
onClick={submitCode}
className="w-40 border border-blue-600 text-sm font-medium px-4 py-2 mt-2 rounded-md bg-blue-500 hover:bg-blue-600 text-white"
>
Sign In
</button>
</div>
<p className="text-gray-600 text-sm prose">
{"By signing in, you agree to our "}
<Link href="/terms">terms of use</Link>
{" and "}
<Link href="/privacy">privacy policy</Link>.
</p>
</div>
</div>
</div>
</>
);
}
