LongCut logo

Build and Deploy a SaaS AI Website Builder | Next.js 15, React, Inngest, Prisma | Lovable clone

By Code With Antonio

Summary

Topics Covered

  • AI Coding Agent Architecture Revealed in Real Time
  • ShadCN UI Is How You Build a Component Library
  • End-to-End Type Safety Prevents 404 Headaches

Full Transcript

Imagine building your next startup with nothing more than a sentence. This is

Vibe, an AI powered app builder that makes that possible. And in this course, you're going to learn how to build it yourself. Let's try it out by entering a

yourself. Let's try it out by entering a simple prompt and hitting submit.

What you're about to see next is something you'd normally expect from high-end tools like Lovable, Replet, or Bolt. But in this tutorial, it is

Bolt. But in this tutorial, it is something you will learn how to build.

The result, a fully functional Netflix style homepage generated entirely by AI.

We can explore the full app right here in the preview and everything just works. The layout, the interactions,

works. The layout, the interactions, even models and favorites. It doesn't

just look like a Netflix style homepage, it behaves like one. And just above the preview, there's a live URL. You can

open it in a new tab, share it with others, or test the app in a real browser environment. And when you're

browser environment. And when you're ready to see exactly how it works, simply switch to the code tab and explore every component, utility, and

file that was created. But how is this even possible? And how does it all work?

even possible? And how does it all work?

Let's break it down. What you're looking at here is the inest developer server.

It shows a background task that kicked off as soon as we submitted our prompt.

The task itself is handled by an AI coding agent. The agent has access to

coding agent. The agent has access to various tools used to build the app. It

can run commands in the terminal, create, read, or even update files. And

its goal is to create a fully functional Nex.js JS application tailored to the user's request. You can click into any

user's request. You can click into any step to see exactly what happened and when. Which packages were installed,

when. Which packages were installed, what commands were run, and which files were created or modified.

Finally, the agent spins up a real development environment using an E2B sandbox, a secure container that runs

your app and exposes a live URL, so you can preview and interact with it just like any deployed project.

And finally, the finished app is saved to our Postgress database powered by Neon.

Let's head back to our Netflix project for a second. And just above the message form, you will see that we have a message, two credits remaining. That's

right. Each generation uses a credit.

So, let's see what else we can build with this app and find out what happens when we spend our last point.

This time, I will go with something a bit more interactive, like a conbon board I can drag around. And just like our previous project, this one was

created flawlessly. I can drag and drop

created flawlessly. I can drag and drop cards and even create new ones.

Everything just works. And just like our previous project, I can visit the file explorer and see every line of code that was generated. Notice how I've got one

was generated. Notice how I've got one more credit left. Let's go ahead and spend it.

Right beneath our homepage, we can find all of our previously generated apps or vibes as we are going to call them.

Let's click into the first one just to confirm it is still live and working.

But now, let's use our final credit to build something new. This time I'm going to build an admin dashboard and I'm

hoping to see some status cards and a pageionated table. And here it is, a

pageionated table. And here it is, a sleek looking admin dashboard with status cards, sidebar, and a pageentated

searchable data table. Notice how I have no more credits left. meaning that when I try to generate a new prompt, I'm going to get an error and it is time to

upgrade. In order to get more credits,

upgrade. In order to get more credits, it's time to upgrade. Billing is powered by Clerk. That's right. Just like their

by Clerk. That's right. Just like their out system, the developer experience is incredibly smooth. No web hooks, no

incredibly smooth. No web hooks, no complicated code, no confusing Stripe setup. Everything works out of the box.

setup. Everything works out of the box.

Watch how quickly I can upgrade my account. That's it. I have upgraded and

account. That's it. I have upgraded and I am now on the premium plan and I can see my status reflected immediately in the user settings.

And as a developer, you can now track your monthly recurring revenue on the clerk dashboard page. After a successful upgrade, you will see that you have a

100 credits remaining and beneath them the exact time they will reset. And by

the way, dark mode is fully supported in this project. You can switch it on from

this project. You can switch it on from the project's settings bar, and it will immediately update the entire app's UI, including the code explorer and the

landing page.

This isn't just a coding tutorial. We'll

also be following a proper Git workflow throughout the project. This will

include creating commits, new branches, and real pull requests.

Every pull request will be reviewed by Code Rabbit, our AI reviewer that provides feedback on everything from logic issues to best practices.

All actionable insights and critical mistakes will be flagged automatically, drastically improving our code quality.

Let's quickly go over the text stack we'll be using. Next.js 15 with React 19 make our framework with support for serverside rendering and server

components. TRPC combined with fanstack

components. TRPC combined with fanstack query will ensure our app meets full stack type safety. Prisma OM with

postgress provided by Neon will be our database solution. Tailwind version 4

database solution. Tailwind version 4 for styling along with chats and UI for accessible and reusable components.

Authentication and billing will be done by clerk while background jobs and agent tooling and agent networks will be done

by ingest. E2B for executing AI

by ingest. E2B for executing AI generated code in secure cloud sandboxes. Docker for generating custom

sandboxes. Docker for generating custom sandbox templates. Open AAI anthropic or

sandbox templates. Open AAI anthropic or Grock depending on the model you choose to power your AI agents. Code Rabbit for

AI powered code reviews. And of course, we'll deploy everything to production when it's finished. And now, without further ado, let's get started.

In this chapter, we're going to set up our Nex.js project, confirm our environment, get familiar with the file structure and versions of our project,

and set up our component library. And

finally, create a GitHub repository for this project. So let's start by setting

this project. So let's start by setting up our Next.js project. If you head to the documentation page of Nex.js and click on the installation tab, you will

find the system requirements. The

minimum Node.js version is 18.18 and these are the supported operating systems. So first things first, let's confirm we have a proper node version

installed. You can go inside of your

installed. You can go inside of your terminal and you can run node-v.

And while you're here, also confirm these two commands. You should not be getting errors for any of these three.

If you get errors for any of them, it is time to upgrade or reinstall your node.

You can do that by visiting the official Node.js website. If you have a version

Node.js website. If you have a version which is lower than 18.18, you're going to have to upgrade as well. Keep in mind that if you are or Linux or some

different operating system, your versions of npm and npx may be different, but as long as you're not getting any errors and you have a correct node version, you are good to

go.

So now let's go ahead and let's actually install our Nex.js application.

Seeing here, we have an automatic installation CLI command. So I'm going to go ahead and copy it, but I'm not going to run it immediately. I will

slightly modify it. Instead of using at latest, I'm going to write the exact version 15.3.4.

So instead of latest, I'm going to do 1534.

Why am I doing this? And do you need to do this? The reason I'm doing this is

do this? The reason I'm doing this is because I don't know when my viewers will come across this video. This might

be a month from now, 6 months from now, or a year from now. And depending on that, there might be a lot of new breaking changes introduced in the

latest versions. So if you want to, you

latest versions. So if you want to, you can use the latest version. That's

perfectly fine. But if you want to avoid any breaking changes, meaning that you're watching this video far into the future, like 6 months from now or a year from now, and you just want to code

along, I'm giving you the option to use the exact version that I had at the time. Right? So this is the latest

time. Right? So this is the latest version at the time of me making this tutorial. So I'm going to go ahead and

tutorial. So I'm going to go ahead and use this version. I'm going to create a project called a vibe. I'm going to select yes for TypeScript, yes for

slint, yes for Tailwind, and I'm also going to select yes for the source directory. Be careful here because I

directory. Be careful here because I think that the default value might be no. So use the arrow keys to select yes

no. So use the arrow keys to select yes and press enter. Same thing for the app router. Make sure you select yes here.

router. Make sure you select yes here.

Yes for Turboac, but only for nextdev in my case. And I'm not going to customize

my case. And I'm not going to customize the import alias. So this will be the only no option for me. And now let's just wait for our dependencies to install.

After our dependencies have installed, you're going to see a success message like this. What you have to do next is

like this. What you have to do next is you have to enter this directory with your terminal. So let's go ahead and do

your terminal. So let's go ahead and do change directory vibe like this. And

once you're inside of here, you can run the ls command to see a list of files inside. Before we run this project, I

inside. Before we run this project, I want to set up our IDE, the place where we are going to write some code. For me,

that's going to be VS Code. So I'm going to go ahead and select open, and I will select my new Vibe project.

Inside of here, you should be seeing a similar or the exact same file and folder structure. So, first let's

folder structure. So, first let's confirm our versions. I'm going to go inside of package JSON. You can see that I have Turboac here because I selected

TurboAC. Yes, you can see that I have

TurboAC. Yes, you can see that I have React 19. You can see that I have next

React 19. You can see that I have next 15.3.4.

You can see that I use Tailwind version 4 and TypeScript version 5. And these

are probably the most important versions for this project. Of course, if you are watching this into the future and you want to use whatever are the newest versions for you, you absolutely can.

You don't have to worry about this. Then

I'm simply showing this for those people who want to use the exact same versions as me. Great. So about the config files,

as me. Great. So about the config files, I have a tsconfig, a post CSS config, next config, and an esllet config. You

might notice that Tailwind config is missing. That is because we are using

missing. That is because we are using Tailwind version 4, which no longer introduces a Tailwind config.

Inside of the source folder, I have an app folder. Source folder is quite

app folder. Source folder is quite important. Make sure you have it. So,

important. Make sure you have it. So,

inside of the app folder here, I have a favicon, globals, layout, and page.

You can quickly visit them if you want to. And inside of my public folder, you

to. And inside of my public folder, you can see that I have some SVGs here.

Great. So now let's go ahead and let's install Shats CNN UI into our project.

First of all, let's mark these as completed.

Now let's go ahead and set up Shatsen UI.

So by visiting chats UI and going into the introduction you can see that even though we are going to use it as our component library is actually not a component library.

Instead it is how you build a component library. It is basically a collection of

library. It is basically a collection of open code components with composition pattern that you can simply add to your project. So let's go ahead and go inside

project. So let's go ahead and go inside of the installation and select next.js.

Let's pick our package manager here.

Let's copy the command. Make sure you are doing this inside of your project.

So again, you can run ls to confirm you are inside. And once again, instead of

are inside. And once again, instead of using latest, I'm going to go ahead and show you which version I have. So for

me, that is 2.7.0.

So let's go ahead and run the command.

My apologies.

instead of latest 2.7.0 in it. If you get prompted to install,

in it. If you get prompted to install, you can select yes. I'm going to be using neutral for my color here.

And there we go. Just like that, we have initialized chats UI into our project.

Here you can see how it verified the framework next.js and it valid validated the Tailwind config. It found version 4.

Perfect. You can see that it also installed some dependencies, modified our global CSS and created one file. So

we can now go ahead and visit all of those things here.

You can see that I now have CLSX Lucid React and I believe also Tailwind Animate CSS.

I think that's the new package that came from uh Shotsy UI and Tailwind merge is new as well I believe. Great. So those

are the packages that SHAT CN CLI added.

Now let's go ahead and look at our lib file utils inside of the source folder.

In here we have a CN function which we're going to use throughout our project whenever we need to safely uh merge or add dynamic Tailwind classes.

And it also modified the globals.css

by adding a bunch of variables which we can now use uh to build our project theme. It also added dark mode rules as

theme. It also added dark mode rules as well. Perfect.

well. Perfect.

So now let's go ahead and let's learn how to add a component. So for example, let's go inside of our components here.

Let's select a button component and let's go ahead and select CLI option for the installation. And let's go ahead and

the installation. And let's go ahead and copy this to clipboard. And I'm going to use 2.7.0 add button.

And just like that, we have added a component to our project. If you get prompted with the option to use legacy peer depths or force, uh you can select

any of those two options. But if you're using the same versions as me, I'm pretty sure uh it will you will have the exact same experience. It should just work straight out of the box. But if you

get any errors or any uh decisions to make, you can select legacy peer depths.

If you don't have that choice, perfectly fine. You can just continue.

fine. You can just continue.

Great. So now you can go inside of source components UI and you can find button.tsx

button.tsx and you can see that it's using some of these new packages that it added before and it also uses the CN which it initialized before. So the cool thing

initialized before. So the cool thing about SAT CNUI components is that they are open code meaning that it's not bundled in a Node.js package. it is

actually available for us to modify and build as much as we want. So now let's go ahead and let's run our project.

So npm rundev you will see d- turbo pack here and then you can open the localhost 3000 to see your app.

Now let's go ahead and let's modify source app page tsx so we can see some changes here.

go inside of page here and let's learn how to actually write a page component.

So I'm going to remove everything and the important thing here is that your components need to be uh using default export. Right? So the name doesn't

export. Right? So the name doesn't matter. This can be called home or it

matter. This can be called home or it can be called page. I like to use the page convention. And now in here I can

page convention. And now in here I can just write hello world.

And once I save you will see the change hello world. If this is your first time

hello world. If this is your first time using Visual Studio Code, if you have a little uh circle here, it means the file is unsaved. So, just hit save and then

is unsaved. So, just hit save and then it will be updated.

And here's what happens if you don't do a default export. It will not be able to find the page. So, that's why default expert is important here. But the name

itself does not matter. But of course, you can't use some reserved uh things, right? You should not be able to call

right? You should not be able to call this error, right? Because error is already reserved.

Great. So now let's go ahead and let's test out our tailwind. So I'm going to go ahead and add a class name here. Text

bold.

My apologies. It is font bold. I forgot.

There we go. And now my font is bold.

But let's try changing the color. Text

rows 500.

And now I've changed the color. You

might notice that I have this little color icon. And when I hover over my

color icon. And when I hover over my classes, I can see the inner CSS it is applying. If you want to see the exact

applying. If you want to see the exact same thing, you can go ahead and install Tailwind CSS IntelliSense package. It will be quite useful in this tutorial.

Great. Now, let's go ahead and let's remove this and let's add a button from components UI button.

Let's go ahead and give it some children. And let's close it.

children. And let's close it.

And just like that, you have a button here.

And now I'm going to show you a quick way you can enter the button inner code.

You can use command or control and then click here and that will take you to the actual source components UI button.

Right? So it will be quite useful for you to learn this shortcut because I will use it quite often in the tutorial so you don't get confused how I got there that fast. Another shortcut you

should learn is command space. My

apologies command P or control P depending if you are on Windows. And

then once you open this bar you can search for button and press like this.

I'm going to be using this quite extensively in the tutorial. So it would be good that you learn this as well. So

once you're inside of the button here, you can see that we can have some variants like destructive. Let's go

ahead and try it out.

Once you add it, you can see that the destructive variant is now uh active.

But what happens if you go inside of the code and change this to danger?

You can see how it immediately breaks.

And we have to change this to danger. So

what we've just done is we've changed the inner code of the button to our liking. Instead of destructive, it is

liking. Instead of destructive, it is now called danger. So let's bring that back now and let's try creating a new one. So new and let's go ahead and try

one. So new and let's go ahead and try something fun like background purple 500 text white.

And if you go ahead and try now, you will see that you have the new option.

And just like that, you created your own variant. So that's the power of Shatsen

variant. So that's the power of Shatsen UI. And now that we confirm the button

UI. And now that we confirm the button is working, let's go ahead and let's add all of the other components. The reason

I want to add all other components is so that it's easier to follow along in this tutorial. You obviously don't need all

tutorial. You obviously don't need all of them and you can clean them up later, but it's just going to be easier for us to have all of them at our disposal and then simply choose which ones we want to

use instead of having to install and wait. So, I'm going to go ahead and shut

wait. So, I'm going to go ahead and shut down my app and I'm going to run npxhat cnui 2.7.0-all.

And this will add every single component to our project.

And you can see how it added all of these components, but it skipped the button because it already exists.

Perfect.

So, now we can go ahead and inside of your source components UI, you will see all of these various components here.

You now probably have a lot of unsaved files here. My apologies, not unsaved,

files here. My apologies, not unsaved, uncommitted files. Uh, we're going to

uncommitted files. Uh, we're going to explore what that means in a second. So,

let's go ahead now and let's do the last thing that we need, which is create a GitHub repository. So, I'm going to go

GitHub repository. So, I'm going to go ahead and go inside of my GitHub. And I

selected new repository here. Now, just

a quick note, you don't have to do this.

So yes, following this step right here, creating a GitHub repository, branching out, doing commits, and opening pull requests, it's completely optional. It

is simply for those who want to learn uh how to follow a proper Git workflow. If

that is not something of interest to you, you don't have to do it at all.

Right? So I'm going to create a new repository called Vibe, and I'm going to set it to private. And I'm going to create a new repository here. And then

I'm going to go ahead and copy uh these three lines because we need to push an existing repository here. But before we can do that, we have to stage our

changes. So we have 53 unstaged changes

changes. So we have 53 unstaged changes now. So let's go ahead and add a plus

now. So let's go ahead and add a plus here. And now all of them are staged.

here. And now all of them are staged.

And now let's add a commit message. So

I'm going to go ahead and do 01 setup.

basically my commit messages will match uh my chapter and then I'm just going to commit. And then what we're going to do

commit. And then what we're going to do is we're going to go ahead inside of our project and we're going to run those three commands here.

Wait a second. And now if you go ahead and refresh your repository, you can see that you have your project available right here. And now in here you no

right here. And now in here you no longer have that button publish a branch because now you have access to your uh remote origin main meaning that this is

no longer a local repository. This is

now a remote branch on a remote repository. Perfect. So that marks the

repository. Perfect. So that marks the end of this chapter and now we are ready to start setting up our database.

Amazing job and see you in the next chapter.

In this chapter, we're going to set up our database. We're going to start by

our database. We're going to start by obtaining a connection URL using a poser database provided by Neon. We are then

going to set up Prisma, our OM. We're

going to learn how to add and modify a Prisma schema, some basic migrations, as well as how to use a database studio, and also how to reset your database in

case something goes wrong. And then

we're going to go ahead and branch out, open up a pull request, and review and merge that pull request. So, let's go ahead and visit Neon database. You can

use the link in the description or the link you can see on the screen to let them know you came from this video. Once

you've created an account with Neon, go ahead and click create project. I'm

going to go ahead and call my project Vibe, and my database name will be Vibe as well. And then I'm going to click the

as well. And then I'm going to click the connect button and I'm going to copy the snippet for my connection string.

After that, I'm going to go inside of my project and I will create a new file environment.

Inside of here, I'm going to create a database URL and I'm going to paste my connection string.

After that, let's go ahead and let's set up Prisma. You can use the link in the

up Prisma. You can use the link in the description or the link you can see on the screen to let them know you came from this video. This helps me a lot in creating more content like this. So

let's go ahead and learn how to use Prisma with Nex.js. The first step is to set up the project. Since we already have that, we don't have to do that.

Instead, we can go immediately to step two, install and configure Prisma. Since

we are using other databases, specifically Neon, let's click here so we know what to install. So, let's go ahead and start by installing Prisma and

TSX as our dev dependencies.

Once this was installed, I will just go ahead and go inside of my package. JSON

so you can see the versions, right?

Prisma is 6.10.1 and TSX is 4.2. 20.3.

So if you're using the latest versions, this probably does not matter for you.

But if you want to use the same versions as me, you would go ahead and set up your installation like this. For

example, if you want to. Great. So after we've done this, our next step is to install

Prisma client, but this time not as a dev dependency, but as an actual dependency instead.

After we've done this, let me show you the version.

Prisma client 6.10.1. So I think the most important thing about the Prisma versions is that Prisma client needs to match your Prisma dev dependency. At

least at the time of me making this video, I'm pretty sure that is an important rule. It might change in this

important rule. It might change in this in the future. So I'm not sure, but I think it was this way for a long time now. So now let's go ahead and let's

now. So now let's go ahead and let's actually run our app. So in here they have this snippet but I'm not sure if this exact output will work with our

directory because we have a source file here. So instead what I'm going to do is

here. So instead what I'm going to do is I'm just going to run npx prisma init with nothing more. So npx prisma init.

Let's go ahead and run this.

And after this was finished I see this big log here. So your Prisma schema was created in Prisma/sema.prisma.

Prisma would have added database URL but it already exists in your environment.

You already have a g ignore file. Don't

forget to add environment in here. So

pretty good warnings here. So yes, every time you run npx prisma in it, it creates an environment file or it modifies it and it adds a database URL.

But this time it detected that we already have a database URL inside of our environment. So it didn't do

our environment. So it didn't do anything. Right? Usually it would modify

anything. Right? Usually it would modify your uh uh environment file and it would write a big message at the top saying modified or generated by Prisma. But the

only thing you need in your environment is the database URL. So even if yours looks different, maybe in the future they've changed this. All you need is a

database URL for now. Great. So now

let's go ahead and let's visit the other things added inside of Prisma folder. We

now have a schema.prisma. And what's

important here is that your provider is posgress and that your URL is the database URL. So make sure you don't

database URL. So make sure you don't have any typos here. But if you got this warning message, it means you have typed it correctly because it did not override

it. And yes, about this second message,

it. And yes, about this second message, uh don't forget to add environment in the git ignore file. That is very important. But as you can see, my

important. But as you can see, my environment file is grayed out, which actually means it is inside of git ignore. You can find it right here.

ignore. You can find it right here.

Great. So now that we have that set up, let's go ahead and learn how to modify the schema. So I'm going to go ahead and

the schema. So I'm going to go ahead and I'm going to copy this exact changes that they are using in their Prisma. If

you don't have access to that documentation page for whatever reason, don't worry. I'm just doing this as an

don't worry. I'm just doing this as an example. Our schema will be different

example. Our schema will be different anyway, or you can just pause the screen and type it out now. So basically we're adding a user model with an ID of type

integer and it will auto increment.

Basically if I add one user it will be ID1 and then I add another user it will be ID2. Then an email string which is

be ID2. Then an email string which is required and unique and a name string which is optional.

So basically, Prisma is using decorators for stuff like defining a primary key or ID in this case or adding the default value or setting something to be unique.

And if you want to make a field optional, you simply add a question mark after its type. And if you want to make a relation like user and post, you start

by defining well obviously your database structure one to many many to many, right? uh and then you simply add how

right? uh and then you simply add how you want it to be architectured. So I

want user to have many posts but I want post to have only one user. So you

define the second model and you literally say it's an array of that model, right? And then inside of here in

model, right? And then inside of here in order to properly connect it using foreign keys, what you have to do is you have to set the author ID or the user ID

and then you have to create an actual relation using the foreign key. So you

have to use user as the model and then you use a decorator relation. So it

matches the outer id and it references the ID of the user model and this is the place where you would add things like on

delete cascade right so in case the user gets deleted we want the model post to get deleted as well so you can remove this for now just leave it exactly like

this this is like a pretty good minimal example to learn Prisma and one thing I forgot to tell you yes you can install Prisma uh here to see the syntax. I

should have told you this before. My

apologies. I just remembered. Uh, so

make sure to install this, right? So you

can see the pretty colors and everything.

And once you've done this, make sure you save this file. And let's go ahead and see how do we actually, you know, commit this, right? Because right now, uh, our

this, right? Because right now, uh, our database here is completely empty.

Nothing yet exists here. Nothing is

pushed here. So let's go ahead and let's do npx prisma migrate dev. I'm not going to do this flag because that's not how we're going to uh run our commands. So,

npx prisma migrate dev. And now we're going to be asked to uh call this migration in a certain way.

So, I'm going to call this migration in it. And just like that, we applied the

it. And just like that, we applied the migration. And two things have happened

migration. And two things have happened now. Actually, three things. The first

now. Actually, three things. The first

thing is that it synchronized our database from neon to the schema. Right?

So now uh our neon database has the same schema there. Uh the second thing it did

schema there. Uh the second thing it did is it created a migrations file instead of our project. And the third thing it did is it generated the Prisma client instead of source generated Prisma.

Right? So let's try and check all of those things out. Uh if you go inside of your Prisma, you can now see the migrations here. And inside of here you

migrations here. And inside of here you can see the actual SQL file that happened.

Uh and the second thing you can see is I'm not sure where is my uh generated.

It's right here. Source generated. There

we go. You can see the Prisma is now available here. And the third thing that

available here. And the third thing that it did is it synchronized the Neon database. So if you go inside of neon uh

database. So if you go inside of neon uh and if you go inside of tables I think you might be able to see there we go post and user right. So you can see uh

the exact fields here title content published author ID and user relation ID email name and posts relation. There we

go. So we officially synchronized all of those things now. So let's go ahead and see the next steps that we have to do.

Uh in here it suggest creating a seed script. Let's go ahead and do that.

script. Let's go ahead and do that.

Right, I'm going to copy this. You don't

have to do this, but I think it's nice.

It's a nice way to learn Prisma. Let's

go inside of Prisma and let's create a seed.ts script like this. And let's paste it inside. Now in here I have to go inside

inside. Now in here I have to go inside of source I think.

Let me just see how do I access this.

All right, found it. So it's source generated Prisma, right? We have to go inside of source generated Prisma. It's

not inside of app like they suggested here. Perhaps uh this depends on whether

here. Perhaps uh this depends on whether you use the source folder or not or they've changed it. So they use the prisma dot user create input. Now if

you're wondering where does this come from? How does it know user create

from? How does it know user create input? Why is this called user? Well,

input? Why is this called user? Well,

that's actually the magic of Prisma.

Every time that you modify the Prisma schema and you run the proper command npx prisma migrate dev which internally

runs npx prisma generate.

What basically happens is that it refreshes its internal uh intellisense typescript tool if I can call it like that and it creates a bunch of these

useful types for you. So right now you also have things like Prisma uh you can see all these weird things. If I I think I can you I can import user and I can

import post right and if I were to add a new model I would be able to import that as well. Right? So that's the cool thing

as well. Right? So that's the cool thing about Prisma. Uh perfect. So I have this

about Prisma. Uh perfect. So I have this user data here and let me just if you if you are not unable to copy this let me just show you this first example and all

the other ones are exactly like that and this is the bottom part right uh or you can use the link that I will put on the screen uh for this seed

script if you want if you are unable to find it great so now let's go ahead and let's add this to our package JSON so

Prisma and seed Let's go inside of package. JSON.

So after scripts here, let's add Prisma seed. TSX Prisma SLT seed.ts. Just

seed. TSX Prisma SLT seed.ts. Just

ensure that yours is in the correct place.

And once you've done that and saved the file, uh in here we have a warning.

Before starting the development server, note that if you're using Nex.js JS version 15.2.0 or 15.2.1 do not use Turboac. Right? So you can

see that Turbopac sometimes has this small little issues. But since we are on a newer version, we should have no problems here. Right? Uh and now let's

problems here. Right? Uh and now let's go ahead and run npx Prisma database seed here like that. And that uses the tsx Prisma

like that. And that uses the tsx Prisma seed. And there we go. The seed command

seed. And there we go. The seed command has been executed. So, just make sure you have tsx installed in your dev dependencies and your seed command set at the correct place. And you should

immediately be able to see this if you go inside of your neon database. And if

you go inside of your users, you will see Alice and Bob inside of the users here. So, we successfully populated our

here. So, we successfully populated our database. Perfect. Another way of seeing

database. Perfect. Another way of seeing this data is by using the Prisma Studio.

So, NTX Prisma Studio should open it up on 555.

And there we go. You can see that inside of here, I have some posts and I have some users right here, Alice and Bob. Perfect.

Let's see what are the next steps here.

So, now we have to learn how to actually uh fetch our data, right? So, let's go ahead and do that. I'm going to go ahead

inside of my project inside of source inside of lib and I'm going to create a database.ts file. So they recommend

database.ts file. So they recommend creating Prisma. I like to call it

creating Prisma. I like to call it database. And I'm going to import Prisma

database. And I'm going to import Prisma client from generated Prisma. This is

the same thing that they are doing. I'm

just using an alias here. And then you literally have to do the same thing here.

I'm going to go ahead and try and explain uh how this works. So basically

why not just export new Prisma client.

The reason why is because of Nex.js hot reload. Uh every time a hot reload

reload. Uh every time a hot reload happens a new Prisma client gets initialized and that causes problems and you would actually see a warning in your

terminal about that. So what they do is they store Prisma in a global because global as the window object is not

affected uh by hot reload.

I used window object. I'm not sure if this belongs to the window nameace.

Perhaps node namespace would be a better descriptor of it. And I also don't like to use the export default. So I will just use the export const here. And let

me just put it here. Actually,

I have to do it like this. Okay.

So, now that we've done that, let's go ahead and let's uh try and query something. So, I'm going to go inside of

something. So, I'm going to go inside of my source folder inside of app page.x

and I'm going to go ahead and import prisma from lib database right here. And

I will get my users from await which means I have to turn this into an asynchronous component prisma dot user

and let's just use find many and then I'm going to remove this entirely and do JSON stringify users null and two and we

can remove the button import. So this is a server component by default meaning that it is rendered on the server and it has access to the database. Server

component is not the same thing as server side rendering. Those are two different concepts. A server component

different concepts. A server component is actually a react thing not a next.js thing. Next.js is simply the environment

thing. Next.js is simply the environment where server components can be demonstrated. So let's go ahead now and

demonstrated. So let's go ahead now and do npm rundev.

Let's visit localhost 3000. And in here you should see a JSON of your users inside. And if you change this to post

inside. And if you change this to post and change this to posts, you should be able to see posts here as well. So that

is basically it. Right in here they go a bit more in depth. They're creating a whole, you know, unique include. We're

going to learn that through the project itself. So that's basically it for this.

itself. So that's basically it for this.

But there is one more thing I want to uh go over here. So we added the schema, we learned about basic migrations and we learned about database studio. But we

didn't learn about database reset.

So why do we even need to learn about database reset? Well, I just think it's

database reset? Well, I just think it's very useful for development. So let me go ahead and actually modify this. So

for development, right, not for production cases, right? You would

pretty much never need to reset your database in production, but during development, it's just super easy if you get stuck, right? Because this is the case now. So we now have some posts and

case now. So we now have some posts and we have some users, right? So what

happens if I go ahead now inside of my schema Prisma here and for example, I remove the title from the post. It's no

longer required. or I remove the email, right? And if I go ahead and do that

right? And if I go ahead and do that now. So, npx prisma migrate dev. I'm

now. So, npx prisma migrate dev. I'm

going to add this changes now. Uh it

will probably ask me to reset database anyway, right? Uh that's why this is a

anyway, right? Uh that's why this is a dev command, right? It should this should also not be used in production.

You would usually do npx prisma migrate.

You can learn more about that uh in the actual Prisma documentation here. And

there we go. So now we have a problem.

You are about to drop the column title which still contains nonnull values and you're about to drop the column emails.

Are you sure you want to create and apply this migration? I'm going to select yes and I'm going to call this test migration here. Uh and this time it

worked right. So this seems to be uh

worked right. So this seems to be uh normal now. But the problem is what if

normal now. But the problem is what if you do something more complicated? For

example, let's try and let's drop the user here and let's drop this. Right?

I'm trying to make a scenario uh where this actually stops working. Right?

Let me try this.

I'm going to do another migration and I'm going to call this test again.

Test two. And okay, it's still working.

Never mind. Basically, my point is that you need to learn how to nuke your database. Let's say that you do some

database. Let's say that you do some some of your own experiments here and you get to a point where you're getting errors with your npx Prisma migration.

What I usually do if I get completely stuck and I'm in development, this is important only for development mode. I

would go inside of Prisma here and I will remove all of my migrations, all of them. And then I would do npx prisma

them. And then I would do npx prisma migrate reset like this.

Are you sure you want to reset your database? All data will be lost. And I

database? All data will be lost. And I

will press yes. Right. So, uh, oh yes, um, I forgot that resetting it also runs the seed script. So, let's remove the seed script because it doesn't make

sense, right? We just use it to learn.

sense, right? We just use it to learn.

So, we can remove that. We can go inside of our package JSON and we can remove this.

And we can also go inside of our source app page and we can remove this as well.

And then let's go ahead and just confirm that we can do npx prisma migrate reset again.

So just confirm and this should clear your entire database.

Basically this is quite useful in development mode when you are for example doing what we just did right we just learned how to use Prisma. So we

populated our database with some models.

We don't really need we don't need the user with name optional and email required and posts right there. There's

not going to be any posts in our project. So we just learned how to reset

project. So we just learned how to reset our database as well. Perfect. So now

it's time to create our first pull request.

Once again uh the part of git workflow of this tutorial is completely optional only for those who want to learn. So you

can end the chapter here uh if you don't want to follow the git workflow. So what

I'm going to do is I'm going to go ahead down here where it says main and I'm going to click create new branch and I'm going to call this 02 and then I'm going

to call it database. So I'm going to call my branches according to my chapter 02 database. And you can see that down

02 database. And you can see that down here it says 02 database. Then I'm going to go ahead and click the plus icon to add all of these changes. And then I'm

going to go ahead and add my 02 database commit message. And after that, I'm

commit message. And after that, I'm going to publish my branch.

And once I've done that, I'm going to go back to my GitHub here. And immediately

you will see an option to create a pull request in my repository. If this did not appear for you, you can manually go inside of pull requests, new pull

request. The base will be main or master

request. The base will be main or master depending on what you're using. And

you're going to select your new branch here in the compare. And then create a pull request and then click create pull request right here.

And once you've created your pull request, you can go inside of file changes here. And in here you can see

changes here. And in here you can see every single thing that was modified.

You can see that we added the Prisma client instead of our lib database. We

slightly modified our page tsx and we added the schema prisma. We installed

some new packages and we also added uh source generated prisma to get ignore.

So that was not added. And after that we can go ahead and merge this pull request and click confirm merge. Uh I'm not going to delete my branch simply so I have access to all of my branches here.

You can see that I can now always go back to that state of the application.

And then what you have to do inside of your project is the following. Go uh

down here and click on the 02 database branch and then you have to select your main branch. You might be wondering

main branch. You might be wondering which one this main or this main. Well,

basically the difference is one is a local branch and the other one is a remote branch. So the remote branch

remote branch. So the remote branch would be the one that is most uh well I I would say that is the source of truth.

I could be wrong in doing this because you could have some changes on your main branches that you didn't push. But in

our case we're going to push everything from the main branch. So in your case it doesn't matter if you click on this one or if you click on this one you will have the exact same result. What's

important is that you do the following.

uh you click on synchronize changes here and then okay like this. And now you should no longer

like this. And now you should no longer have any uh visible buttons here, right?

And if you go inside of your graph, you should see this an initial commit then 01 and then something different for 02, right? Because we branched out and then

right? Because we branched out and then we merged that back into our main like that. And now if you go ahead inside of

that. And now if you go ahead inside of your main here, you can see that six minutes ago we merged this, right? Uh,

perfect. So that's it for this chapter, you can now see that even though I'm in my main branch, I still have my Prisma schema, meaning everything is fine.

Everything is good. Perfect. So let's go ahead and wrap this up. So I'm going to go ahead and uh check this as done. Amazing job. and see

you in the next chapter.

In this chapter, we're going to set up TRPC, which is going to be our data access layer. Let's go ahead and let's

access layer. Let's go ahead and let's head to the TRPC documentation page. You

can use the link in the description or the link you can see on the screen to let them know you came from this video.

Once you are on the landing page, go ahead and click on the docs. And in

here, click on the client usage. And in

here you can find tanstack react query with a little star icon. And in here going to server components. Be mindful

that at the top here you do have a nex.js tab but that is talking about an older nex.js version. So make sure that

you are inside of tanstack react query server components. This is what we need.

server components. This is what we need.

The first thing we have to do is install all the necessary dependencies. So, if

you're watching this video far into the future, I would recommend waiting until you see the exact versions that I had installed. So, before you run this, what

installed. So, before you run this, what I suggest you do, and what I suggest you do before you start any of my chapters, is click on this button right here and

just click okay. Basically, this is just a sanity check to confirm that you are on your main branch and that you didn't accidentally uh forget to synchronize

your merged branch from the previous chapter. So, you only care about this if

chapter. So, you only care about this if you are actually following the git workflow.

Once you've done that and you are confirmed to be on the main branch, you can go ahead and install all of these packages. I am going to show you which

packages. I am going to show you which versions exactly I have installed.

Let's head into package.json so we can see all the changes here.

As you can see, I have tenstack react query 5.80.10 and all tRPC versions are 11.4.2.

So if that's something you care about, you can go ahead and install these versions as follows. You would add this exact number to all of these packages

regarding TRPC and then you would change this from using the latest version to using 5.80.10

and the ones for ZOD client only and server only are not that important but I'm going to show you them as well. So

server only is 0.0.1 0.1 and zod is a package we already have installed and we had it installed in the first chapter setup because when we added all chats

component we also added forms and forms in chaten use zod for validation so that's why this isn't marked as a new dependency because we already had it

great so now that you have confirmed to installed all TRPC ones just double check that you actually have the same versions of all TRP PC packages because

that is quite important, right? So the

version here is the the important version here is 11.x, right? The minor

versions probably don't matter that much, but there is a big breaking change in uh 11 if you're coming from 10 or 9.

So make sure you're using at minimum 11 something and then you're good to go.

Perfect. Now let's go ahead and let's create a small init file here. Uh so I'm going to copy this. Uh it's a very simple snippet. So even if you don't

simple snippet. So even if you don't have access to this documentation page, don't worry. I will pause the screen and

don't worry. I will pause the screen and you will be able to copy with me. Let's

create a TRPC folder inside of our source folder. And inside let's create

source folder. And inside let's create init.ts.

init.ts.

And in here we are importing init TRPC from TRPC server package and cache from React. We are setting up the create

React. We are setting up the create TRRPC context here with some mock information and we are also creating our initial T object which is basically

initializing the TRPC and then we are extending it to create the router callers uh and factory and base procedure.

Perfect. So that's our init file.

Now let's go ahead and scroll a bit down and let's create our base routers.

So I'm going to go ahead inside of TRPC I'm going to create routers folder and inside app.ts.

inside app.ts.

So I'm importing Z from zod and from the previously created init file I'm importing the TRPC router with the base procedure. So in here we have a very

procedure. So in here we have a very simple procedure called hello and it accepts text which is a string and it returns back an object with a property

greeting which is a string with the information from the text that we've entered. We are going to test this out

entered. We are going to test this out later so it's easier for you to understand if this is the first time seeing the TRPC syntax.

So that's it for the routers and now what we have to do is we have to create our API folder DRPC and then a special

uh Nex.js variable folder uh and then route.ts. So let's do that first.

route.ts. So let's do that first.

Inside of source app folder, let's create API.

Then let's create tRPC. And then let's create a dynamic folder inside of square brackets TRPC again. and then route. DS

and let's go ahead and copy this. Now,

in here, we're going to get some errors.

It's specifically uh about this import alias. So, we don't use uh this curly

alias. So, we don't use uh this curly little string. Instead, we use an add

little string. Instead, we use an add sign. So, you can just switch it to that

sign. So, you can just switch it to that and you will have no more errors. As you

can see, uh all of these things already exist. So, we have the TRPC server

exist. So, we have the TRPC server package. They are just extending it

package. They are just extending it here. Some tree shaking it seems. And

here. Some tree shaking it seems. And the TRPC in it is the one we just created as well as the router's app.

Right? So you can commandclick on this to visit that same as the init one. And

this is a NodeJS package. So that's a different thing. Great. You can save

different thing. Great. You can save this as well. Just double check that you have app folder API TRPC TRPC in square

brackets and then route.ts. It's very

important to have this exact structure.

Great. Once we've done that, let's go ahead and let's create the queryclient.ts.

queryclient.ts.

So I'm going to go inside of source drpc queryclient.ts.

queryclient.ts.

Yes, it is.ts. Perfect. So about super JSON, we can um let's do this. Let's

immediately install it. So super JSON because we are going to need it. And I

will go inside of my package JSON here.

So this is my version in case you want to use the exact same one. And what

we're going to do is we're going to immediately enable serialized data using superjson.s serialize and des serialize

superjson.s serialize and des serialize data using superjson. DS serialize as well. So you can leave it the component

well. So you can leave it the component like this. No need to modify anything

like this. No need to modify anything further.

Now let's go ahead and let's create our client.tsx.

client.tsx.

So this will basically be a wrapper, a provider of TRPC and Tstack query which we're going to wrap our entire app around. Uh if this is your first time

around. Uh if this is your first time ever seeing Tanstack query or TRPC, this is a lot of information at once. Uh but

if you've ever worked with uh React Query or something like SVR, I promise it's a similar API. It is just uh a bit

more advanced data access layer format that we are doing here. So as much as this setup seems a little bit complicated, it is definitely worth it.

You will see how easy it will be to build your API routes and your procedures later on. You will thank yourself for going through this because of how easy it will be to maintain this

project going forward. Right? So just

stay with me. I promise it will be worth it. So let's create client.tsx

it. So let's create client.tsx

inside of here. client.tsx.

So this extension is important because this will be exporting a component. So

since this is a bit of a larger file, I'm going to go ahead and explain what it is. So first of all, we are adding

it is. So first of all, we are adding use client because this has to be a client component. You can see it tries

client component. You can see it tries to access the window here and it's also using some uh hooks like use state that can only be achieved using a client

component. That's why we are using use

component. That's why we are using use client at the top and you can also see the explanation here. So we are importing all of those things. We should

not have any errors because we either created or installed these packages.

So one thing that we are going to change is this. So I don't like how this is

is this. So I don't like how this is specifically tailored for Versell because I don't know where you want this deployed. So don't worry. I'm going to

deployed. So don't worry. I'm going to show you how you can modify this so it works well regardless of where you deploy.

So let's go inside of our let's just save the file uh and let's go inside of environment here

and let's simply add next public app URL and this will be the following. When you

go ahead and do npm rundev you're going to see where your app is being run. So

go ahead and copy this and simply paste it inside.

Then I always like to copy from here and then paste it rather than type it out because you can do some typos if you're not careful. And you can see how

not careful. And you can see how complicated this is, right? So it

recognizes Versell URL and then it has to append the protocol and then it has to add this because Versel URL doesn't have the protocol otherwise it has to guess that we are using the 3000 port.

It's just completely unnecessary. We can

do this much easier. you can return this completely and instead of using this you can just do process environment next public app URL. So then when you deploy, you will simply change this to your

production URL regardless of where you deploy and this will work just fine. And

in my opinion, it's much simpler to work with. And this is important, right? You

with. And this is important, right? You

can see that this will attempt to load the URL from localhost 3000 /trpc.

So it's important that you didn't do any changes herec.

That's why this structure is important.

And then in here we just have some regular uh trpc and uh tanstack query setup. But we have to enable the

setup. But we have to enable the transformer superjson because we did enable it here. So we have to enable it here as well. So let's do the following

at the top here. I'm going to import superjson from super JSON and I'm going to go down here remove this part and simply uncomment. So we are using the

simply uncomment. So we are using the super JSON and you can ignore the error for now. It's because we need to enable

for now. It's because we need to enable it in some other places as well to get it to work.

So once you've done this, we didn't do any changes besides this and this. Right? That's the only thing we changed. So now as per their

instructions, we have to go ahead and wrap this in the root of our application when using Nex.js. Since we are in Nex.js, JS. Let's go inside of the root

Nex.js, JS. Let's go inside of the root of our application and that's inside of the app folder layout. So in here simply go ahead and wrap the entire

application.

So be careful with the component you are importing. It is TRPC react provider

importing. It is TRPC react provider from TRPC client. The reason I'm telling you to be careful is because there are similarly named imports from packages.

We are not importing from any npm package. If you need to be able to

package. If you need to be able to command or controlclick here and it should lead you to this exact component which has this little superjson error.

That's the one we need to import and wrap our application around because if you just do TRPC provider, you can see that that also exists, but that's the incorrect one, right? It's TRPC react

provider from TRPC client component that is currently throwing the error. That's

the one we need.

Once you've done that, we have to create a server.tsx.

a server.tsx.

And this is where things become interesting. So let's just do that now.

interesting. So let's just do that now.

I'm going to go inside of DRPC and I'm going to create server.tsx.

And I'm going to paste this here. And

you can immediately delete this part. So

this is just an example. If your router is on a separate server, not case for us. So we can remove this. So you can

us. So we can remove this. So you can see it's much simpler. Now again, we have all of this either installed or already created.

Great. So now that we have that, uh let's go ahead and just fix this little super JSON uh issue here that I'm having.

So I'm using super JSON in client.dsx.

I'm using it in queryclient.ds.

And I think I should also be using it in init.ds.

init.ds.

You can see that I have it commented out here. So let's enable this and let's

here. So let's enable this and let's import super JSON from super JSON. This

basically helps with serialization when it comes to passing specific props from server to client components with complex objects, right? Like well object, array,

objects, right? Like well object, array, date, things like that. Super.json helps

sparse those things. Uh great. So we now created server.dsx.

created server.dsx.

And this is actually a very very uh interesting file and I'm going to try and do my best to explain why. So that

is basically it for the setup. We are

now ready to use this API. So let's go ahead and let's do that. In here I think they've added the most complicated example. So I'm going to try and use a

example. So I'm going to try and use a more familiar example first. Let's go

inside of source app folder and let's go inside of page.tsx

and let's try and do the following.

Let's add TRPC using use TRPC from naturally client, right? That's our client.tsx

right? That's our client.tsx

component. This is where we import everything client related.

So now we have access to TRPC. You can

see that I can find my hello and I can go ahead and pass in the query options inside. I can find the greeting or the

inside. I can find the greeting or the text and I can say hello.

Right, this isn't doing anything now.

I'm just showing you the API and how it works. So, let's just quickly go inside

works. So, let's just quickly go inside of our routers here so you can see this change in real time and so I can give you a little tip if it doesn't change for you. So, go inside of TRPC routers

for you. So, go inside of TRPC routers app and rename this from hello to, for example, create AI, something like that.

You can see how immediately I've gotten an error here because that's how TR the RPC works. So instead of having to do

RPC works. So instead of having to do localhost 3000 / AI slashcreate dash AI, right, which is most of the

time a literal string, right? So it's

very hard to u it's very easy to make mistakes, right? I can accidentally do

mistakes, right? I can accidentally do this and this is now an invalid API route, but I wouldn't know until I see a 404 error. So what the RPC does is it

404 error. So what the RPC does is it enables full stack type safety from start to end. So if I accidentally make a mistake here, it immediately throws an

error like this route doesn't exist.

That's what the RPC is.

And it is much easier to build your apps when you know that you can rely on your code rather than having to see it break in production and then go and fix it.

Right? So if it works inside of your IDE, if there are no errors here, it will pretty much work everywhere. That's

the power of having uh end to end type safety. And let's go ahead and change

safety. And let's go ahead and change one more thing inside of this input here. This basically represents things

here. This basically represents things you can send to your API. So let's go ahead and imagine an API post request.

Again, for example, this would be create AI. In here we would send something like

AI. In here we would send something like body and then we would somehow you know stringify this with text hello right this is a stupid example right but you

know what I mean uh this is also very easy to break but in here you can see that if I try sending a number here I'm getting an error why because we clearly

defined this needs to be a string so if I change this to uh number now you can see that it works right but if I try string it will break So what happens if

you are not being able to see the same result as me? Right? When I hover over text, I can see the text is a type of number. When I hover over create AI, I

number. When I hover over create AI, I can see the input and text number and output is a greeting of string. Right?

Exactly as I'm typing it here. If you

are seeing type any for everything, you could be having a problem with your setup. So what you can do is you can go

setup. So what you can do is you can go uh inside of extra information and go into frequently asked questions and here

we have it. It doesn't work. I'm getting

any everywhere. So there are a couple of things you can do. The first thing is you can check your tsconfig.json

and in here make sure you have strict true enabled. The second thing is to

true enabled. The second thing is to make sure that you are using the proper TypeScript version and make sure your editor is using the same TypeScript version as package JSON. So for me, none

of these things were ever an issue, but I did have this as an issue and this is what actually fixed my types. So go

inside of your VS Code settings.json.

Let me just go ahead and try and do this. So settings,

this. So settings, let me Oh, I think that yeah, you can just create it if you want to. So go

inside and create a VSS code like this and then settings.json

and you can paste this inside and then you can click allow if this happens. And

let me go back to page.tsx. So nothing

changed for me because this worked from the start. But if you're having any

the start. But if you're having any problems, when I say any, I mean this you're getting the type any all over your project, it could be due to these

missing settings. So this actually fixed

missing settings. So this actually fixed my problem once. So that's why I am sharing it with you. And you can always do commandshiftp and then reload window

and this kind of restarts the TypeScript server. So then it could work maybe.

server. So then it could work maybe.

Great. So now let's actually see the result of this query.

So make sure that you have your app running and that you can visit uh your root page. And first thing you should

root page. And first thing you should see is this very big error. Why? Because

by default in Nex.js every page and component is a server component unless specified differently or if it is a

direct child of a client component. So

let's go ahead and add use client to the top. This will then turn it into a

top. This will then turn it into a client component. And you can see we no

client component. And you can see we no longer have these errors. We can now use hooks as much as we want. So what I want to do now is show you how we actually get data from our API using a very

familiar use query from the package 10stack react query. And in here you would usually create you know your own fetch method which would then call

forward slash AI create AI and then you would pass in the body right and you would have the JSON stringify blah blah blah. What you can do now is the

blah. What you can do now is the following. You can just use the TRPC

following. You can just use the TRPC oneRPC create AI and pass in the query options inside text and let's try Antonio here.

Let's go back inside of Yeah, you can also commandclick here and it will take you directly to the router. So, I'm

going to change this back to string. And

then what we can do is simply render data. Let's just do JSON stringify data.

data. Let's just do JSON stringify data.

There we go. And there we go. Greeting.

Hello Antonio. If I change this to John, it will change to hello John. That's how

we're going to fetch our data. This is

our data access layer. Great. So, this

is the most simple way of fetching data using a client component, right?

Everyone knows this. But let's go ahead and cover two more examples which I think are important, right? So, we've

kind of set up the RPC and we experimented with a client component.

Now, let's experiment with a server component. And then finally, let's

component. And then finally, let's experiment with pre-fetching. And I'm

going to try to explain what it is and why it is important.

So basically server components have several advantages over client components. Uh they are not of course

components. Uh they are not of course one is not a replacement of other. They

work together. But server components have that advantage that they are well on a server which means server components have direct access to the database for example. But in our case

what's important is that they render sooner than client components. Right? So

what is currently happening is that our application has to wait for page.tsx to

be rendered from the server and only then does it start fetching the data.

But what if we could start fetching the data on the server and then continue using the data using this familiar API

within a client component that is prefetching. But in order to understand

prefetching. But in order to understand pre-fetching, let's first remove use client like this and let's break our app. So immediately our app is broken,

app. So immediately our app is broken, right? We can't use tRPC here at all.

right? We can't use tRPC here at all.

Right? So now what we have to do is we have to learn how to use TRPC inside of a server component. And let me just show you uh I'm going to try and do this. So

if I add console log here and just remove these things and just refresh, I think that you should be seeing server component inside of your terminal. How come you're seeing

it in the terminal? Because it is a server component, right? Because if I change this to use client now and change

this to client component and refresh, I'm pretty sure we can still see it here. Yes, this is a bad example. My

here. Yes, this is a bad example. My

apologies. Previously in the past you could not see it if it was a client component. Uh so yeah ignore this. I

component. Uh so yeah ignore this. I

tried to do an example but I used a wrong one. Let's just learn how to fetch

wrong one. Let's just learn how to fetch data from a server component. So in here we actually have some guides. Let me

just go back here.

There we go. So uh in here getting data in a server component. So let's go ahead and simply create this little caller inside of our server.tsx.

So go inside of tRPC server.tsx

and at the end here export const caller call the app router create caller create trpc context right. So there is a way you could you know fetch data from a

server component and that would be either you know directly calling the database like calling Prisma or there is nothing stopping you from fetching

inside of a server component. So you

could again do create AI and then the body blah blah blah right but that is unnecessary overhead because server component already has access to the

database right no point in doing this that's why TRPC has invented something called a caller so what you can do now is let me just confirm the API you can

import the caller so let's just do that so I'm going to do cons data and turn this into an asynchronous server component component and await caller

from DRRPC server create AI and let me just see do I have to do it like this? I

have to Antonio server and then I believe let me just check is it data like this? There we go. JSON

stringified data.

So this is how you would fetch from a server component using TRPC using a caller. So this isn't doing a network

caller. So this isn't doing a network request on the server component. This is

server component literally directly having a remote procedure access a remote procedure protocol to TRPC.

That's why TRPC is so powerful because I think it's one of the only RPCs available that has these types of callers. I could be wrong, but it's the

callers. I could be wrong, but it's the only one I've seen that performs this well. And this isn't so impressive right

well. And this isn't so impressive right now, but if you've worked with server components before and you tried any kind of RPC here, you would almost always

encounter an issue that you lose authorization headers, right? Uh the

server component would never know if the user is logged in or not. TRPC solved

that problem as well. That's why I love TRPC so much because it allows us to leverage server components. So now let's finally do the uh way we are going to

use the RPC and that is using pre-fetching. So we would do things like

pre-fetching. So we would do things like this const query client would be get query client from gRPC server. We're not

going to call the caller here but we will import tRPC from here. And then in here we're going to do void t uh

queryclient dot prefetch query drpc create ai query options text Antonio

and this time prefetch and then in here what we would do is we would add a hydration boundary from

tanstack react query.

We would pass the state to be dehydrate again imported from tanstack react query and pass in the query client. And then

in here we would render a client component.

So let's go ahead and create that client.tsx.

client.tsx.

This is not a reserved keyword. So this

is just a component which needs use client at the top. And then in here what we would be able to do is the following.

We can now get the data by using use suspense query from tanstack react query. And let's add

our TRPC from the client and pass in TRPC create AI query options. And

important, you need to have the exact same text here. Otherwise, the

pre-fetching will fail because this would usually be some kind of filter.

For example, instead of text, you would most likely have page one limit 10. So

if you prefetch one thing and then expect to have something in suspense in the client component, it wouldn't work.

That's why it is super important that your query options are exactly the same in your prefetch and in your client component. And then in here, you would

component. And then in here, you would be able to do JSON stringify data.

And now let's go ahead and let's do this. So let's go inside of here. Let's

this. So let's go inside of here. Let's

import client from dot /client and let's wrap this inside of suspense from react

and let's add a fallback here loading.

So what's going on here?

Basically instead of directly calling the data inside of a server component what we are doing is we are leveraging t uh we are

leveraging tanstack queries cache and state and we are immediately populating it the moment server component gets created the moment server component

loads because this will then allow the client component to load whenever it loads because we know a server component will load sooner than client component,

but this time the client component won't have to wait until it gets loaded and only then initiate a network request.

Instead, it will already have the data ready even though because we prefetched it inside of a server component. And

it's very important to use a void here.

And this prefetch query actually doesn't return anything. So even if you tried

return anything. So even if you tried things like this, this wouldn't work. it

doesn't return anything. They've done

that on purpose so you don't uh so you don't actually use this data because pre-fetching query all it does is it initiates a call on a server component

but only for the sole purpose of populating uh tanstack query which can then be accessed in a client component. So we

are leveraging server component to start fetching our data immediately and then we are passing it down to the client component which uses a familiar API. So

because this is use client, we can now go ahead and have a use effect here, right? We can go ahead and we can have a

right? We can go ahead and we can have a state here, right? We can do all of these things and it would work as fast as if we did the entire thing in a

server component. So we don't lose the

server component. So we don't lose the familiarity of client components and we also don't lose the speed of server components. We basically get the best of

components. We basically get the best of both worlds by doing this. And let's

finally try it out. You can see it works just fine. I know this is still a bit

just fine. I know this is still a bit confusing. I try to explain it the best

confusing. I try to explain it the best I can. I would highly suggest, you know,

I can. I would highly suggest, you know, reading about prefetching and just going through uh this documentation in the first place. Perhaps Dave explained it a

first place. Perhaps Dave explained it a bit better, but this is how I like to explain it. Basically, we are now

explain it. Basically, we are now getting the best of both worlds. both

the speed of server components and the familiarity of client components and I think that this officially uh marks the end of this chapter right so

let's just remove these things because we don't need them and what we're going to start doing in the next chapter is finally initializing our uh background

jobs and start introducing some background actions we now have our database we have our OM and we have our data access layer. So those are the three things that we need so we can

start saving uh some data in our database properly. Right? So let's go

database properly. Right? So let's go ahead now and mark this as completed. We

did all of these things and let's go ahead and branch out now. So I'm going to go ahead and go down here and create a new branch. I'm going to call this 03

DRPC setup. I'm going to go inside of my

DRPC setup. I'm going to go inside of my source control here and I'm going to stage all changes and then I'm going to

add 03 TRPC setup comment and I'm going to commit and then I'm going to uh publish the branch.

Then let's go ahead to our GitHub to open a pull request.

So in here compare and pull request and let's create a pull request.

And as you can see I have something that you probably don't in your pull request and that is an AI code review using code

rabbit. As you can see not only do I

rabbit. As you can see not only do I have a complete summary of this pull request. As you can see, we introduced

request. As you can see, we introduced TRRPC integration for type safe API calls between client and the server and we also added some chores. Not only

that, but I have a change file by file summary so you can see exactly what I did in each file. And I also have a

sequence diagram explaining exactly how every single component works in which order and how it responds with data and result, which is especially useful if

this is your first time working with the RPC and Tanstack query.

And I also have some potential issues it caught. For example, we added this dummy

caught. For example, we added this dummy TRPC context from the documentation.

Right? So, it noticed that and it told me that I should replace this hard-coded user ID with proper authentication. In

this exact case, it is okay for us to proceed because this is just uh our initial TRPC code. We will replace this later when we add authentication. But

you can see how it already started noticing some potential issues in our code. And then it also told us to change

code. And then it also told us to change this further on when we update this React context. So it actually

React context. So it actually understands our code very very indepth.

If you're interested in having the exact same code review, you can use the link in the description or the link you can see on the screen and create a Code

Rabbit account. But that is not all. So

Rabbit account. But that is not all. So

now I'm going to go ahead and merge this right here. Let's merge this pull

right here. Let's merge this pull request.

I'm not going to delete my branch simply so I have access to it right here. So

this was our previous chapter and now we have TRPC setup. And you can see that I got this popup in my Visual Studio Code as well asking if I want to start a

review inside of my Visual Studio Code.

So, since I just reviewed my code uh in a pull request, I'm going to click no.

But I'm going to show you in a second what this is. So, before we proceed, go ahead and make sure you change back to your main branch. And then just click on

this little button to synchronize the changes.

And then when you click on a graph here, you should see our last one was a database commit. And then we merged

database commit. And then we merged that. And now we branched out for TRPC

that. And now we branched out for TRPC setup. and we merge that back inside.

setup. and we merge that back inside.

And what I suggest you install is Code Rabbit extension. So Code Rabbit is a

Rabbit extension. So Code Rabbit is a completely free VS Code extension. You

can go ahead inside of your extensions here, Code Rabbit, and install it. And

if you don't want it connected to your pull requests, you can have complete free code reviews in your Visual Studio Code. All you need is an account with

Code. All you need is an account with Code Rabbit. You can use the link you

Code Rabbit. You can use the link you can see on the screen and you will get amazing pull request reviews, but also you will get completely free code reviews in your IDE. We're going to try

that in the next chapter because we just reviewed our code in a pull request this time. So again, just confirm you are on

time. So again, just confirm you are on your main branch and you have synchronized all changes. And that will allow us to continue to the next chapter. So let's go ahead and mark this

chapter. So let's go ahead and mark this as done. Amazing job. We now have the

as done. Amazing job. We now have the database access, our ORM, and our data access layer. We are finally ready to

access layer. We are finally ready to start doing some AI related things and background jobs. Amazing, amazing job.

background jobs. Amazing, amazing job.

And see you in the next chapter.

In this chapter, we're going to learn all about background jobs. We're going

to learn how to add them to an Nex.js GS application and we're going to learn what they are and why we need them. In

order to understand that, let's first look at a normal example. Imagine a

login form. You enter your email, your password, you click login, we send a network request, and we get an instant response. Success or fail. We've already

response. Success or fail. We've already

seen this a million times. But now

imagine you have a more complex task at hand. Imagine you offered your user an

hand. Imagine you offered your user an ability to generate a summary of a very very long YouTube video. Imagine my

videos for example, they are sometimes 12 24 hours long.

So this time we send a network request.

And in order for our backend to generate this summary, it can take well over 30 seconds to do that because just imagine everything that needs to happen. We

first have to download the YouTube video. Then we have to transcribe the

video. Then we have to transcribe the video and only then can we send that to an AI model to generate the summary. So

depending on the size of the video, depending on the AI model you will use and depending on your overall infrastructure, it can take well over 30 seconds for that to finish. And if you

have a task that's running for so long within a normal network request like this one, you know, something completely normal, you risk your user never getting

the result. So the problem is not that

the result. So the problem is not that the user has to wait. The problem is this network request can time out, the user can accidentally close the tab, or

the user can lose their connection. If

any of these things happen, the user will never get the result back and we have to start the entire process again.

That's why we have something called background jobs. So let's imagine this

background jobs. So let's imagine this again. The user clicks generate summary.

again. The user clicks generate summary.

This time we send a network request again, but instead of using our backend to generate the summary, we use our backend to invoke a background job. And

the moment we've done that, we are finished with our network request, which means that we immediately return back to the user and say the summary is being generated and the user can now close the

tab, they can go for a run, they can do whatever they want. What's actually

happening is that the moment we invoke a background job, the background job now runs in a separate environment independent of the user's session,

independent of the user's connection, right? And we can simply notify the user

right? And we can simply notify the user when we are done. This is the structure you have to understand if you want to build AI apps because depending on the model you will use and the complexity of

the app you will build most of your tasks will be longunning tasks.

We will achieve this in our project using ingest. So let's go ahead and

using ingest. So let's go ahead and create our first function and let's trigger a background job from nextjs.

You can use the link you can see on the screen or the link in the description to let them know you came from this video.

And once you're here, you can immediately go inside of the documentation, select NextJS, and select app router here. And then

let's go ahead and install inest.

Before you do that, just make sure that you are on your main branch. And you can click synchronize changes just to confirm uh you didn't have any unsaved

changes here. You can see my last

changes here. You can see my last chapter was TRPC setup and yours should be as well. So I'm going to shut down my

app now and I'm going to do npm install injust.

And now that I have this installed, let me quickly show you my package JSON so you can see the version that I'm using.

Once you have installed Injust, the second step is to run the Ingest developer server.

The version that I will be using is 1.8.0.

But you can see that in here they simply target the latest version. So when you see me now doing this npx inest- cli at

latest, the latest is equivalent to 1.8.0.

zero just in case you were interested in my exact version. So, npx inest- cli at latest or a specific version and then

dev. And when I start this, you can see

dev. And when I start this, you can see that it says injustdev server is online at localhost 8288.

And if I visit the project here, well, you can see that not much is going on here. It is a developer server, but

here. It is a developer server, but nothing is here for us to do. So what we have to do now is we also have to have

our app running. So let's do mpm rundev.

And in here you will start to see something. You will start to see a bunch

something. You will start to see a bunch of 404 pages here. That is because the ingest developer server is trying to find our inest initialization in our

project. But since we haven't done that,

project. But since we haven't done that, we just have a bunch of 404s. So let's

go ahead and continue with the documentation here.

I'm going to go ahead and create an ingest folder and then put a client.ts inside with this simple code snippet.

So let me go ahead here inside of source and I will create a new folder called inest and I will create client.ts

and I will paste this inside. I'm going

to call this vibe project or vibe development something like that. basically the name of your project here and once you've

done that you should oh actually not yet sorry so this is the first step and then we come to the second step which is creating an API endpoint for ingest so

you can copy this snippet as well and go inside of app API create a new folder ingest and inside go ahead and create

route ts and paste this inside and you and replace this with an add sign and a forward slash. There we go. It's

forward slash. There we go. It's

basically using this inest which we just created. And the moment you save this

created. And the moment you save this file, if you have named it correctly and put it in the API folder, you should go here and you will see that

now you finally get 200 here because it finally found the ingest integration and now it will only try to hit that endpoint instead of all of these other

ones.

Great. But we still don't have anything useful in the developer server here. So

let's go ahead now and let's continue by creating the first ingest function.

So I'm going to go here inside of source ingest functions.ts and create a new function. So inside of ingest folder in the source, make sure

you don't accidentally do this inside of API inest. So in here create functions.

API inest. So in here create functions.

Whoops.

Functions. DS

import the client and a simple hello world in just create function. We give

this an ID. We give this an event name.

And we have a very simple step which waits for 1 second and it then returns a dynamic string which uses the data we can pass to this background job. So this

is an example. So they show us how easy it is for us to pass some data to this background job. This would be for

background job. This would be for example a link to a YouTube video we want to summarize, right? That's what

this data object would hold. For

example, I think I have a typo in my functions here. So, let me just fix

functions here. So, let me just fix that. And then let's go inside of the

that. And then let's go inside of the app folder API inest route and inside of here, let's import hello world from that

inest functions file. And now if you go back here you should be seeing uh your app here available auto detected. You can see

everything is fine. It found it at API inest framework next.js and one function found hello world because we just added

it here. So if I rename this to hello

it here. So if I rename this to hello world 2, it immediately renames here as well. And now instead of these

well. And now instead of these functions, let me just refresh. So, it's

back to this name. I can now click invoke here. And you probably have empty

invoke here. And you probably have empty data, but you can go ahead and add email and pass in I don't know it it can even just be the name. It doesn't matter. And

click invoke function. And you will see how it went from ceued to running to completed. So that's the status that

completed. So that's the status that just happened. It happened very quickly

just happened. It happened very quickly because uh there wasn't no no no one there was no function before this. So

the queue to running went very fast and the running only took 1 second because we only wait for 1 second. For example,

let's now increase this to 10 seconds and save the file. And let's go inside of our functions invoke and let's click

this again. You can see how now it keeps

this again. You can see how now it keeps running. It's running for 5 seconds, 7

running. It's running for 5 seconds, 7 seconds, and finally after 10 seconds, it's finished. Is this getting familiar

it's finished. Is this getting familiar to what we just discussed? This is a background job. The only problem so far

background job. The only problem so far is that we are not invoking this function from our network request. We

are manually clicking on invoke here.

How do we invoke this from in our case TRPC procedure? Let's go ahead and let's

TRPC procedure? Let's go ahead and let's do that.

So I'm going to go inside of source trpc routers_app and in here I'm going to call this invoke. This will be base procedure and

invoke. This will be base procedure and let's add an input here z.object

and let's pass in the text to be z dot string like this. And then instead of dotquery, let's add dot mutation. This

time it's going to be asynchronous here.

Let's extract input from here like this. And this time what we're

like this. And this time what we're going to do is await inest from injust client.

The name of the function you can find here. This is the name of the function you will run.

So pass that here and then you pass in the data and the data can be anything you want but we know that we accept email here. Uh so let's go ahead and

email here. Uh so let's go ahead and pass email to be input.ext

because we defined it as text here. And

this is how you invoke a background job from TRPC. So now let's go ahead and

from TRPC. So now let's go ahead and actually use this invoke method. I'm

going to go inside of source at folder and I'm going to go inside of page.tsx

here and uh well we already have this set up but since we are not going to need it I'm going to delete it for now.

I'm going to delete this client.tsx.

I'm going to go back inside of the page here and I will simply return a div here test and I will remove everything from

here. We only use this to learn about

here. We only use this to learn about the RPC. So let's mark this as use

the RPC. So let's mark this as use client. So this becomes a client

client. So this becomes a client component. The use client is very

component. The use client is very important for our demonstration here. So

please do it. Now when you refresh on your local host 3000, you should just see a test here. So now let's go ahead and let's create a padding for maximum

width 7 XL MX auto. And in here let's add a button component.

invoke background job.

And there we go. We now have a button to invoke a background job. So let's go ahead and add TRPC here.

Use TRPC and let's add invoke from use mutation from tanstack react query. Pass in

tRPC.invoke

and pass in the mutation options here.

And then in here on click call invoke and call mutate

and pass in the text to be test or John something like that.

So what I want to demonstrate to you now is how quickly this background job uh lasts in comparison to the network request. So, I'm going to go ahead and

request. So, I'm going to go ahead and open my developer console here. I'm

going to go inside of the network tab and I will click invoke background job and let me just see did I even do anything now or not

because I I'm definitely expecting to see something here but it is not uh happening.

Let me refresh the page.

All right. So my website froze. So what

I did was I simply shut down my app and I did npm rundev again. So I'm hoping to try it out again. This time

successfully.

There we go. Now when I click on invoke a background job, you can see how quickly this finished. Let's just see.

468 milliseconds. That's how long this network request took. But we know that the actual background job took 10

seconds. So that is what we wanted to

seconds. So that is what we wanted to achieve. If I go inside of the RPC

achieve. If I go inside of the RPC invoke again uh and inside of the ingest and go inside of the functions, let's change this to 30 seconds and add a

comment here. You know, imagine this is

comment here. You know, imagine this is a u download step right here. In here we are downloading a video. Then in here,

imagine this is a transcript step. Another 10 seconds. And

transcript step. Another 10 seconds. And

then finally, you know, imagine this is a summary step. And this finally then

summary step. And this finally then takes 5 seconds. This is what we want to achieve, right? The moment we click on

achieve, right? The moment we click on generate summary, we send a network request. We trigger a background job and

request. We trigger a background job and we immediately allow the user to close the tab. Right? So let's try it again.

the tab. Right? So let's try it again.

Uh in order if you want to you can also do this. You can go inside of your

do this. You can go inside of your layout in app folder and you can add a toaster

from components UI soner like this. I just like to order the

like this. I just like to order the components like so. And now once you've added the toaster you can go inside of the page here go inside of mutation

options and add on success here.

And you can add toast from soner.su

success background job started.

And now you will see the following. If

your app gets stuck, you can go ahead and just do mpm rundev again. Uh I did get this happen a few times and I think I solved it when I removed the turbo pack, but we'll see. Basically, if your

app hangs on loading, don't worry. You

can just uh restart it. it will not happen in the actual app. So let's try this now. Uh we can also do

this now. Uh we can also do disabled here and let's do invoke is pending

like that and we can also go inside of invoke function here. the RPC invoke and after a wait let's return

okay success like this so now if you click invoke background job you can see this is it

the request is finished right the user can now close their tab and what's happening is in the background right we

are now doing the first step which would be 30 seconds of downloading a YouTube video. After that, we're going to go

video. After that, we're going to go ahead onto the second step, right? Which

would be transcribing a video. So, let's

see. After 30 seconds here is finished, we go to the second step. And this step will last for 10 seconds because we just

wait for transcription to happen. And

then finally, we have a third step. And

in here we would do the AI summarization. And that's it. Hello John

summarization. And that's it. Hello John

or this would actually be the summarization. Right? So that is how

summarization. Right? So that is how background jobs work. And that's how you add them uh in a Nex.js environment.

Right? And it doesn't matter if the user lost their internet connection. It

doesn't matter if they're closed the tab because the moment they invoke a background job, the background job has started. they can lose their internet

started. they can lose their internet connection. Obviously in development

connection. Obviously in development mode if you lose the internet connection your dev server would fail. So yes in development technically you need you

cannot really shut down your laptop but in production it will run on a separate server in a separate environment right and from this inest developer server you

can easily cancel things if you don't want to you can rerun them uh you can go ahead and look at the payload that was added you can do a bunch of things here

uh and one cool thing about ingest steps is this will later be of course more complex things than just sleeping for 30 or 10 seconds. This will be API calls,

database requests and if they fail, it is crucial to well retry them and that is what inest does automatically for you. Uh they actually have a cool

you. Uh they actually have a cool example on their landing page here. They

have uh and yes they have agent kit.

This is something I didn't want to talk about immediately because I don't want to confuse you, right? So uh alongside background jobs, we're going to use injust to build autonomous agents,

right? So AI and background jobs go hand

right? So AI and background jobs go hand in hand and Injust is the platform to do both of that. But I first want to introduce it through background jobs because it's easier to understand,

right? And in here they have three very

right? And in here they have three very cool examples. So this is the

cool examples. So this is the transcription example. You can see that

transcription example. You can see that uh they have a step called transcribe video. So it's very similar to

video. So it's very similar to step.slip, slip, but instead it is

step.slip, slip, but instead it is step.run

step.run and they call it transcribe video and in here they simply return a deepgram SDK with a function to transcribe a video

URL and once this steps finishes they call an LLM chat GPT uh GPT4 to create a summary. So exactly what we did. So this

summary. So exactly what we did. So this

would be the two steps right this would be the first one you know transcribe the video and then the second one summarize the video right I just use a download step as well so looks like we don't need

a download step right so this is what you can use in just for the second one is to build AI with automatic retries cach caching and improved observability

right so it's way more powerful than I can showcase in this short chapter that's why We will have more chapters later on to build the actual agent

networks, agent router, and agent tools, right? And of course, you can do sleep,

right? And of course, you can do sleep, which right now seems only for fun, but sleep can be very useful. For example,

you can send a welcome email to a user, then wait a week, and then send a follow-up email. So, yes, that's how

follow-up email. So, yes, that's how long these tasks can wait. They are

background tasks. Uh, perfect. Yes. And

for development, you don't need any account yet. But later uh for

account yet. But later uh for production, for deployment, we're going to have to create an account with ingest. But for development, there's

ingest. But for development, there's going to be no need for that as well.

And I think that's great because we can get started right away just by running this. Perfect. So I think that was the

this. Perfect. So I think that was the goal of this chapter. I think we achieved this exact thing right here. So

we set up inest. Let me just change the color. So we've set up inest. We created

color. So we've set up inest. We created

the first function. We explored the ingest developer server and we triggered a background job from Nex.js. Now let's

go ahead and branch out and push this to GitHub. So let me see this chapter name.

GitHub. So let me see this chapter name.

This is 04 background jobs. So I'm going to go ahead and click here. You can see that I have 10 unsaved files. And I also have this little database in the inest

folder. If you're wondering what that

folder. If you're wondering what that is, I'm just guessing it's cache for the inest developer server.

And now I'm going to go and click on the main here. I will click create new

main here. I will click create new branch and I will do 04 background jobs.

After I've done that, I'm going to stage all of my changes and I'm going to create a commit message and I'm going to click commit. And then

you can see that code rabbit extension if you remember from the previous chapter uh you can install this a completely free AI code extension which

allows you to review all of those files.

So let's go ahead and review all of these files and while this is doing its own thing which is most likely a background job on its own. So you can see uh you can see background jobs every

day right? You probably just didn't know

day right? You probably just didn't know they were called background jobs. While

that is going on, let's go ahead and let's create a new pull request inside of our repository here. So, let me go inside of pull requests here. Uh, new

pull request.

Uh, oh, looks like it won't push until uh this reviews. So, I'm going to go ahead and review first.

Actually, it will. I just have to click publish branch. Yes, I forgot to click

publish branch. Yes, I forgot to click that. So, make sure you click publish

that. So, make sure you click publish branch. And you can see that this is

branch. And you can see that this is still doing its own thing in the background. And I think now that we have

background. And I think now that we have a new branch. There we go. I forgot to do that. My apologies. So, now we're

do that. My apologies. So, now we're going to have two uh AI reviews here.

One is going to be from here and the other one is going to be from here. And

you can see how cool it is that it can add comments on my code locally here in my There we You can see fox fix duplicate

step ID. So it noticed that all of my

step ID. So it noticed that all of my steps are called wait a moment uh which is very you know not useful uh when you were reading inside of the ines

developer server. So it already detected

developer server. So it already detected that for example. So you can see how cool it is and it also fixes some wrong things here. Payload mismatch with

things here. Payload mismatch with ingest function. This is of course not

ingest function. This is of course not something that uh we need to fix right now simply because this is a demo. But

it is very useful. As you can see, it detects pretty much everything. So if

you don't want it here in your pull requests, you can have it here in your uh IDE.

And here we have our pull request summary which is pretty much identical as you can see uh to the reextension for

Visual Studio Code. You're going to see in here we have fix duplicate step ids and you can see that here at the bottom I have the exact same issue here. So you

can choose which one do you like more.

Do you like your pull request reviewed or do you want to submit clean pull requests by having this run before you

push a pull request? So in here I'm going to read through the pull request simply because we have the summary here.

So we introduced an API endpoint to handle background jobs using inest. We

added a button on the main page to trigger a background job with real-time toast notification on success. We use

this to visually measure how quickly the network request is finished in uh comparison to how long the background job actually lasts. Right? And this is

what I like the most. I like the sequence diagram because it is exactly what we discussed in the beginning of the chapter. So the user clicks on

the chapter. So the user clicks on invoke a background job. We send the invoke mutation with the text and then

our TRPC router which is our network request simply sends the event with that data. This can basically read as invoke

data. This can basically read as invoke a background job and the moment we do that we can send back the user. Okay,

success. So this part right here is identical to what I wanted to achieve here. The user clicks, we send the

here. The user clicks, we send the network request, we forward the data to a background job, and we immediately respond to the user so they can close

the tab and move on. And the fun thing about this background jobs is the way Code Rabbit just reviewed my 10 files

here is by using a background job. They

definitely didn't have a network request which went on for 30 seconds. They had a background job which did this exact

thing. So I went through this potential

thing. So I went through this potential changes. They are all very correct. But

changes. They are all very correct. But

since this was just a demonstration, it makes no sense to fix them right now because we will remove the whole page entirely uh in the first place. Right?

So we are good to go with merging this pull request right here. I'm not going to delete my branch simply so I can go

back to this part whenever I want. Uh

and let's go ahead now right here. Let me close this. Let's go

right here. Let me close this. Let's go

back to our main branch. Let's click

this and let's synchronize our changes.

And after that, I'm going to click no on this. I'm going to go inside of my

this. I'm going to go inside of my source control on the graph. And you can see that I have 04 background jobs now merged right here. And you can see that

I'm on the main branch. And I should have access to my inest folder here, which basically means we fixed all these things. Perfect. I'm also not going to

things. Perfect. I'm also not going to do uh anything regarding this code rabbit uh comments here simply because all of this was just a demonstration.

Amazing amazing job. Let's go ahead and mark this as complete now and see you in the next chapter when we are going to extend the use of our

background jobs with AI. Amazing amazing

job.

In this chapter, working to implement AI background jobs. In order to do that,

background jobs. In order to do that, the first thing we're going to have to do is choose our AI provider. In here, I have added a list of all the options

that we have and some comments for each of them. Starting with the best choice,

of them. Starting with the best choice, which is Open AI. It is by far the most reliable, the most normal rate limit

with a fast reset and a very very good coding model. This is the coding model

coding model. This is the coding model that I have chosen GPT4.1 and it is almost perfect. The absolute

best coding model though is cloth specifically set 3.5 or 4. They are

kings of coding models. The problem with anthropic is a very strict rate limit and when you hit the rate limit it will

take you longer than 24 hours for that rate limit to reset. So it's it is just very very annoying to work with. If you

want to, you can choose anthropic, but you will almost certainly hit a rate limit and you're going to have to either change the model or create a whole new

organization and account. So, basically,

Anthropic allocates their resources to uh higher paying customers, right? Which

are this very very large companies. So,

it's not exactly suitable for tutorials.

As per Grock or XAI, I'm not sure. I

haven't worked with it. It is on the list of the supported AI models, so I don't know. I won't recommend it and I

don't know. I won't recommend it and I won't tell you not to use it. I I'm not sure. And as for Gemini or Google, the

sure. And as for Gemini or Google, the great thing about Gemini is the amazing free tier. The biggest problem with it

free tier. The biggest problem with it for our use case, it is just not good for calling tools. It will straight up be errors all around. So because of that

at this moment I just don't recommend it. Uh I've heard that Grock AI has free

it. Uh I've heard that Grock AI has free tier. So I would rather you use Grock

tier. So I would rather you use Grock than Gemini. So unfortunately at this

than Gemini. So unfortunately at this point I cannot recommend Gemini. It is

okay for this simple chapter that we're going to do now, but later when we use AI for the thing we will actually need to use it for, it will simply not work.

So if you really need a free tier, you can try and use Grock rather than Gemini.

The absolute best choice and the choice that I will be using is Open AI, specifically this model. As I said, there is a chance we might hit the rate

limit here, but the reset is around 2 seconds, which is completely fair and it will happen rarely, only when we are doing some very very large uh tasks.

With Enthropic we get the amazing results. It completely understands

results. It completely understands Nex.js ecosystem. It understands what

Nex.js ecosystem. It understands what chats and UI is. But once you hit a red rate limit and you will hit it very soon. It is almost impossible to get rid

soon. It is almost impossible to get rid of. You will almost be stuck in a rate

of. You will almost be stuck in a rate limit. So in my opinion, choose Open AI.

limit. So in my opinion, choose Open AI.

It is the simple best solution for this project. If that is possible for you,

project. If that is possible for you, you will have the best uh experience using Open AI. And now I'm going to show you uh how you can find if any changes

have been made regarding this uh if you're watching this tutorial in the future. So you can use the link in the

future. So you can use the link in the screen uh again or link in the description to visit ingest and in here

go to the documentation and then go ahead and find agent kit and in here go ahead and click on this support for

openai anthropic and gemini or click on the models here. So in here you will see all supported models as you can see open

AAI anthropic Gemini and Grock as I said even though Gemini is supported here I just wasn't able to get it to work if you want to you can try but I wasn't

able to get it to work. Anthropic worked

amazingly, especially the 3.5ET versions, but the rate limits were very easily hit. The Open AI, I initially

easily hit. The Open AI, I initially tried with 4.0 and I really was not satisfied with the results. It's not

that good. But even though it's not on this list, you can try 4.1. So, that is confirmed. I tested it myself and it

confirmed. I tested it myself and it works no problem and it's amazing. Not

as good as Enthropic 3.5, but very very good and very reasonable rate limits. So

what we have to do next is we have to create our account in one of these providers. I'm going to show you what I

providers. I'm going to show you what I do with OpenAI and then you can do whatever you want to choose here. In my

case, I'm going to go ahead to platform.openai.com.

platform.openai.com.

You can use the link you can see on the screen or link in the description. Once

you've created your account, you're going to go ahead into settings.

Once you are in the settings, you're going to go into billing. In here, it is very important that you have a credit balance. So, maximum of $10, even less,

balance. So, maximum of $10, even less, will be enough for you to complete this tutorial many times, which will of course depend on how often you create uh

new uh websites and uh apps with this project. but I barely spent that amount

project. but I barely spent that amount and I tested pretty heavily. Once you

have uh filled your account, you can go ahead and obtain an AI key. If you're

using Grock or Gemini, you have a free tier, but as I said, Gemini just doesn't work. Uh and Grock, I'm not sure. You

work. Uh and Grock, I'm not sure. You

can try. So, let's go ahead and let's create a new secret key. I'm going to call this Vibe development. I will use the default project and I will select all permissions and I will create the

secret key. I will then copy this key

secret key. I will then copy this key and then what we have to do is we have to add that to our IDE I mean to our project. As always ensure that you're on

project. As always ensure that you're on your main branch and you can synchronize the changes just to make sure you're up to date. As you can see my last chapter

to date. As you can see my last chapter was background jobs. So now what I'm going to go is I'm going to go inside of environment here

and I'm going to create open AI here open AI API key and I will paste it inside like this. If you're using something else let me show you how to

add that. So I'm going to go inside of

add that. So I'm going to go inside of the inest uh agent kit documentation here and here you have it environment variable used for each model provider.

If you're using OpenAI, it is OpenAI API key. If you're using Anthropic, it's

key. If you're using Anthropic, it's Enthropic API key. If you're using Gemini, it is Gemini API key. Or if

you're using Grock, it is XAI API key.

So, make sure that you've added one of those here. Perfect. Now that you have

those here. Perfect. Now that you have done that, let's go ahead and do the following. Uh, go inside of the agent

following. Uh, go inside of the agent kit by ingest and go inside of installation. and let's go ahead and

installation. and let's go ahead and install inest agent kit. So, I'm going to go ahead and install this and I'm going to show you the version.

Once this has been installed, I'm just going to go inside of the package JSON and show you the version 0.8.3.

That's the version I'm working with.

Now, let's go ahead and let's use the agent kit. In order to do that, I just

agent kit. In order to do that, I just want to do the following. Let's go ahead and do npm rundev. And let's go inside of source app folder page.tsx.

And in here, what I'm going to do is the following. I'm going to add a simple

following. I'm going to add a simple input from components UI input. And

above this tpc methods, I will add value set value and a simple use state from react. Make sure you import that. I'm

react. Make sure you import that. I'm

then going to give the input a value and on change a simple event calling set value and setting it to event target

value. You've probably done this 100

value. You've probably done this 100 times. And this will simply be uh it can

times. And this will simply be uh it can stay invoke background job. It doesn't

really matter. Great. So now let's go ahead and run npx inest cli latest dev.

Simply so we have both our app and the dev server running. And now let's go ahead and do the following. Let's go

inside of our inest functions here. And

let's just remove this one. Leave this

one for 5 seconds like this. And change this to

like this. And change this to um let's just say input. Let's call it that. And then I'm going to change this

that. And then I'm going to change this to be input as well. Actually I'm going to change it to be value. So we control it from this input here. I'm going to go

instead of the invoke tRPC method. So it

is inside of routers here. I will change this to be input.

I will change the input to be input.

Well, I just call that in a dumb way, didn't I? Why don't we just call it

didn't I? Why don't we just call it value? That would be better. Sorry. So

value? That would be better. Sorry. So

let's go inside of invoke change this to value input dov valueue and call this value and then make sure to save this file go back inside of the functions and

change this to hello event data value.

So let me show you the changes again inside of the page we added use state and the input with value and set value.

We then added a control to this input with those fields and we modified slightly the invoke.mmutate to pass in the value to be the value from the

state. We then modified our TRPC router

state. We then modified our TRPC router to accept value in the Z object and we've accepted we changed uh the ingests

send to pass in value in the data object and of course we modify the function to read dot value and we removed an extra waiting step.

So now that you've done this let's go ahead and let's run our app on localhost 3000 and let's open our uh development server here. So now I'm going to call

server here. So now I'm going to call this uh test value and I will click invoke background job and then in here in the running uh text I should see

value test value here and in finalization hello test value. So

exactly what we pass here. Perfect.

That's a very good setup. Now that we have agent kit installed let's go ahead and do the following. in the inest documentation which is outside of the engine agent kit you can find a very

very simple example by going inside uh let me just find inest functions step and workflows AI interference here and

in here where they show you agent kit for the first time uh they show you this very very simple uh way of doing it so this is what we're going to do I'm going

to add the following import. So let's

now go inside of inest functions here and I'm going to add this

agent agentic open AI as open AI and create agent from inest agent kit and then I'm going to go ahead and open

this function. It's already opened,

this function. It's already opened, right? So basically I'm going to now

right? So basically I'm going to now write inside of here. You can leave this hello world. This can be unchanged.

hello world. This can be unchanged.

Let's create a new agent like this.

So let me just show you this. And you

can remove this.

It is directly open AI, right?

And you can remove the step here as well.

So instead of writer, let's call this summarizer.

The name will be summarizer.

You are an expert summarizer.

You summarize in two words. So something very obvious, right? A very easy task. You can give it

right? A very easy task. You can give it a model GPT40 if you're using Open AI.

And let me just remove the things I don't need for now. Let me remove the step from here since I don't need it. So

in here we open a summarizer agent like this. And since I'm using open AI, these

this. And since I'm using open AI, these are the models that I can use. One of

them is CH GPT40. If you import anthropic from here, you can see that then you're going to have to choose one of these models. So

just pick the one you like. And same is true for XAI or Gemini, whatever you ended up using. So now we have to find a way to invoke this summarizer with event

data value.

And since you saw when I copied this import, I had to fix the invalid OpenAI import because I've copied it from here,

right? So it would be best if you follow

right? So it would be best if you follow the instructions for agent kit on the actual agent kit documentation. Again,

you can find it right here under agent kit. I simply used this one because I

kit. I simply used this one because I thought it was a very similar example to what we discussed in the previous chapter with the summarizer, right? But

I think it would be better for you to follow the agent kit documentation here because this is the one that is kept up to date constantly. So please follow this one. So you can again go inside of

this one. So you can again go inside of the agents here and you can find this exact thing we just did. We created an agent. We called it summarizer and we

agent. We called it summarizer and we gave it uh a system prompt and then we gave it a model. So we did that correctly. Now what we have to do is we

correctly. Now what we have to do is we have to run it. So let's go ahead and do that right here. I'm going to go ahead and add this summarizer

summarizer.run

summarizer.run like this. And let's go ahead and learn

like this. And let's go ahead and learn what to type here. So I'm going to go ahead and add summarize the following

text and I'm going to open back. So I can insert event data value like this. And now

let's go ahead and add a weight here.

And now we have access to the output here. Uh so let me just see. I'm not

here. Uh so let me just see. I'm not

sure if I know the API by default, but let me try output first in the array.

Is it uh like that? I'm not exactly sure. Let's try and let's just say

sure. Let's try and let's just say success.

Okay, here. And let's rely on the console log. Or perhaps we can just

console log. Or perhaps we can just return out the whole output like this.

Maybe this would be easier to work with.

So we just created a very simple summarizer agent which is an expert summarizer and can summarize in two words. We imported open AAI and create

words. We imported open AAI and create agent from inest agent kit new package that we have installed. We specified a

GPT40 model. Another hint here uh I mean

GPT40 model. Another hint here uh I mean just I'm basically just repeating what we previously went over. make sure that your environment variables are properly

set because as you can see we did not define the API and variable here. So it

will search for it itself. So the name is very important. But if you want to name it differently for whatever reason you can do that and I think that inside you can pass the API key and then you

can call this you know API key if you want to or if it's not managing to find your environment variable for whatever reason.

Let's try this out now. So, I'm going to go ahead um and honestly, I don't know how this will perform. So, I'm going to call this I am Antonio and I am a

developer.

Let's go ahead and try and doing that.

So, in here, as you can see, it immediately finished and you can see the step was called summarizer and you can see the content inside. So the content

is you are an expert summarizer. You

summarize in two words. And then we passed in the role user summarize the following text. I am Antonio and I am a

following text. I am Antonio and I am a developer. And in here I think we can

developer. And in here I think we can already see the output. And there we go.

The output was Antonio developer. And if

you actually look at the finalization step, I think that is exactly what you will find. So output.content is Antonio

will find. So output.content is Antonio developer. Amazing. So, we officially

developer. Amazing. So, we officially created our first AI background job.

So, now just for fun, let's try and change it up just a little bit. How

about we change the system prompt here?

Actually, let's change the name of the agent to code agent. And let's call this code agent and call code agent.run.

And now we're going to say you are an expert Nex.js developer.

And let's go ahead and just say something like you write readable maintainable code.

And let's go ahead and also answer you write simple Nex.js snippets

like button component Nex.js and React snippets. Okay, let's just do that and

snippets. Okay, let's just do that and then write the following snippet like this.

So, this is still called hello world.

That's perfectly fine. We don't have to change anything else. But let's just see what we've achieved now. For example,

I'm going to say uh create a button component.

I'm going to click invoke background job. And you can see this is a bit

job. And you can see this is a bit longer running task. And let's see what it created. I'm not really sure what the

it created. I'm not really sure what the output would be here, but here we have it. Here's a simple and reusable button

it. Here's a simple and reusable button component using Nex.js. And you can see how it actually writes code. import

react const button with props on click children type button class name. And it

returns JSX button on click. It has

class names. It uses tailwind. It added

a class name prop. It has children in the button. export the default of the

the button. export the default of the button. So basically a fully working

button. So basically a fully working button. So we are you can say halfway

button. So we are you can say halfway there, right? We just made AI create a

there, right? We just made AI create a React component. So the next step that

React component. So the next step that we have to learn is how to make it use tools and run this code snippet it just

created inside a sandbox inside of a cloud environment that we can then show to the user as a result. So that's what

our next chapter will be about and I think that in the this chapter we've done what we aimed to do. So let me just check this. We chose our AI provider and

check this. We chose our AI provider and we've set up in justest agent kit and we even tried a very simple AI step. Right?

So basically that's how you're going to write uh agent kit tools and we are then only going to extend it by introducing

tools. One of the tool can be terminal

tools. One of the tool can be terminal usage. Another tool can be create files.

usage. Another tool can be create files.

A third tool will be read files, right?

That's what we're going to do. And then

we're going to explore networks and routers. So we can keep the uh agent in

routers. So we can keep the uh agent in a execution loop. So it consist so it constantly creates new components until

its task is finished. You saw that in the intro video of this tutorial. I had

a lot of uh coding steps. That's by

because it is in a execution loop until it completes its task. So that's what the tools will be used for. We're then

going to have the state history a bunch of things and then finally we're going to have the finalization step where it will save to the database and it will uh

save the URL of the sandbox so we can show that to the user. So in order to advance further and create these tools and things like that, we're going to

have to establish our sandbox because without the sandbox, we can't work right. So that will be our next step for

right. So that will be our next step for this chapter. Uh we did a very good job.

this chapter. Uh we did a very good job.

We created a very simple interface here on the front end and we are now able to call AI background jobs and we are able

to get some AI code right here. So later

on when we actually connect this to a proper network the function will say coding agent right you're going to see it's very very cool amazing uh so now

what we have to do is we have to uh open a new branch and push to GitHub so let's go ahead and open 05 AI jobs branch I'm

going to go here and as you can see six files changed one of them was the ingest database upgrade which is Again, I'm guessing some cache for the ingest

developer server. So, in here, I'm going

developer server. So, in here, I'm going to create a new branch and I'm going to call it 05 AI jobs. I'm going to stage

all changes here. 05 AI jobs. And I'm

going to go ahead and click commit. And

then I'm going to publish the branch.

And if you want to, you can press yes.

And then this code rabbit uh free code rabbit extension will analyze all of these files. Or if you prefer, you can

these files. Or if you prefer, you can go to uh where we are now going to open our pull request. And in here we're

going to have that very same review.

And here we have the code rabbit summary. So we added an input field

summary. So we added an input field allowing users to submit custom prompts for code generation. So you can see how it connected all of those separate entities of ours from the front- end

input to the TRPC invocation of a background job to the actual content of a background job. And we now generate code snippets dynamically using an AI

agent specialized in Nex.js development.

So in here we can see step by step we can see the sequence diagram as always.

You can see how it now features the new agent kit right here.

And in here we have some potential issues. So you can see how it cares

issues. So you can see how it cares about our TRPC value because we are uh lacking any kind of validation. We're

not even requiring a minimum length. So

obviously it is telling us that that's something we should add of course and we will later on. We're going to change our form schema entirely. So you don't have to worry about that. Right now it's just

for demonstration purposes.

In here it's recommending using constants instead of hardcoded strings and that is exactly something we will do. So later on I have prepared very

do. So later on I have prepared very very large system prompts which I have tested on which gave me the best results. So I will share them with you

results. So I will share them with you and then you will paste them in your app and you will be able to use them as constants.

And in here it again suggests some sanit sanitization and uh some other limits on the front end. Uh my apologies in the background job. I thought that this was

background job. I thought that this was the submit function. It is not. We will

take care of that as well.

Uh and yeah, no need to do anything else here because this will not look like this. We are going to modify it quite

this. We are going to modify it quite heavily in the next few chapters when we introduce the actual agent network.

Great. So I'm going to go ahead and merge this pull request. So 05 AI jobs.

I'm not going to delete the branch simply so I have everything here. And

then I'm going to go inside of my IDE here and I'm going to go back inside of my main branch and I'm going to synchronize the changes like this. So

everything is now up to date. I'm going

to select no for uh this trigger of the code rabbit extension this time simply because this is a merge which we just reviewed. Right. I'm going to open the

reviewed. Right. I'm going to open the graph here just for a sanity check to confirm that my last changes were 05 AI jobs and they are great. So that marks

the end of this chapter and in the next chapter we're going to learn how to create uh online sandbox cloud sandboxes which run Nex.js JS applications which

in the following chapters will be something our agents will work on and create new components and run terminal commands into. Amazing job and see you

commands into. Amazing job and see you in the next chapter.

In this chapter we are going to explore E2B sandboxes. This will be the

E2B sandboxes. This will be the environments where our AI will generate files and create a working Nex.js

application. In this chapter, our

application. In this chapter, our specific goal is to create an E2B account, learn how to use their command line interface, and create a Docker file

template for our Next.js project, and then push that template to E2B. And then

we're going to preview that Nex.js application inside a sandbox. So, we're

not going to make AI create any new files and run any terminal commands right now. The goal for this chapter is

right now. The goal for this chapter is to learn more about the sandboxes, how they work, and basically to create a template that we are going to use moving

on to uh create working Nex.js applications. So, we're going to start

applications. So, we're going to start by creating an E2B account. You can use the link in the description or the link you can see on the screen to let them

know you came from this video. As you

can see, uh in short, E2B allows you to run AI generated code securely in your application. It's an opensource runtime

application. It's an opensource runtime for executing AI generated code in secure cloud sandboxes. It is made for

Agentic and AI use cases. And some of the uh uh some of their customers are Perplexity, Hugging Face, Manus, and

even Grock. So these are very very big

even Grock. So these are very very big names in AI here. And my experience with

E2B was nothing short of amazing, right?

So they are built for AI use cases. So

for exact thing that we are building here, it is generative UI. This is

pretty much the thing that we will be building here. But they have an even

building here. But they have an even deeper use case which I highly invite you to explore yourself.

So let's go ahead and create an account and let's go in the dashboard.

After you create an account, you will probably be redirected to your sandboxes which looks like this. And at the moment

you will probably have zero sandboxes running. If you head into templates, you

running. If you head into templates, you will probably have less templates than I do. You might have three. I have five

do. You might have three. I have five templates because well, I explored E2B while I was developing this project. You

can see that I have this code with Antonio Nex.js thing. You don't have this. So, I have this because I tested

this. So, I have this because I tested it. Same thing with this no-name

it. Same thing with this no-name template, right? You probably have

template, right? You probably have desktop code interpreter and base. I

assume that's the things you have. You

probably don't have these two. So, no

worries about that. And I just want to show you uh inside of your budget, you should see your credits. So, you should have $100,

uh, free for your new account. And as

you can see, I tested E2B pretty thoroughly. So, I I really really tested

thoroughly. So, I I really really tested it all day, every day, and I barely spent $9. So it is quite well optimized

spent $9. So it is quite well optimized for tutorial making. So ensure that you have that in your budget. Make sure that

you have the credits. Uh and now let's go ahead inside of the documentation here. And specifically let's go in the

here. And specifically let's go in the CLI installation.

So you can use brew or you can use npm.

I used npm here. And once you do that, you should be able, my apologies for this, you should be able to run E2B, right? And you should see a bunch of

right? And you should see a bunch of options on how you can run it and you can shut down your app, right? So

basically E2B should become available after you install uh either via npm or using brew. So what we have to do next

using brew. So what we have to do next is we have to authenticate, right? So

E2B O login uh should open up. You can see that I am already

open up. You can see that I am already logged in. So uh in here I get that

logged in. So uh in here I get that message. But you should get a not you

message. But you should get a not you probably won't get this message. Instead

you will get redirected to E2B page and from there you're going to have to uh approve the login and then you'll be good to go.

So once you've logged in uh what I would suggest is try uh listing sandboxes or just try listing something just to confirm that you are logged in. So I

have no running sandboxes. So this is my message. I believe that if I wasn't

message. I believe that if I wasn't logged in I would get some kind of error here. Right? So just make sure that at

here. Right? So just make sure that at least you get a message like this which means okay you're logged in but you have no running u you have no running

sandboxes. Great. So now uh what we have

sandboxes. Great. So now uh what we have to do is we have to learn how to add a new template because right now you can

see that inside of our E2B here templates uh you probably have three of them as I said desktop code interpreter and base. But what you need is a NextJS

and base. But what you need is a NextJS template. Now they do have their own

template. Now they do have their own Next.js template which you can use but I don't want you to do that. I want you to learn how to create your own template.

So for this we're going to have to go into the second uh step. So we've

created CLI and we've connected to our account. Now we need to create a Docker

account. Now we need to create a Docker file template. Now I have provided you

file template. Now I have provided you with a GitHub repository with two files that we are going to need for this not because uh I don't want to write this

with you but because we have to be very careful about writing these files right so this is what I suggest we do now as always ensure that you're on your main branch you can synchronize changes just

in case and then go inside of your source source folder here my apologies outside of source folder so completely outside create a new folder called

sandbox templates like this and inside create a new folder which we're going to call nextjs and then in here you are going to create

e2b docker file like this you don't have to install any extensions at least I didn't use any for this now this docker file

has the content that you can find in the public repository sorry that which you can access by using the link in the description or the link you can see on the screen and when you're in here you

can access that file. So just go ahead and copy it entirely and paste it here.

And now we're going to go ahead and uh explain what it does. So the first thing we do is we set the environment. In our

case that's going to be node. After that

we run a terminal command to install kernel. So we updated the system. We get

kernel. So we updated the system. We get

the install method for curl and then well whatever else we need to do here.

I'm not too familiar with configuring docker environments but basically in this part we configure curl. After that

what we do here is we copy another important file a bash script compile page which we currently don't have. So

let's create it. compile_page.sh.

Make sure to not misspell this because we need it right here. And you can find the content in the same uh public repository here. I'm going to

explain what this does as well. But for

now, just make it like this.

After we copy this compile page and we put it in the environment, right, in this docker environment, we run this

command on it to make it executable.

Then we change our directory to next.js app. And then inside of that next.js

app. And then inside of that next.js app, we run a command which you've already seen a couple of times. Create

next app. So basically the same way we started this project, we are now creating a docker which is going to start the project the same way. The

versions I'm using are 15.3.3 with chat 2.6.3.

The reason I'm using these versions is because when I started making this tutorial, uh those were the newest versions. You already know that now that

versions. You already know that now that I'm recording this tutorial, there are newer versions than this. So, if you want to, you can upgrade, but I'm going to stay with these versions for now

simply because I know that they work for me. So, later on when we finish the

me. So, later on when we finish the project, you know, feel free to upgrade this to 7.0 and this to 7.0 zero and this to whatever is the newest version.

But for now, I want to stick with these versions because they worked for me initially. Now let's explain these

initially. Now let's explain these flags. So why do I add d- yes after npx

flags. So why do I add d- yes after npx and why do I add d- yes after create next app? The reason I'm doing that is

next app? The reason I'm doing that is because you have to remember these commands will be running in a container in a dockerized container which means no user will be able to interact with them.

Remember when we started our Nex.js application, we had a bunch of questions. Do you want to use tailwind?

questions. Do you want to use tailwind?

Do you want to use slint? Do you want to use this that so because of that uh I have to uh just agree to all of those things so the terminal doesn't hang

right? So it doesn't block it can keep

right? So it doesn't block it can keep moving forward because if it blocks it will not work. That's why we have to add

both d- yes in front of npx and in front of our command because npx might ask you to upgrade right so we will also agree

to that right whatever you ask me I agree to and same is true for running chaten in it and for adding all shatzen components

and then what we do is the following we move the content of that new folder nextjs app where we just added Nex.js and all of its components and we move

that entire content into home user directory and then we remove the old folder. The reason we do that is simply

folder. The reason we do that is simply because it is easier for AI to understand that the the place where it's located because initially it will be

loaded here. We just tell it wherever

loaded here. We just tell it wherever you are this is where you have the next.js application. You don't have to

next.js application. You don't have to go to any other directory. Trust me,

that is much easier for nextj for an AI to understand because otherwise it will hallucinate things. This way it's just

hallucinate things. This way it's just easier to work with. So you might be wondering why did we then even open a new folder if we're just going to bring

all the stuff back here? Because if you try to initialize this command, the dot basically means in this folder. If you

try to do that inside of home user, it would fail because in order to initialize a next app, it needs to be an empty folder and home user has some hidden files, right? So it's never

really empty. That's why we needed to do

really empty. That's why we needed to do this trick. So that is basically our

this trick. So that is basically our docker file. Now let's explain the

docker file. Now let's explain the compile page. So the compile page is a

compile page. So the compile page is a little trick that we are going to use to ensure that the next.js JS application is running and that the root page is

compiled. So basically in here we have a

compiled. So basically in here we have a function called ping server which uses curl which we install right here and we

are attempting to ping localhost 3000 and we are doing an iteration of 20 uh I'm not sure not too familiar with the bash shell language to tell you if this

is seconds milliseconds I'm not sure but basically it's more than enough time for the server to start. So it gives it 20

attempts, right, to try and get a 200 response. And once it does, it simply uh

response. And once it does, it simply uh uh marks it as done. And then it runs that method ping server and it goes

inside of home user where we just said that we create this project and it runs npx nextdev- turboac.

So the turbo pack is uh really cool here because it speeds up the uh the start of the dev server. So it's actually very useful in this case.

Great. So once you have these two things, the second part is very important and that is that you set up

Docker inside of your project.

So here I am on the Docker landing page.

You can use the link you can see on the screen to let them know you came from this video. Uh, and basically the way I

this video. Uh, and basically the way I set up Docker in my project is by downloading Docker Desktop. I use a MacBook, so I download it for Apple

Silicon. If you use Windows, you choose

Silicon. If you use Windows, you choose your architecture here. Or if you use Linux, uh, well, you probably know what you're doing then. So, after you've done

that, make sure that you have Docker uh, installed. So, in here, let me just try

installed. So, in here, let me just try and open it back here. Uh since if I go inside of uh let me just try and find the

documentation here. Docker desktop.

documentation here. Docker desktop.

Let's go into overview. And in here somewhere we should have setup install.

And in here, there we go.

So in here we have some deeper documentation here. Uh on on Mac and on

documentation here. Uh on on Mac and on Linux it's pretty straightforward, right? If you install it uh with a DMG,

right? If you install it uh with a DMG, it will add it to the terminal uh as well. But if you don't, here's how you

well. But if you don't, here's how you can add it from terminal as well. Also,

if you want to, you can use orb stack.

That's a Docker alternative. All right.

But the reason I kind of don't know how to explain how to install Docker is because I don't know if I'm talking to a person who has a Windows, who has Linux, or who has Mac OS, right? So that's why I'm kind of don't want to give you too

much information and I don't want to tell you something incorrectly but basically try to research yourself you

know uh what you are using right so try and install that and your goal is basically to be able to have docker

available right that's kind of the goal you should not get an error in docker and on windows In here, you can see

pretty similar instructions. Uh I'm not too sure what this means. I don't use Windows. Uh but in here, I think it is

Windows. Uh but in here, I think it is important for you to be able uh to run Docker from the command line as well. Uh

but at minimum, you should be able you you need to install the Docker desktop package and you should be able to start that application at minimum. That's the

minimum thing that I expect, right? So

now I'm going to go ahead and start my Docker application. Actually, the first

Docker application. Actually, the first thing I'm going to do is I'm going to try without Docker started simply so uh we can test if uh if the command fails.

So I want to show you what happens if the command fails first. So let's go ahead inside of SDK reference CLI template and let's do template build. So

in here we have E2B template build command. And the way we're going to do

command. And the way we're going to do this is the following. Go inside of sandbox templates and go inside of next.js

like this.

So I'm going to try and run B2B template build here and I'm going to give this a name and I'm going to call it vibe next.js test. So that will be the first

next.js test. So that will be the first thing I'm going to try. So in here you can see how uh it found the E2B docker

file and it requested build for the sandbox template ID with this name login succeeded and then it attempted to run docker build and then it failed. Right?

So this is the error you will see if your docker is not running. So now I'm going to start docker for me very simply I'm just going to open the application

docker and then in here uh I will have docker desktop available. You can see docker desktop is now running. So uh

let's see if that will be enough for me to run this and try it. So let me see.

E2B template build. Let's see this time.

And there we go. So all I had to do was install Docker Desktop and I need to make sure that I have Docker Desktop running. That's the important thing for

running. That's the important thing for me. So you can actually stop this now

me. So you can actually stop this now because it will take some time to build but it's not going to work correctly. I

just wanted to show you how it looks like when your Docker is not running.

Basically this is the error you will get. So just make sure you have Docker

get. So just make sure you have Docker running. You don't even have to create

running. You don't even have to create any image on Docker or nothing. We are

doing that now. Right? So just make sure you have Docker installed on your system and make sure you open whatever application you installed. It can be Orbstack, it can be Docker Desktop,

right? Uh I'm not sure if you need it

right? Uh I'm not sure if you need it inside of here, right? As I'm I I keep saying that because I don't know what machine you're on. I have it inside of my terminal as well. I'm not sure if

that matters, right? Because you can see that in here it does run the Docker command. So it probably does matter the

command. So it probably does matter the fact that I have it inside of my terminal. So uh make sure that you have

terminal. So uh make sure that you have Docker CLI installed as well. If you

don't, you might even see a different error showing here. Great. But once you get to this part, once you get to the fact that after Docker build is being fired, you start seeing these kinds of

messages, right? Something resolving,

messages, right? Something resolving, something transferring, it means the Docker is working. And then you can cancel it using command C or control C.

Uh and then we're going to run a proper command with a proper uh start command.

So now in here, here's what we have to do this. Basically uh we have to add

do this. Basically uh we have to add E2B template build-ame

vibe next.js js test two dash cmd and you have to add compiled_page.sh.

compiled_page.sh.

So now uh let's go ahead and run this again. So I'm just going to wait to see

again. So I'm just going to wait to see this succeed. There we go. And now I'm

this succeed. There we go. And now I'm going to pause until this part completes because it is a little bit long. you

know it needs to upgra it needs to upload the entire Nex.js project there with all the packages and everything and it needs to you know install curl and

all of that. So I will pause until some interesting things show here so you can compare with your process as well.

So here's an interesting part. Uh it is running npx create next app and you can see my result typescript types node types react tailwind CSS. So it is

obviously successfully installing this with no questions asked. That's why

these parts were so important because if we didn't include this uh it would be blocked by waiting for the user input,

right? That's why that is important.

right? That's why that is important.

It was also successful in copying compile page.sh. So we did this

compile page.sh. So we did this correctly as well.

Now I am running chaten in it. I'm

selecting neutral color. I'm forcing

everything to install here. Right? So

I'm just making sure that I'm not adding any prompts. And there we go. It's

any prompts. And there we go. It's

succeeding. It uh checked the reg registry. It found app globals CSS. And

registry. It found app globals CSS. And

now it's installing all dependencies. So

so far so good.

Now it is adding all components. You can

see again very successful. It found

global CSS. It is upgrading it and it is installing all of those radics packages which we use for our component. So it's

we are basically doing the same setup we did for our project but this time inside of a docker container.

Great. And now it has finished that part. And now it is moving the entire

part. And now it is moving the entire content from Nex.js app into home user and removing the old folder. Now this is this command actually failed for me a

lot during the initial development of this project and the reason was actually because I was missing this d- yes commands. So if this part is successful

commands. So if this part is successful for you as it is to me right now you have pretty much succeeded in doing this. So if this part fails for you it

this. So if this part fails for you it is almost certainly because uh you forgot to add d- yes to some of this.

Basically, it's hanging on some command and it didn't create the Nex.js app. Uh,

great. So, now what's happening, I think, you know, I'm I'm new to Docker as well. I've briefly experienced it

as well. I've briefly experienced it before, but now what's happening is obviously pushing all of that data to the Docker container on the E2B app.

And just to confirm, in case you're wondering, this part does take a while.

After that part has completed, it is triggering the build. And here you can track the progress of the build itself.

So this is more specifically a build of the docker image. We're not building the Nex.js app, right? Those are two different things. Our Nex.js uh app is

different things. Our Nex.js uh app is just a development instance. We are

building the Docker image here.

All right. So mine uh failed. So I will uh it looks like it failed because

it is missing the compiled_page.sh

command not found. And that's definitely because of this. I should have uh done this differently. I have to find a way

this differently. I have to find a way to execute this in a different way.

Something like this. I will test it out so I know for sure and then I will tell you the correct command.

So I think that the start command should be this forward slashcompilepage.sh.

And let me just show you something. Uh

regardless if this part fails or not. I

mean depending on how I edit this video, perhaps I told you not to even run this command in advance, right? But you can see that I have something called e2b.tl

generated in here. You can see my start command is compiled_page.sh.

sh. So I have two ways of changing this now. I can either change it here or I

now. I can either change it here or I can change it here. But you can see how it remembered all of these things. But

for now basically this should be the working command. So I'm going to pause

working command. So I'm going to pause and try it again.

And this time it worked. You can see that right here it is waiting for the template to be ready and then it is waiting for server to start a couple of times and then finally we can see

next.js 15.3 and that signals that it is done template is ready pausing sandbox template. So be very very careful here.

template. So be very very careful here.

During my initial development of this project, I was able to get to template is ready and pausing sandbox template and uploading template. But I actually

never saw this part. Let me just go up here. It is very important that you can

here. It is very important that you can see this part in your terminal because this means that it's actually working.

If you can't see this everywhere, even with this saying it's ready, it will actually not be working. So just make sure you did this correctly. And also on

the second run, this runs much much faster because half of the I mean the entire project is already uploaded, right? So you won't have to wait that

right? So you won't have to wait that long. But basically, this was the final

long. But basically, this was the final command E2B template build. And the cool thing now is that you actually get instructions on how to use this. You can

see that you can use the name or you can use the ID. So I don't know what this actually tells us. Is the name unique or not? I think it might be unique per

not? I think it might be unique per team. uh which then again you know uh

team. uh which then again you know uh depends if you're going to publish this or not. Uh what does publishing mean?

or not. Uh what does publishing mean?

Well, I'm going to explain in a second.

But basically now you can go find your E2B.l file and in here you can see all

E2B.l file and in here you can see all the information team ID start command docker file template name template ID and from now on if you ever want to do

any changes to here you can just do template build. That's it. And you can

template build. That's it. And you can just modify whatever you want from here.

want to change the name just change the name here and run template build. So

right that the in the tommo file that's basically where the configuration is now. Uh great. So before we move on now

now. Uh great. So before we move on now let's go ahead inside of E2B inside of your project go inside of templates and refresh. And now if you have done this

refresh. And now if you have done this correctly you should see vibe next.js test dash2 right here. And you can see

that this says private. So just to make things easier for now, I want you to do the following. I want you to copy the ID

the following. I want you to copy the ID of vibe next.js test 2 and I want you to go and go to E2B template publish here.

And let's go ahead and do the following.

So actually we need the team ID. My

apologies. So just go ahead and find I think it's inside of your team here.

Find the team ID and copy it. And inside

of here. So you are inside of this uh Nex.js template. So you can just do E2B

Nex.js template. So you can just do E2B template publish D and then paste the ID of the team and this will make the template public to everyone outside of your team. The reason you're doing this

your team. The reason you're doing this is simply because uh it will be easier to connect to it. At least that was my experience. Later on we can easily

experience. Later on we can easily unpublish it or you can delete it. The

reason this is kind of uh you know you should be wary of this. You should not share your uh template ID with anyone because I can use it and I can uh use

your credits, right? I can spend your budget here. So, be careful. Make sure

budget here. So, be careful. Make sure

that only you know about this and later I will make sure to find a way to unpublish this so that only you and your team can use this. Great. So now what should happen is inside of your

templates when you hit refresh and find that vibe nextjs test 2 it should say public under visibility and that

basically means you did it. This is now working. So now what we have to do is

working. So now what we have to do is the last part of this chapter. We did

this we did this and we even did this.

It's time to actually start this sandbox. Right. Let's see if that

sandbox. Right. Let's see if that next.js JS app is actually working or not. And lucky for us, this is quite

not. And lucky for us, this is quite easy to do. So, what we're going to do now is we're going to go inside of source, inside of ingest, and inside of

functions here. Uh, and I think I

functions here. Uh, and I think I completely forgot, but yeah, we have to uh we have to install

uh E2B. I think I forgot that entirely.

uh E2B. I think I forgot that entirely.

So let's add E2B code interpreter to our project. And make sure you go in the

project. And make sure you go in the root of your app like this. And I'm

going to show you the version of this package now.

So let's go inside of package.json

quickly. So it is 1.5.1.

That's my version.

Now let's go inside of our functions here and let's import sandbox from E2B code interpreter package which we just installed. And

then what we're going to do is the following. Let's go ahead and before we

following. Let's go ahead and before we create an agent, let's do const sandbox ID await step.r run. And we can extract the step from here. We removed it in the

previous chapter I believe. So step.run

run get sandbox ID. That will be the first step we're going to run.

And in here, we're going to attempt to get the sandbox by doing await sandbox.create.

And inside of here, you will simply pass the template ID. So in your case, this will be vibe next.js test 2. Now, you

will probably have a different name for this because I think if they allow public templates, there probably has to be some kind of originality here, I

guess. So, once you've created that, go

guess. So, once you've created that, go ahead and simply do await sandbox set timeout. Uh, actually, you don't have to

timeout. Uh, actually, you don't have to change the timeout. I'm going to explain what the timeout is in a second, but for now, let's just do sandbox.box ID. So

this will be the step which we are actually going to preserve here and keep throughout uh this entire project because we will always need the sandbox ID. And now that we have the sandbox ID,

ID. And now that we have the sandbox ID, uh what we have to do is we have to create a sandbox URL. So after the agent finishes, let's pretend that this agent

now actually connected to this sandbox and then added a bunch of files. Then

what we would do is we would generate the sandbox URL using await step.r run

get sandbox URL.

In here we would get the sandbox again using await. And then in here uh we need

using await. And then in here uh we need to create a util called get sandbox. So

let me just go ahead inside of the ingest and create utils. DS and export asynchronous function get sandbox

which accepts sandbox ID which is a type of string.

Let's get the sandbox using await sandbox from E2B code interpreter.connect

interpreter.connect sandbox id like this and then in here just return sandbox. So basically I'm

writing a function to make this uh reusable. You're going to see why later.

reusable. You're going to see why later.

And also let's import this like so. So

the structure now that you have get sandbox you can do await get sandbox here and pass in uh the sandbox id and

then return sandbox.get

host 3000. So, uh, basically what this is, it

3000. So, uh, basically what this is, it creates the host under the port 3000.

Why 3000? Well, we know why. Because of

our uh, sandbox template, right inside of here, we know it's running on port 3000. That's the only port it can run

3000. That's the only port it can run on. It's a Nex.js app. And this is

on. It's a Nex.js app. And this is actually only the host. So, let's do con host to be that. and then return open back

https and then post like this and then in here

sandbox URL like this. Are you ready to try this out now? Make sure you have imported the get sandbox right. So let's

go ahead and do it. npm rundev in one npx ingest cli dev in the other. It is

running perfect. Let's go to localhost 3000. Make sure you have your ines

3000. Make sure you have your ines development server here. And I'm just going to do uh create a button component again. Why not?

again. Why not?

And let's go ahead and see what's going on here. Oh, so there we go. It is

on here. Oh, so there we go. It is

failing. So something uh you can see the error. Invalid API key. So we can cancel

error. Invalid API key. So we can cancel this run because I forgot to add the API key. Uh I'm constantly not reading this

key. Uh I'm constantly not reading this the way I should. So let me just go ahead uh and add click on API key here.

Uh and we can find it at the dashboard.

So let's go back inside of our dashboard here. Uh you can see I already have

here. Uh you can see I already have some. So I'm going to click create new

some. So I'm going to click create new key here. And you can find it here under

key here. And you can find it here under API keys. So create new key vibe dev.

API keys. So create new key vibe dev.

And I'm going to click create key here.

I'm going to copy that key. And let's go inside of environment here. E2B and

let's add it.

So it's going to be E2B API key like that. And if you add it like this,

like that. And if you add it like this, I don't think you have to explicitly add it anywhere because this is what it will be looking for. So, uh I'm not sure if I

can find the documentation for this specifically, but I think that now it will be working.

Let's try again. Invoke another

background job. And let's see, do we get any errors or do we successfully get a running template? We're going to see get

running template? We're going to see get sandbox ID has succeeded. And now it's doing the coding agent thing. It's

generating some code and now it's grabbing the sandbox URL. And in the finalization here, you should see the output of an AI who created a component

and you should see an actual URL. Let's

try and visit this URL. Fingers crossed.

And we should now see a Nex.js application. And we are a completely

application. And we are a completely empty Nex.js application hosted in E2B started from our background job. You are

halfway there. you can already uh kind of tell what the next steps are. We are

going to dive deeper into E2B sandbox API now and we're going to learn how to run terminal commands, how to create files, how to read files, basically how

to translate what it's now doing here very simply as an output into actually modifying the code in this sandbox.

Amazing, amazing job. I know this chapter wasn't too easy. a lot of new things, Docker, Docker files, all of those things. But if you've come this

those things. But if you've come this far, you did it. Amazing, amazing job.

So, let's go ahead and mark this as complete. And yes, this uh will stop

complete. And yes, this uh will stop working after some time and that's completely normal. So, after about 5

completely normal. So, after about 5 minutes, I think by default, uh this link will show an error, right? So,

that's normal. Don't worry, we can change the timeout later.

So now let's go ahead uh and let's merge this. So I'm going to go ahead

this. So I'm going to go ahead and I'm going to open my source control here. I'm going to go ahead and create a

here. I'm going to go ahead and create a new branch 06 E2B sandbox. Let me just confirm that's my chapter name. So sandboxes.

I'm going to go ahead and stage all of my changes including the Docker files of course.

And I'm going to do 06 E2B sandboxes here. And I'm going to commit.

sandboxes here. And I'm going to commit.

And I'm going to publish my branch.

Again, there is a free code rabbit extension you can use if you want to review your changes and learn about all the things you can do differently in your code. So if you want to, you can

your code. So if you want to, you can click review all changes. Or if you're like me and you like to see uh the summary and all other things, you can

use their pull request function. So now

I'm opening my new pull request here which I will merge. So let's go ahead and review it.

And here we have the code summary. So

what I'm very impressed with is how well Code Rabbit understands what we just did. So we introduced a sandbox

did. So we introduced a sandbox environment for Nex.js allowing users to create and access isolated Nex.js instances. We added functionality to

instances. We added functionality to generate and return a unique URL for accessing sandbox next.js app. So quite

impressive that it understood the entire context of this pull request with nothing more than files. Very very very nice. So in here we even have an entire

nice. So in here we even have an entire sequence diagram demonstrating how that happens. So once the user triggers a

happens. So once the user triggers a hello world background job, we go ahead and create a new sandbox with our template name. After that, we return the

template name. After that, we return the sandbox ID and we have it for that session of that background job. We then

use our new package E2B code interpreter to return a sandbox instance and from it we extract the sandbox URL. Amazing. And

you can see how it even has some related pull requests. It detected where we

pull requests. It detected where we initially added hello world. So if you were working in a team, if this was some big operation, you can now link pull requests automatically with this

extension. Amazing. So in here, it has

extension. Amazing. So in here, it has some potential issues regarding our TOML file, but that doesn't matter because this file is not generated by us in the first place. So it is okay for it to be

first place. So it is okay for it to be like this. In here, it is suggesting

like this. In here, it is suggesting improving the compile page shell script, which could be completely valid. I'm

don't know bash. I'm I'm also mixing bash and shell. I'm not even sure what is the correct name. So for all I know, this could be completely correct.

But since I want to be very careful about this part, you know, I'm not going to change it because I know it works, right? So good enough for me at the

right? So good enough for me at the moment. Uh great. Uh so in here it

moment. Uh great. Uh so in here it recommends adding some try catch all of the things which we will add later but in a different syntax because we will implement the agent network and the

agent retrying and it's going to work in a different way. It's going to be using tools. So overall pretty good summary

tools. So overall pretty good summary pretty good review. Let's go ahead and merge this now. As always I'm not going to delete my branch simply so I have

access to all of my progress here.

Amazing amazing job. So, as always, go back to your IDE here and change to main branch and then click on synchronize changes. And as a final sanity check,

changes. And as a final sanity check, you can go ahead inside of source control inside of graph and you will see 06 E2B sandboxes have just been merged.

Amazing, amazing job. I believe that marks the end of this chapter and see you in the next chapter where we are going to learn how to make our AI use tools such as terminal to install some

things and actually modify these sandboxes. Amazing amazing job.

sandboxes. Amazing amazing job.

In this chapter, we're going to add tools to our AI.

The tools we are going to add are going to be the terminal tool which will allow the agent to run commands, create or update files tool, which as its name

says will allow the agent to create or update any files within its environment.

And finally, we're going to have read files, which will be able to read files.

We are then going to add a completely new prompt for our agent. And we are then going to implement the agent network and the routers. So we are going

to heavily rely on agent kit by ingest.

You can use the link you can see on the screen or the link in the description to let them know you came from this video.

So in here what we are going to do is we are going to uh add some tools. Tools

are used to extend the functionality of agents for structured output or for performing tasks. So for example, they

performing tasks. So for example, they are used to call code enabling models to interact with systems like your own

database or external APIs like B2B. So

let's go ahead and let's create a very simple tool which will be allow our agent to interact with the terminal.

So the first thing we're going to do is ensure we're on the main branch and we are going to synchronize our changes and just confirm your last merge was E2B sandboxes.

After that let's go inside of source inest functions.

Now in here we're going to do the following. After you create the coding

following. After you create the coding agent uh go ahead and do the following.

Right after your model, add tools.

Open this array and let's create tool which you can import from ages uh a inest agent kit.

In here, let's go ahead and give this tool a name. It will be called terminal.

Add a description. Use the terminal to run commands and add the parameters which the AI will

pass to this tool. It will be a very simple command which is a type of string. And after you have added the

string. And after you have added the parameters and the handler method, extract the command from the first

argument and extract the step from the second argument.

In here you are going to return the step execution await step question markr run. The

reason we need to use question mark is because step can be undefined. So let's

go ahead and run a step called terminal.

It's going to be an asynchronous method inside of this step. And the first thing we're going to do is we are going to create an object called buffers.

Inside of here, we are going to set std out to be an empty string and std error to be an empty string as well. Now,

let's open a try and catch method.

Instead of the try, let's grab our uh let's get our sandbox here using await get sandbox

and pass in the sandbox ID.

Then let's grab the result of await sandboxcomands.

And pass in the command. So we are reusing our get sandbox method from utils. I also imported zod. So make sure

utils. I also imported zod. So make sure you add this as well.

Uh and we are basically doing the same thing we're doing right here. We are

establishing the connection with our sandbox using this simple util here. So

we don't have to repeat this every time.

And now what we are doing is we are running a command. So you can learn more about this uh by going inside of the E2B documentation and simply learning about

the command. Let me see if I can find

the command. Let me see if I can find that here. Here we have the commands. So

that here. Here we have the commands. So

this is how you basically run commands in your environment. Perfect. So now

let's go ahead uh and define some more settings here. So after we run the

settings here. So after we run the command, let's define on std out grab the data which is a type of string

and for buffers std out simply add that data and the same thing on std

error. So data is a type of string

error. So data is a type of string and in here simply add buffers std error plus equals data. So we are handling all

of the results of running the terminal commands in this object. So we're going to know if the terminal command succeeds or if it fails. That's why it's important to keep track of the result.

And then let's go ahead and let's return result std out. And then in the catch method, let's extract the error here.

First, let's do console. Simply so we see this in the terminal. Open back. And

you're going to say command failed render the error. And then you can use forward slashn to break into new line so it's more readable. std out will be

buffers std out.

And then go ahead and break line again.

std error will be buffers std error. So that's going to be the console

error. So that's going to be the console log. And then you're going to return the

log. And then you're going to return the exact same thing.

This will basically tell the agent what went wrong with additional information about std out and std error. So yes,

just make sure that you don't accidentally type this incorrectly as it is important for AI to understand what's

going on. And that is our first tool.

going on. And that is our first tool.

Our agent now has the ability to use the terminal. It uses sandbox API for this

terminal. It uses sandbox API for this and it will keep the results of this uh terminal execution. So either a success

terminal execution. So either a success message or an error with detailed information about what happened because commands can fail and thanks to ingest

they will automatically retry but now with the context of what happened. So if

you're building nex.js with me for some time you know that sometimes when we install a package which doesn't support next uh react 19 it fails because we

need to add d-leacy pure depths. So if

that happens here it will first fail and then the AI will read the message and say oh okay I need to add d- legacy peer

demps and ingest will automatically retry the terminal step with that new information and then it will succeed. So

that's how powerful injest background jobs are and now they're amazing inest uh agent kit.

So now that we have finished uh the terminal tool, let's go ahead and create a new tool.

This one will be called create or update files.

The description will be and let me just see. I think I'm doing something. Yes,

see. I think I'm doing something. Yes,

I'm doing something incorrect here.

I think it needs to be here and add a comma here. There we go. So yes, so just

comma here. There we go. So yes, so just make sure you're doing it at the end of this create tool bracket right here. The

description will be create or update files in the sandbox.

Let's go ahead and let's add the parameters. It's going to be an object

parameters. It's going to be an object of items and it will accept the files.

files are going to be an array of objects and inside we're going to have path which is a type of string and content

which is a type of string as well and it's going to be the only thing we will accept. So now we can build our

will accept. So now we can build our handler method inside of this handler method. Let's go

ahead and let's extract a few things.

The first things will be the files and the second thing will be step and network.

And now in here let's go ahead and let's get the new files by doing await step question mark run create or update files.

Open the asynchronous method here. And

let's go ahead and open a try and catch block.

Let me just add catch here. There we go.

Instead of try, let's create updated files by first looking at network.state.data.files

network.state.data.files

or an empty array.

Then let's get the sandbox here. Await

get sandbox and pass in the sandbox ID.

And now for const file of files await sandbox files write file.path

file.content

updated files file.path is now file.content.

file.content.

So basically when the agent gets access to create or update files tool, it will give us a structured input of files it

just created. So imagine this first part

just created. So imagine this first part which we already have. We've already

seen it create a a few files I think.

There we go. So here's a simple button component and then it just returns jsx.

So now it will do exactly that but it will recognize the input this accepts uh accepts. So what it's going to do is

accepts. So what it's going to do is it's going to return back an object.

Let me try like this. It's going to return an object like this and it will have app psx and then in here it will be you know

paragraph app page. This is how it's going to look like. And then the same thing for any new components. Right?

This is what it's doing now. And then

we're going to iterate over each of that and we're going to write that to the sandbox file explorer using files.right

which is the similar API like sandbox commands.

So that's how we know which file to write where. And then we just keep a

write where. And then we just keep a track of updated files internally in our network state simply so we can later tell the user which files were changed

because technically we could just ask it in the prompt hey you know when you finish also tell me which file you changed but you can't really rely on

that because AI has a token limit it can only talk for so much but for this part you can rely on this right so each file it actually writes in the sandbox box in

the file explorer. We are going to save it. We are going to keep track of it.

it. We are going to keep track of it.

And the reason we are choosing the format of an object rather than an array is because this way it is very easy to to overwrite any files if they change by invoking this step again because this

step can be called 50 times for all we know. That's why we are choosing an

know. That's why we are choosing an object rather than an array. So we can just simply overwrite any path if it changes.

And then let's go ahead and do return updated files. And then in the error

updated files. And then in the error here, let's return error and simply render the

error. Perfect. And then uh outside of

error. Perfect. And then uh outside of this if type of new files is equal to object

network state data files will be new files.

So why are we waiting for this to be an object? So the new files are basically

object? So the new files are basically the return of this step. This step can either be an object or it can be a string. So we are basically waiting for

string. So we are basically waiting for it to be an object and only then do we store it into our internal network state. Perfect. So that's another tool

state. Perfect. So that's another tool finished. Now let's go ahead and let's

finished. Now let's go ahead and let's create another tool with a name read files. Now in here let's add a

files. Now in here let's add a description read files from the sandbox.

parameters Z dot object files Z dot array and Z dot string inside.

Now let's go ahead and let's add a handler method again an asynchronous method and in here we are going to return await

step and we just need to extract the step from here. So let's do that. First

extract the files from here and then extract the step from the second step.

Be mindful if you forget to do this. You

won't get an error simply because we have a step defined elsewhere.

We have it here. So be careful.

You always have to extract the step from the tool because it holds different context.

So step.run

read files asynchronous.

And in here open a try and catch block.

In the try block, connect to the sandbox. Await get sandbox sandbox ID.

sandbox. Await get sandbox sandbox ID.

In here, open the contents array. And

for const file of files, push to that contents array. So const

individual content is await sandbox files read file. So we read the file and

then we simply push the path to be file and the content next to it. So in here it doesn't really matter how we store

this data because this is not for us.

This is for the AI if they if it needs to read data. So yeah let's just fix this path. So if we in our prompt

this path. So if we in our prompt instruct the the the AI agent to read before they do something, they're going to use this. Why would they need to read

something? Well, so they don't

something? Well, so they don't hallucinate, right? So we tell it if you

hallucinate, right? So we tell it if you attempt to use a chat component, make sure to read inside of the components folder. So then it's going to use this

folder. So then it's going to use this tool to attempt to read a file. And if

it doesn't exist, it's going to say, "Oh, okay. then I need to create it or I

"Oh, okay. then I need to create it or I need to use something else. It's

basically not going to hallucinate or assume a tool exists. That's why this step is quite useful and also why we don't really care about the format too much because AI can read from various

formats here. And if this fails, we

formats here. And if this fails, we return an error like this.

And that is the last tool that we need.

What we have to do next is we have to update our prompt and tell it that it can use these tools.

I have prepared a prompt for you in my public GitHub of assets. You can use the link you can see on the screen or the link in the description to access it.

Now be mindful of something. I am not a prompt engineer. I have no idea if this

prompt engineer. I have no idea if this is a good prompt or a bad prompt. I have

generated it using AI itself. So I

assume it's okay. I have found it to work quite well for my use case. But you

are free to modify it however you want it. I I started out very simple like it

it. I I started out very simple like it was almost just like one or two lines.

And then I had to addit more and more and more instructions until it understood things very well. And I found this to be kind of a very very good uh

at least starting point if nothing more.

Right? But the cool thing about this project is your app can get twice as good just by adding a new model. So if

OpenAI or Enthropic release a new model, all you have to do is use that new model and your app is suddenly twice as good.

So that's the cool part about working with AI, right? So copy this prompt from my assets. Let's go inside of source and

my assets. Let's go inside of source and let's create prompt.ts and let's paste it here. So I'm going to slightly go

it here. So I'm going to slightly go over it just so you understand what I'm doing here. So you're a senior engineer

doing here. So you're a senior engineer working in a sandboxed Nex.js 15.3 environment. Why that environment? Well,

environment. Why that environment? Well,

because that's what we define here.

So I'm telling it exactly where it is running. And then I tell it some tools.

running. And then I tell it some tools.

You can write files with create or update files. You can execute commands

update files. You can execute commands via terminal. I also tell it to use d-

via terminal. I also tell it to use d- yes simply because uh it's not a human.

So it uh basically we we must not get it in a position where the terminal waits for human input. Then I tell it you can read files with read files. So those are the first three instructions that I give

it. I then tell it you know some general

it. I then tell it you know some general rules. Don't install package JSON or log

rules. Don't install package JSON or log files directly. You can install packages

files directly. You can install packages but don't modify the these directly. I

tell it the main file is in the app folder page.tsx. I tell it that all

folder page.tsx. I tell it that all chats components are pre-installed and imported here. And then I tell it some

imported here. And then I tell it some general rules like you must never add use client to layout. This must always remain a server component. Uh I tell it

to never create any CSS or SCSS files.

styling must be done strictly with Tailwind and basically some rules like that. So you can of course tweak this if

that. So you can of course tweak this if you think you you can modify it a little bit. Of course maybe I will modify it

bit. Of course maybe I will modify it during this tutorial but basically it's just a bunch of rules that I have added after I experienced it fail. So after I saw that it does something incorrectly I

added a new rule for that. And this is important the final output. So after

it's fully completed, I instruct it to return this type of format task summary and inside uh a description of what it

just did. So it needs to return with

just did. So it needs to return with this and you're going to see why in a second. This is super important and this

second. This is super important and this is why I'm very strict about it because this is the only way to terminate the task. If it omits or alters this

task. If it omits or alters this section, the task will be considered incomplete and it will continue unnecessarily.

So now that we have our new prompt, let's go inside of inest functions.ts and let's go ahead and change our code agent here and let's extract the open AI

here a little bit. And now what we are going to do is we're going to change this model to be GPT4.1.

Default parameters here will be temperature 0.1. Now if you're using uh

temperature 0.1. Now if you're using uh something that is not open AI, this might not exist and that's completely okay. You don't have to modify this.

okay. You don't have to modify this.

What temperature means is randomness. So

the larger the number here, the more random is something going to be. And

when it comes to generative UI, I kind of want it to be deterministic and reliable rather than completely random.

But I give it a little little little chance of randomness. So if you're using grock or anthropic, you don't have this probably. So that's that's that's

probably. So that's that's that's completely okay, right? You can even do without this in open AI. And the reason I changed the model is because 4.1 is

much much much better at generating UI than 40. And for Antropic, the better

than 40. And for Antropic, the better model is 3.5 set.

For Grock or Gemini, I simply don't know. And as I said with Gemini, I have

know. And as I said with Gemini, I have problems running these tools. You can

try, but for me, I just got errors. So

now, let's go ahead and modify the system here to use our prompt constant from at prompt, which we just added. So just make sure it's this one.

added. So just make sure it's this one.

Perfect. Now, let's go ahead and let's add a slight description here. An expert

coding agent.

There we go. So, I'm just going to bring back this temperature 0.1. If you're

using Open AI, you can add this. So,

both you and I will get similar results.

I hope. Great. Now that we have these tools, we are still not ready to try them out just yet because what we have to do now is we have to add life cycle

here. So after the array of tools ends,

here. So after the array of tools ends, so make sure that you find where the tools end, right? Go to the bottom here.

We're going to add life cycle. And in

here, we're going to get on response.

And from here, we're going to get the result and network.

And now what we're going to do here is we are going to check if the last message that is in this cycle because this is a cycle right this is not going

to be linear. It's not going to go uh using the terminal then create or update files and then read the files and we're done. No, it has access to all three

done. No, it has access to all three tools equally and it will create its own plan. It might use them 50 times in a

plan. It might use them 50 times in a row. That's why in the prompt we tell it

row. That's why in the prompt we tell it when you are finished when you know that you're done go ahead and return the task summary. So now in the life cycle we are

summary. So now in the life cycle we are going to uh extract the last message from the assistant and we are going to check if that message includes the task

summary. If it does, we will break the

summary. If it does, we will break the cycle and then we can go ahead and do these steps where it actually shows to

the user what it generated.

So in order to implement that part, we have to go inside of utils here in the inest where we have get sandbox. Let's

export function here last assistant text message content.

It will accept a result which is a type of agent result from inest agent kit.

Let's go ahead and do const last assistant text message index which will be resultput find the last index.

Extract the message and we are going to find the index of a message whose role is assistant. So we know okay this is

is assistant. So we know okay this is what assistant said last. Then let's

extract the actual message content from that index. So result dot output last

that index. So result dot output last message index as a type of either

text message again from ing inest agent kit or unde so it can even not exist. Right? And

then let's return message question mark.content.

mark.content.

And we're going to add a turnary below it. If type of message.content

it. If type of message.content

is string, we're going to simply render message.content.

message.content.

Otherwise, we will do message.content

domap. Get the individual inner content and simply return that inner contents text. And then we're going to join it

text. And then we're going to join it all in a single string. And then finish the outer turnary by adding or undefined

here. Like that.

here. Like that.

Perfect. I'm going to pause the screen just so you can double check your code.

Now let's go ahead here and let's use that inside of here. So I'm going to do const last assistant

text. just this last assistant. Uh let's

text. just this last assistant. Uh let's

do message text and in here we're going to call our last assistant text message content which we can import from the utils and

pass in the result like this.

And let me just see if I did something incorrectly here. So uh life cycle on

incorrectly here. So uh life cycle on response uh I have to end this here. There we go.

Perfect. And then we're going to check if last assistant message text and if we have network if last assistant message text

doinccludes task summary add to the network state in the summary

key last assistant message text like this and then outside of this outer if clause return result

and let me just see what I did incorrectly here. So I think I have to

incorrectly here. So I think I have to end this maybe like that. Let me just try and fix this quickly.

So let's see. I think I don't need this part. Do I need this?

part. Do I need this?

Okay, so that was the extra. Perfect.

So basically what we're doing here is we're extracting last assistant message text using our util last assistant text message content which simply finds the index of the last message whose role was

assistant and then if it is type of text message and if it's string it just returns that content. But there is obviously a special type of message where it can be an array of strings. So

in that case we simply join that into a single string using a very simple method. And then once we parse that

method. And then once we parse that message, if we have network available and if we have last assistant message

text and if that message includes task summary, which is our rule here, right, to return that if it's done, we store

the network state data summary last assistant message text and we return the result. And what we will be able to do

result. And what we will be able to do now is the following. Go below this entire coding agent and create a network.

The network will be create network from inest agent kit like this. And in

here go ahead and add the following. The name

coding agent network agents will be our coding agent or whatever we called it. it we called it code agent. So let's just add it here.

code agent. So let's just add it here.

Code agent max iteration

will be 15. Now this will basically this is a number that will uh limit how many loops the agent can do. So what is

a loop? As I explained previously, the

a loop? As I explained previously, the agent can pretty much do things indefinitely if it wants to, right? We

need to find a way to tell it to stop.

So, we are doing that currently using the task summary, right? But there has to be some kind of limit. We cannot

really let it go forever. So, I'm going to say if you reach 15 iterations, you're doing something wrong. you should

have already been done and this is too much and I have to stop you because you will use all of my open AI credits.

That's what max iterations is.

Now let's add a router here which is an asynchronous method and it can extract network.

Let's add the summary here to be network data summary. And if we have the

data summary. And if we have the summary, we are going to break this network. Otherwise, we are going to

network. Otherwise, we are going to return uh the agent and the agent will

be code agent like this. There we go. So

this is how we break the loop. If we

detect this summary in the network state, we break the network. Otherwise,

we return code agent, right? So the code agent will call itself many times until finally we detect there's a summary. If

we detect a summary, we say great, you are done. Perfect. So now I have a bunch

are done. Perfect. So now I have a bunch of these errors that I have to fix. So

I'm going to go ahead and just see did I maybe remove an important bracket or something? Uh because something seems to

something? Uh because something seems to be wrong here. I'm going to start by trying to reload my window just to see if that maybe fixes it. Looks like it did not fix it. So, I'm going to go

ahead and see uh exactly what I did wrong.

Okay, I think I found it. It's all the way uh up here. Uh somehow this happened. I'm not sure how. So, create

happened. I'm not sure how. So, create

network.

There we go.

Perfect. And now the only error in our app is this unused network variable.

Everything else seems to be working. No

errors seem to be flying around.

Perfect. So once I have this network, what I can now do is I can run this network instead of running the actual code agent. So I will remove this now

code agent. So I will remove this now and I will do con result that comes from the entire network.

And I will simply pass in event data and I believe it was value that we pass. So

let's go ahead and do that. And then in here what we are able to do is this part can stay the same. And in the result part uh we can actually do the

following. We can show each file that

following. We can show each file that was changed. So next to sandbox URL

was changed. So next to sandbox URL let's do it like this.

So URL is sandbox URL.

The title will be for now just a fragment. files will be result state

fragment. files will be result state data files and summary will be result state data

summary. So right now these are a type

summary. So right now these are a type of any depending on the version you use maybe they will even become errors.

Don't worry we will add the types later.

So if we've done this correctly, we should have working code now. Especially

after you change this and if you add this prompt, I believe this should be working. Now keep in mind uh it is hard

working. Now keep in mind uh it is hard to be reliable and deterministic with AI agents. I might get one result and you

agents. I might get one result and you might get a completely different result and that will actually quadruple if you're using a different AI model than

me.

So again, the my biggest advice is use the same model I'm using, use the same open AI, you even put the same temperature. This will make it much more

temperature. This will make it much more easier for you uh to have the same result as me. Let's try it out now. So,

uh, I have my app running here and I'm going to go inside of here and I will create, um, create a calculator

app. Let's try this and let's invoke a

app. Let's try this and let's invoke a background job. Let's see. Maybe it

background job. Let's see. Maybe it

fails immediately. Maybe it works. We're

going to see. Uh, let me refresh the runs. Are they working? Are they not

runs. Are they working? Are they not working?

Not sure. Okay, it seems it seems like there is some kind of error happening here. So, let me just

try and debug this.

All right. So, what I did is I've shut down the npx inest cli. I have shut down npm rundev and I restarted both of them.

So, do that. Shut them down and restart them and let's see what's going on. So,

I managed to get the sandbox ID. That's

a good start. It means we successfully started the sandbox. And now we are running the code agent. And you can see here we are running it with all the tools available. The tools to use the

tools available. The tools to use the terminal, the tools to create or update files, the tool to read files. And now it will

use those tools. So there we go. It used

create or update files. Let's see what it created. It created calculator.tsx

it created. It created calculator.tsx

tsx and then it imported that calculator from that file. At least that's what it says it did. And it returned back the code sandbox URL. Let's check it out.

I'm going to click here. And if it worked, we should now be seeing a simple calculator app. Fingers crossed. And

calculator app. Fingers crossed. And

looks like something went wrong. So what

happened here? What happened is that it forgot to use use state. But here's the cool part for you. Maybe this doesn't

didn't even happen. I don't know, right?

Uh it can behave randomly. So, how do we fix this thing? Well, we can fix it in two ways. We can fix it by uh making the

two ways. We can fix it by uh making the prompt even more strict, right? We can

find the places where I add, for example, in here I added you must never add use client to layout tsx line 13.

Maybe it's confused because of this. It

reads this part and then it gets confused.

Let's remove that part.

Let's search for use client again. File

safety rules again. Never add use client to app layout tsx. Maybe it gets confused by this. So I'm going to remove this where it says never add use client

because it seems like u it is avoiding to add use client in that case.

I have another instance of use client here where it says if building a form or or interactive element include proper state handling and add use client to the top. Perfect. That's a good example.

top. Perfect. That's a good example.

Only add use client at the top of files that was react hooks or browser APIs.

Never add it to layout tsx. So I will remove this part as well simply because I feel like it is leading it away from using use client properly.

So let's see if our next iteration will be better. So I will add it again.

be better. So I will add it again.

Create a calculator app. Uh and if it doesn't work then we can instruct it in this prompt further. Right? And as I said for you maybe it worked first try.

I don't know. That's kind of the part about building this type of apps. Um you

have to simply rely on luck sometimes right? Sometimes the agent will perform

right? Sometimes the agent will perform very well, sometimes it will perform very bad. And the better this models

very bad. And the better this models get, the better your results will actually be. And as I said, uh your

actually be. And as I said, uh your prompts will also get better with time.

Right? So let's go ahead and see if this was any better. Let's see if it added use client this time. And there we go. I

have a working calculator app generated by AI. Can you believe that AI has

by AI. Can you believe that AI has generated this? Now, as I said, I have

generated this? Now, as I said, I have no idea what kind of result you are going to get, right? I don't think you will get the same result as me. Your

might have some colors. Your might not work again. If it doesn't work again,

work again. If it doesn't work again, you know, you can try and go inside of here and then explicitly tell it, you know, uh, be mindful of use client where

it needs to be added, right? You can

tell it that and then it definitely won't make that mistake or, you know, uh, read through my prompt and see if there's something you don't

like here. Or maybe paste my entire

like here. Or maybe paste my entire prompt inside of chat GPT and tell it to improve it somehow.

So for me, I removed those couple of lines for use client.

Perfect. Uh let's try build a landing page.

So you can now pretty much you know you're pretty much let's say finished when it comes to backend side. You can

now only improve these tools and improve the prompts. So what we're going to do

the prompts. So what we're going to do next is we're going to implement saving this to the database and we're going to implement creating a summary of what it just created. So we can save that to the

just created. So we can save that to the database and kind of return a message back to the user. And if you want to uh you can instruct it to use some package

like use drag and drop use framer and then you will see it use the terminal tool. So let's see if it managed to

tool. So let's see if it managed to create a landing page. There we go. And

may I say a pretty good landing page, right? Very impressive. Perhaps you

right? Very impressive. Perhaps you

should try with the landing page example because it doesn't use any use client or things like that. I'm very very impressed by this is better than I expected. So let's try telling it to use

expected. So let's try telling it to use framework this time. Build a landing page. Use motion package.

page. Use motion package.

Let's try this.

So in here we can see that now it is using terminal and we can see the result added three packages right. So let's see if it actually used uh that or something

else. I think you can also click on the

else. I think you can also click on the code agent right before it uses the terminal and click on the output and in here you can see npm install framer motion. So that's what it run. I'm not

motion. So that's what it run. I'm not

sure if that's the newest version of framer. Maybe this won't even work. I

framer. Maybe this won't even work. I

don't know but let's click on get sandbox URL. Let's click here

sandbox URL. Let's click here and let's see maybe it will be broken.

Yeah, looks like this doesn't work. It

should use motion package not framer motion. So, as I said, uh it's not

motion. So, as I said, uh it's not perfect, right? You can break it every

perfect, right? You can break it every now and then, but you can also improve it just as easily, right? As I said, Claude Sonnet 3.5 is by far the most

reliable coding agent because it just it is up to date with everything. It just

knows everything, right? But you will hit limits very very soon. So, your best option for now is to create this kind of

app, right? using open AI and simply

app, right? using open AI and simply improve the prompt as much as you can.

So I did this myself and I am not a prompt engineer. So you can definitely

prompt engineer. So you can definitely create this better than me. Your

starting point uh should be this. You're

a senior software engineer and the most the other important part is this. Give

it a very important ending. So this

should be your ending. Everything in

between after before final output you can change. So I wrote all of this with

can change. So I wrote all of this with the help of AI and I basically added more things as I saw uh they as I saw some things fail. So for example,

sometimes it attempted to run dev itself or build. So I told it you must never do

or build. So I told it you must never do that, right? It's always working. Uh so

that, right? It's always working. Uh so

yeah, you can learn how to prompt a little bit better and you will get better results or you can simply use a newer model. So, how about we try build

newer model. So, how about we try build a conbon board use react beautiful

drag and drop. Let's try that. Maybe we

will have some uh better results with this package.

So, this is the result of the query to build a conbon board. As you can see, the first terminal command actually failed and we can actually see the error

here. So, let's go ahead and scroll down

here. So, let's go ahead and scroll down here. Error error unable to resolve the

here. Error error unable to resolve the dependency and it probably told it that it needs to use uh legacy peer depths.

You can see this retry this command with d-force or legacy peer depths. And then

what happened is it simply retried that and you can see then it worked. So

that's the power of ingest and that's the power of returning the result of the terminal tool. Right? So we tell it the

terminal tool. Right? So we tell it the command failed and we tell it why it failed. So that way it knows how to

failed. So that way it knows how to retry. And my get sandbox URL uh was

retry. And my get sandbox URL uh was this what seems to be a working conbon board.

test.

Amazing. It seems to have some issues.

It's missing a prop set here, but honestly, other than that, pretty damn good. Look at this.

good. Look at this.

Amazing, right? It even highlights where it's going to land. Very, very cool. Uh,

great. So, I think that marks the end of this chapter. uh till we finish this

this chapter. uh till we finish this project, we will add some methods to improve the failing builds, right? We

will allow the user to tell the AI like, hey, you forgot to add use client so it understands what happened previously and then it can just easily fix the issue.

That's at least what we are going to attempt to do. So even if something fails, uh we will allow the user to instruct the AI and tell it, hey, it failed. Can you please fix it? Because

failed. Can you please fix it? Because

you know, I saw lovable fail. I saw

replet fail. I saw v 0ero fail. All of

these apps fail, right? They are just AI. It's a it's a language model after

AI. It's a it's a language model after all, right? So, it can definitely fail.

all, right? So, it can definitely fail.

But I think it is super impressive given the fact that we built it so soon and so fast. Amazing, amazing job. Let me mark

fast. Amazing, amazing job. Let me mark all of these things as complete here.

And now, let's go ahead and branch out.

So, 07 agent tools. I'm going to go ahead and create a new branch 07 agent tools.

I'm going to stage all of my changes.

07 agent tools. I will commit and I will publish my branch. As always, you have a completely free code rabbit extension you can install inside of Visual Studio

Code if you wanted to review your files.

And now let's go ahead and let's open a pull request so we can merge our changes and so we can review them here with a summary.

And here we have the code rabbit summary. So we have enhanced agent

summary. So we have enhanced agent capabilities with multi-tool multi-agent network for sandbox interactions including terminal commands, file operations and summary extraction. We

also introduced a comprehensive system prompt outlining coding standards and environment constraints for improved code generation and consistency.

Perfect. So that's exactly what we did.

And in here we even have a sequence diagram of how it happens. So once the background job is triggered, we can see that now the coding agent can use the

terminal, create or update files or read files as needed and then the tools return results using std out using files contents or anything else. And depending

on that the code agent is either calling another tool or finally it returns with the last message which includes the task

summary tag signaling that it is over and then we can return the sandbox URL.

So in here it actually uh recommends not doing a double turnary instead ending early here. So that's quite a good

early here. So that's quite a good suggestion.

We could possibly do that. Then below

here, it fixed a typo to agent. That is

definitely a mistake. Great. Uh, and in here is something quite interesting. So,

what I do here is if I fail, I simply return an error. So, I practically never

store anything if it fails. But in here it recommends actually doing partial saving right. So if there is at least

saving right. So if there is at least some files which were successfully created save them but still throw an error. So quite a good suggestion but in

error. So quite a good suggestion but in my experience if it fails on one file it will fail entirely because uh this doesn't mean that it wrote incorrect

code. If it throws an error here, it

code. If it throws an error here, it means it lost access to the file system.

That's why I'm not exactly worried about this. I will pretty much always expect

this. I will pretty much always expect it to be able to write all files it needs. But very good suggestion here to

needs. But very good suggestion here to handle partial success. I will look into that. Let's go ahead and let's merge

that. Let's go ahead and let's merge this pull request here. I'm not going to delete the branch as always. So I have access to it right here. And now that we

are here, let's go ahead and go back inside of our main and let's go ahead and synchronize our changes. And that

should officially mark the end of this chapter. Just a sanity check here. There

chapter. Just a sanity check here. There

we go. We just merged 07. Amazing,

amazing job. We are now ready to start building our UI. See you in the next chapter.

In this chapter, we're going to implement the messages entity. This will

include creating the actual message Prisma schema, the fragment Prisma schema, and then we're going to modify our current TRPC procedures and our

background jobs to use those new schemas and save user prompts and AI responses in their appropriate models. So, let's

start by creating a simple message schema. In order to do that, we have to

schema. In order to do that, we have to go ahead and visit our schema file.

Before you do that, as always, confirm that you're on your main branch. And if

you are unsure if you have any unsynchronized changes, you can always click this and confirm. And just make sure that chapter 7 is your last merged

change here. Great. Now, let's go ahead

change here. Great. Now, let's go ahead inside of Prisma and schema.prisma.

If you have a folder with migrations here, you can delete it because we're going to remove pretty much everything inside of here. Right? So, we're going to create a whole new schema now. So,

let's go ahead and create a model message inside create an ID which will have a type of which will be a type of

string is going to be an ID with the default value of uyu ID. After that,

let's go ahead and create a content which will be a type of string. Let's go

ahead and add a role which will be a type of enum. So, let's create an enum message type.

And let's give it uh my apologies not message type message role which can be a type of user or assistant

and then you can go ahead and use that right here. So simply assign the role to

right here. So simply assign the role to be message ro just like that. And now

we're going to do what I started to do which is the message type.

So the message type will either be a type of result or a type of error.

And let's go ahead and give this a type of message type. So in case the AI response fails, we are going to treat it as an error. meaning that the AI will

simply return uh I wasn't able to do this generation for whatever reason please retry and now let's add the usual created ad

field which is a type of date time and the default value of now and let's add updated ad which is a date time as well and it has a special decorator updated

ad which is a very cool decorator because what it does is it will automatically update this build when we update the message model. And now let's

go ahead and let's create a fragment model. So the fragment model

model. So the fragment model will also have an ID of string and the default value of UU ID and it will have a relation to the message. So let's add

a message ID to be a type of string and it needs to be unique.

Now let's add a message here to be a type of message. Give it a relation decorator targeting the fields message ID which we defined above

referencing the ID field in the message model. And let's add on delete here to

model. And let's add on delete here to be cascade. So if this message gets

be cascade. So if this message gets deleted, the fragment gets deleted as well. And now we just have to fix this

well. And now we just have to fix this error by adding a proper relation here in the message. So the message does not have to have a fragment. If the user is

sending a message, there will be no fragment. Only for the AI response will

fragment. Only for the AI response will there be a fragment. That's why we're going to create a fragment field and we're going to make it a type of fragment, but it's going to be optional

like this. And then let's go back inside

like this. And then let's go back inside of the fragment model and let's create a sandbox URL to be a type of string, the

title to be a type of string, and files to be a type of JSON. So this is quite cool. Uh it's very nice that possess

cool. Uh it's very nice that possess allows this and it's perfect for our use case because files is not exactly something that in my opinion makes sense

to create a whole new model for because it's just a simple mapping of the file path and the content and it can be pretty much infinite in size. Well,

obviously not infinite but you know what I mean. So I think this is a very good

I mean. So I think this is a very good use case of using JSON in posgress and then we can just copy the created ad and the updated ad from the model above just

like that. Now in here you should have

like that. Now in here you should have no errors and again make sure that you're using the Prisma extension simply so you have the syntax highlighting and it will tell you in

advance if you've done anything incorrectly here. So what we have to do

incorrectly here. So what we have to do now is we have to push this. So let's go ahead and shut down our app. Make sure

you have shut down your ingest server as well. And I will now run npx prisma

well. And I will now run npx prisma migrate dev. And let's just wait a

migrate dev. And let's just wait a second for this to connect to our database.

So I have gotten an error. In your case, you might not get an error. But I think this is because yes, it detected some drift. Your database schema is not in

drift. Your database schema is not in sync with your migration history. That's

because I told you to manually delete the migration folder. This is not a problem. We are working with development

problem. We are working with development data here. So let's use npx prisma

data here. So let's use npx prisma migrate reset first and let's just reset the entire thing. So let me just confirm this. Um and even if this doesn't work,

this. Um and even if this doesn't work, you can always just create a new postgress database and then just go instead of environment and just use a new database URL. Right? That's like the

ultimate brute force you can do. So

after I've done my migrate reset, I will try migrate dev again.

And this time with no problems, I'm going to call this message-fragment.

And there we go. Just like that, we have created new schema here. And now you can go ahead and run the npx Prisma studio.

And in here, you should see the fragment and the message as the models, meaning it successfully created that. Perfect.

So now let's go ahead and let's actually use these things. So what I want to do now is I want to go inside of source and I want to create a new folder called modules.

So I like to have a module-based structure in my application. So instead

of having my procedures written here randomly, I will have them in their own module. So I like to separate modules

module. So I like to separate modules either by large chunks of my application like homepage, landing page, pricing or

by entity models that I have in my database. So for example, let's go ahead

database. So for example, let's go ahead and let's create messages in here. So

inside of here, I will keep everything message related. So for example, one of

message related. So for example, one of those things would be all the things that go on the server uh regarding messages, specifically all our procedures.

So now that we are inside of here, we're going to import uh initc router or create tRPC router from trpc init. And

let's export const messages router here to be create trpc router. And then

inside of here, let me just quickly check inside of my TRPC routers app. I

seem to have this existing one called invoke. So now what we're going to do is

invoke. So now what we're going to do is we're going to create a create procedure. So this will be accessed

procedure. So this will be accessed through as message.create.

This is how you will call this. That's

why it's called create and not create message because it would be redundant.

Message dot message create or create message, right? Makes no sense. So let's

message, right? Makes no sense. So let's

add a base procedure which will of course be a protected procedure later on in the tutorial. For now it's perfectly fine to be a base procedure. Let's go

ahead and define an input here. And I'm

going to set this to be uh the value. It

can be the value. It can be the prompt.

I think value is good enough. And let me just import Z from zod.

Let's go ahead and set it to be a string. And let's give it a message is

string. And let's give it a message is required error.

Great. And then let's go ahead and let's chain mutation here is going to be asynchronous.

Let's dstructure the input from here like this. And then inside of here, what

like this. And then inside of here, what we're going to do is we're going to create a new message by using await

Prisma from lib database and then go ahead dot message dotcreate and pass in the data inside. And let's

add the content to be input value here like this. And let me just see what else

like this. And let me just see what else do I have to add inside because I already forgot how my schema looks like.

So I have to add a role and I have to add a type. So my role here will be user and my type here will be result. Right?

There's no loading. This is an instant message created by the user.

Perfect. So I think I actually don't even need to uh put that in any type of constant. I think this works just fine.

constant. I think this works just fine.

And what I'm doing after this is I'm actually invoking my background job. So

let me go inside of the routers here and let me just copy this part inside of procedures and let's go right here. So let me import inest from the

here. So let me import inest from the inest client. Let me show you my imports

inest client. Let me show you my imports a bit simply so you're on the same page.

There we go.

And obviously we're going to have to change this as well. It makes no sense to be called test. Uh and uh this is what we should actually do. We should

keep this as created message or new message and then simply return created message simply. So our API response has

message simply. So our API response has some kind of well response for the user back. Perfect. Now that we have the

back. Perfect. Now that we have the basic message router created with some basic validation here, let's go ahead

inside of TRPC routers and let's remove everything inside of here. And then in here add messages messages router. You can import the

messages router. You can import the ingest. You can you can remove zone. You

ingest. You can you can remove zone. You

can remove inest and you can remove the base procedure just like this.

And this is how we're going to add all other uh module related things inside.

So later when we add fragments it will be fragments router and we will control all procedures inside of its own module.

Right, like this. Great. So now

obviously we need to fix some things in our page I believe. So let's go inside of source app page.

And in here this is now create message.

This will be TRPC dot messages.create.

And the on success is the same. In here

we can just say on success message created.

And then let's go ahead and let's use create message is pending and create message.mmutate

message.mmutate just like that. So right now uh this should still work exactly the same right let's go ahead and just quickly try it

out uh just to make sure we didn't accidentally break something. So npm

rundev in one npx inest cli latest dev in the other one. Let's go and we can install the new one. If it appears it's okay. And let's go ahead and open local

okay. And let's go ahead and open local host 3000 here. And I'm going to do create a landing page simply because this is the simplest thing that most

likely won't go wrong. There we go.

Looks like it is created. So I'm going to click invoke a background job here.

And looks like message was created. I'm

going to go inside of my inest developer server here. And I'm going to wait for

server here. And I'm going to wait for this to complete.

And here we have it. It is complete. and

quite a nice result. I'm always

impressed by its landing pages. It seems

to have uh gotten that it seems to have gotten very good at creating landing pages. Uh great. So, looks like

pages. Uh great. So, looks like everything is still working. And now

what we have to do is while we are storing the messages from the user, we are not storing the messages from the AI. So in order to keep track of that,

AI. So in order to keep track of that, how about we extract the messages here by using use query from tanstack query.

So just make sure you add this import here. Pass in tRPC do messages. And I

here. Pass in tRPC do messages. And I

just remembered we didn't create any. So

let's simply go inside of the messages router which is inside of your modules here. And simply create uh let's call

here. And simply create uh let's call this get many base procedure.

The input doesn't really matter for now.

Let's just do a query here. Again, it's

going to be an asynchronous method.

And in here, what we're going to do is get the messages to be await Prisma message find many. And how about we do

order by and let me just see I have to use updated at or created at let's use ascending and return the messages.

like this and just like that we have our get many procedure. So now we can go back here and add it. There we go. Get

many. This will be query options here.

And then inside of here, let's go ahead and do it below the button.

JSON.stringify

messages null 2. So now you can see that I just created this create a landing page with the ro user. So if I go ahead

and do create a red landing page and invoke this background job and refresh this page, you can see that now I have create a landing page and after that I have a create a red landing page. So let

me go inside of my procedures and change the updated ad to be descending and refresh and then the newer message appears at the top. And if you want you

can wait for the result but uh you know it's it's not that important right now but it definitely created a red landing

page. Great. So now again I'm expecting

page. Great. So now again I'm expecting that besides having these steps to get sandbox ID create our update and then finalize. I also needed to save this

finalize. I also needed to save this entire thing to the database so that we can access it from the UI and not from the inest developer server. So let's go

ahead back inside of ingest functions here and then we're going to create a whole new step here at

the bottom. So after we get our sandbox

the bottom. So after we get our sandbox URL, we have to go ahead and actually save this to the database. So let's do

await step.r run save result

await step.r run save result asynchronous method like this. And in

here, let's return await Prisma dossage.

Whoops. We have to import Prisma from lib database. So just make sure you add

lib database. So just make sure you add this import.

And we're basically going to save the content. So prisma dossage.create.

content. So prisma dossage.create.

The data will be the following. Content

is going to be result state data summary and role will be assistant. and type

here will be result like this. And let's

also extend it a bit further by also creating the fragment relation. Let's

pass in the sandbox URL here. Uh sandbox

URL doesn't exist. Did I do something incorrectly in my schema or is it just the syntax that I didn't finish? It

definitely does exist here. So perhaps I just have to do result actually sandbox URL like this.

Oh my apologies. This is not how you do it. Create and then sandbox URL. Sandbox

it. Create and then sandbox URL. Sandbox

URL. And let's go ahead and add the the title of the fragment to be just fragment and files can be result state data files.

There we go.

So now let's go ahead and try and do this again. So use a simple prompt

this again. So use a simple prompt again. Build a blue landing page. So

again. Build a blue landing page. So

basically something simple and then wait for this to finish. And after it finishes, you should now see another step happening here which is to save the

result in Prisma. It should create a assistant message and it should also create a fragment with the sandbox URL

and all the files that it created.

There we go. So we have the save result step. And now if I go back here, there

step. And now if I go back here, there we go. You can see I have a new message

we go. You can see I have a new message here at the top. The content includes the task summary, created a fully responsive production quality blue

themed landing page in app page tsx. The

layout includes a navbar, hero section, favorites, pricing, contact form, and footer. Uh, and we don't really have

footer. Uh, and we don't really have access to the sandbox URL here. That's

because what we have to do if we want to see that is go inside of the message router and we have to add include fragment true. And after you do that,

fragment true. And after you do that, you will see the entire fragment content. So you will see the entire

content. So you will see the entire source code actually. And you will see the sandbox URL. So if you try adding that here, we now have the blue landing

page. And that is basically what we now

page. And that is basically what we now have to do. So uh let's remove the fragment for now. We can easily add it later because I'm not sure if we need it

and it's taking up a lot of space. Uh

perfect. So this is what I actually wanted to do for this chapter. I wanted

us to add the messages router and I think that we can do one more thing while we are here and that is the following. We can go inside of ingest

following. We can go inside of ingest here instead of functions and let's just change this right. Let's stop calling it hello world. Let's go ahead and call

hello world. Let's go ahead and call this uh code agent. The ID will be code agent. And let's also change the event

agent. And let's also change the event to be code agent run like this. And now just make sure that

like this. And now just make sure that you go back inside of your modules messages server procedures here and when you invoke it make sure to change this

like that or any other place where you do this make sure to change it. For me

it's only one place code agent run. Uh

okay. And now we have to also go inside of app API ingest route and we have to replace this with code agent.

There we go. Go ahead and refresh.

Uh if it's still stuck, you can always shut this down. And I would recommend shutting both of them down.

And let's go ahead and refresh again.

Build a green landing page.

message created. And I recommend waiting it out just to confirm it works since we just changed this to be a code agent function.

There we go. Seems to work quite well.

And I think that is it for this chapter.

Oh, this one's nice.

uh we've basically created the message model, the fragment model which basically puts it puts us in a position where we can start creating proper UI

around this because uh by having the fragment and by having the message we can create the file explorer we can create the I frame where we render the

URL and we can create the message containers on this side and while we are here it is important to do uh one more

thing go inside of your functions.TTS in

the ingest here and after you do uh the result from the network run define an is error constant and it will be an error

if we don't have result data summary result data.state state my apologies

result data.state state my apologies state data summary or if object do keys result

state data files or an alternative empty array.length

array.length is equal to zero. So if any of those two are missing it means something went wrong. So inside of here when we save

wrong. So inside of here when we save the result what we're going to do is we're going to check if is error we're going to return

content.

Whoops my apologies. We're going to uh return await Prisma message create data content

something went wrong.

Please try again.

like this. Let's give it a role of assistant and let's give it a type of error like this. There we go. So, we do

an early return if we detect it is an error. So, we don't create the fragment

error. So, we don't create the fragment if we don't have the information to create it. And the one thing I

create it. And the one thing I completely forgot about is the types here. So, right now files is a type of

here. So, right now files is a type of any, summary is a type of any, right?

And while this seems to not create any problems for us, I want to show you that there is a way so that you can properly type your entire network state because I think that is important and it will make

your project more maintainable. So let's

go ahead above the uh code agent here and let's create an interface agent state and let's go ahead and do the following. Let's make a summary a string

following. Let's make a summary a string and let's create files which can be mapped as a record string string. But I

don't like this simply because there is a way to make it uh closer to what we expect and it's basically opening an object and then defining path as the key

and simply the content as a string. I

think this more closely resembles what we expect rather than record string string.

Now that we have the agent state, uh we just have to find all the places to use it. So starting with uh oh yes, I really

it. So starting with uh oh yes, I really don't like this. We should not name our function and our agent the same. So how

about we rename one of them? Let's call

this uh code agent function like this. and then go back inside of

like this. and then go back inside of your uh source app API inest route code agent function code agent function like

this way safer like that okay now let's go ahead and use the agent state instead of the code agent here we can open uh pointy brackets and pass it inside so

that's step one then the next place we can use it is in the tool create or update files so In here we have step and network and you can see that files here

are undefined even though we added it to the agent state. That's because what we have to do here is we have to define this step as a type of tool from inest agent kit. So just make sure that you

agent kit. So just make sure that you import the type tool from inest agent kit. I think you can specify type like

kit. I think you can specify type like this.

Let's go back here. So it's going to be a type of tool dot options and pass in agent state inside. And then when you hover over files you will see that it

has the correct state. So that's the second place and the third place is in the network here. So open this up agent state

like this and then data dos summary is a type of string now and you will see that you now have autocomplete. And if you type something else, you should get an error now. Right? So

when you clearly define your state, it is much stricter and you will not be making any mistakes now. Uh perfect. So

I think that this is it for this chapter then. Uh and let me just check uh how

then. Uh and let me just check uh how does this look like? Summary. So this

looks like it doesn't need anything because life cycle seems to infer properly from create agent agent state here. Right? So if I change this, I'm

here. Right? So if I change this, I'm getting an error. Perfect.

Great. So I think that this could be it for this chapter. So I'm going to stop here. Let me just fix this fix this

here. Let me just fix this fix this description coding agent like this. And of course, yeah, if you

like this. And of course, yeah, if you want to, you can change the name of this. I told you you can always go

this. I told you you can always go inside of your let me find the folder sandbox templates toml file and you can change the name

here and then simply run inside of this folder E2B template build.

Great. So now that we have this let's go ahead and open a pull request.

If you want to, you can also, you know, try another one just to confirm it works because we changed again the name of our function. But at this point, I think you

function. But at this point, I think you know how to fix it. But let's just try build a yellow landing page just for sanity check so I don't end the

chapter and things are broken and seems to work just fine. Let's go

ahead and see the yellow landing page.

Perfect. So let's go ahead and open a pull request. So this chapter is 08

pull request. So this chapter is 08 messages. Uh we just created the message

messages. Uh we just created the message schema fragment schema. We're saving the user prompt and we are saving the user response. Perfect. So I'm going to go

response. Perfect. So I'm going to go ahead and I'm going to create a new branch 08 messages.

I'm going to stage all of my changes.

I'm going to add a commit message and I'm going to commit and publish the branch.

If you want to, there is a free Code Rabbit extension which can help you review all of your files here. Now,

let's go ahead and go inside of our GitHub and let's go ahead and open a pull request and let's review with the summary and the diagram here.

And here we have a summary. So, let's

quickly go over it. So, we end this chapter. We introduced a new messages

chapter. We introduced a new messages system allowing users to create and view messages with associated metadata and fragments. Messages now display

fragments. Messages now display additional details including message type and role. And we did some refactors such as we streamlined the backend

procedures and routing for the message management and we removed the legacy user and post data structures. Perfect.

So in here we have the diagram but nothing much has been changed from last time except this time we have additional step before we invoke the code agent run

which is that we save the user message in the database and we have one more step in the background job where we save the uh message to the database to the

Prisma here. Great. And in here we have

Prisma here. Great. And in here we have uh some comments but all of these things will be changed. The on error will be added here later. This will basically

not be in this component at all. So

that's the only reason why I keep you know not fixing this comments. Uh not

because they're wrong. They're

completely right. But it's not the component they are going to be in anyway. This is just for demonstration.

anyway. This is just for demonstration.

Right? We are now going to start and build the proper UI in here. Uh I'm

pretty sure this is not needed simply because uh inest events have their own try catch methods. So let's go ahead now uh and go through the rest of these. So

in here it recommends pagionation.

That's something we can look into later.

But yes, it's very easy to add pagionation with Prisma. As you can see, they have take, they have skip, and that's pretty much all you need here. Uh

in here, it recommends limiting the length of the message, and that is definitely a good thing. Yeah, we don't want any user to be able to spam our app with a huge number of tokens. So we will

have to limit this to some reasonable number. This is a very good suggestion

number. This is a very good suggestion here. Let's go ahead and merge our pull

here. Let's go ahead and merge our pull request. As always, I'm not going to

request. As always, I'm not going to delete my branch. Instead, what I'm going to do is now I'm going to go back to my main branch here and I'm going to click on synchronize changes.

And once that is finished, I can go inside of my source graph and confirm messages are the last merged

chapter. Amazing. Amazing job. and see

chapter. Amazing. Amazing job. and see

you in the next chapter.

In this chapter, we're going to add the projects entity to our application. So,

this chapter will be quite similar to the previous one where we introduced the message model. So, in this chapter,

message model. So, in this chapter, we're going to add the project schema.

We're going to add message relations to that project. And then we're going to

that project. And then we're going to create a new project on user prompt. And

the last thing we have to do is preserve project ID in background jobs so we know where to store that AI result.

Basically, each message needs to belong to a project so we can keep track of all of our uh AI generations. So let's go

ahead and start by adding a new Prisma schema. As always, confirm you are on

schema. As always, confirm you are on your main branch. And if you haven't, synchronize your changes. You should

have 08 messages as your last merge. So

I'm going to go inside of Prisma. I have

some migrations here because we added them last time. And now let's go above the message here. And actually let's go above message type and above message ro.

And let's add a model project. The ID

will be the same as the message. So you

can add it here. The name will be a string. And then we're just going to

string. And then we're just going to have created at and updated at. So we

can add this and then down here add messages which will be a type of message like this. And now we have to create an

like this. And now we have to create an equal relation in the message model. So

let's go inside of the message model here and let's go ahead and add project

ID to be a type of string and below that project to be a type of project. Give it

a relation decorator with fields project ID references ID and on delete cascade.

So exactly the same as the message relation in the fragment right we are aiming for project ID field referencing the ID field in the project and if the

project gets deleted the message gets deleted as well and then the fragment gets deleted as well. Perfect. So now

that we have this we have to push that to our database. So I recommend shutting down both of your uh servers here. And

let's first do npx prisma migrate reset simply so we remove everything from our database because we have invalid data at

the moment. And once this is deleted,

the moment. And once this is deleted, let's go ahead and do npx prisma migrate dev. And once it connects to the

dev. And once it connects to the database, let's go ahead and call this migration project.

There we go. So I'm going to call this projects like this. And that should apply the migration. Perfect. Now let's

go ahead and start this server. And

let's start the ingest server here.

There we go. So now what we're going to do is the following. We're going to go inside of source inside of modules and

let's copy the messages and paste it here. And let's rename it to projects.

here. And let's rename it to projects.

Let's go inside of server procedures.

Make sure you are inside of projects here. We're going to change this from

here. We're going to change this from messages router to projects router like this. And then we're going to modify uh

this. And then we're going to modify uh how this works as well. So for the get many change this to be projects

and then in here await prisma.pro

find many. So that's the get many procedure for the project's router.

for the create here uh the value will also be uh the message right and actually I'm not even yeah so we are

going to create a project by entering a prompt right so we are not going to create a new project and then give the project a name instead we're going to

have a big landing page like this and we will simply say hey enter something like create a Netflix clone and then we're going to click create and this will

create both the message and the project at the same time. So in the create we actually only have the value right the prompt. So that's going to be this. So

prompt. So that's going to be this. So

we're going to do the following const created project await prisma project create and then for the data uh we have to give

a project a name. So for this we're going to add a generator package to our project.

So let's go ahead and let's quickly do npm install random words slugs. Random

word slugs. You can of course use a billion other uh generators, but this is the one I found that looks the most like uh all the other apps I can find. So

this is the version 0.1.7 in case you're interested. And let's go ahead and use

interested. And let's go ahead and use it now. So let me just add it here.

it now. So let me just add it here.

Generate slug from random words slug.

And then in here uh in here the name will be generate slug and pass in two words. And let's go ahead and open the

words. And let's go ahead and open the settings and pass in the format to be ke like this. So that will be it for the

like this. So that will be it for the name. And now we have to immediately

name. And now we have to immediately create the message. So we can do that either uh separately like this or we can just pass in the message messages here

and then open the create inside and you can just copy this exactly like this and then you can remove this and then we start the ingest here and

besides sending the value we will also send the project ID to be created project ID and then in here you will

have created project as the return and that's it. That is our create method for

that's it. That is our create method for the project's router. So in here it would be a good idea uh to limit the length as our code rabbit suggested

previously.

So let me add maximum here and let's add I don't know 10,000 maybe that could be the good upper limit.

Message is too long. This is not actually the message.

This is prompt, right? Or value since this is called value. So yeah, it's either going to be required or if it's longer than 10,000 characters, we're

going to say, okay, that's too long. Uh

you can of course modify this later to however you like. Perfect. So you can of courseh also play around uh with this, right? It even has some more options

right? It even has some more options which you can do but I found this to be sufficient and also in our Prisma schema the project name is not unique so it

doesn't matter if there are conflicts with this right great so now that we have this we also have to modify

our messages procedures here because right now uh they are not exactly working. So let's go inside of the

working. So let's go inside of the create base procedure here. And for the value, well, we can just copy this just

so we're on the same page here. So

either min or max. And then let's also add project ID here, which will be a type of string with a minimum value of

one and a message project ID is required like this. And then in here when we

like this. And then in here when we create a new message we will also assign project ID to be input project ID. So

each message will be stored in an individual uh agent in an individual project. Right? And now what we have to

project. Right? And now what we have to do is also modify the ingest send to also accept project ID from input project ID

like this. There we go.

like this. There we go.

And now what we have to do is we have to modify our ingest functions to accept the project ID.

So let's go all the way down here to when we actually save the result. And you can see we have an error here. That's because this

message is missing the project ID. So

project ID will be input. My apologies.

It is event. Let me just find it.

Uh how do I do this? Just a second.

Event data value. So this will be event data project ID like this and do the same thing here.

So basically you have to make sure that anytime a message is created you add project ID. So you can highlight this

project ID. So you can highlight this part and use command shift F to search it through your entire project. And

basically every place that you find this, it should include project ID. So

just be extra careful in the functions of the ingest here so you don't forget to uh so you don't accidentally misspell this, right? Because there are no strict

this, right? Because there are no strict typings here. We can improve this later

typings here. We can improve this later on, but for now just make sure you didn't misspell project ID when you extract it from event data.

Perfect. And now let's also do inest dot send simply so we see that we are sending project ID in all places that we need. Great. So in here we are

need. Great. So in here we are extracting it from the created project but in here it is from input project ID.

Perfect. So now what we have to do is we have to go inside of source app folder page.tsx

page.tsx and we have to modify this. So this will no longer be creating messages and we no longer have to query messages. We only

did that before because we were interested in seeing them. So we can remove this and instead we can do create project and this will be TRPC

uh projects which doesn't exist. The

reason it doesn't exist is because we forgot to add it. So inside of TRPC folder routers app add projects projects router and you can import it from

modules projects server procedures.

Basically this thing we just created.

And now that we have that we have a proper working projects create we can remove uh on success and instead we can add on error here

and you can do toast error error dot message like that.

And now that we have the create project let's go ahead uh and let's do create project is pending and create project domutate. And

this will be submit like this. And let's go ahead and just

like this. And let's go ahead and just modify this slightly by adding height screen with screen flex item center and

justify center. And inside of here, let's do

center. And inside of here, let's do this.

Let's give this a class name. Maximum

width 7 XL MX auto flex item center.

Let's do flex call and gap Y for an items and justify center.

And now when I refresh this, there we go. It looks like a centered little

go. It looks like a centered little prompt. We can maybe expand this. Let's

prompt. We can maybe expand this. Let's

see. maximum width.

Okay. Uh screen

let's just keep it at 7 XL like this.

And when you write test now and click submit uh it should say well nothing nothing for the success message. But now

what should happen is the following. It

should create well I have no idea what it's going to create now because I just typed test. So let's actually see. Okay,

typed test. So let's actually see. Okay,

so the it returns the error. Something

went wrong. Please try again. Right?

Even though it generated something in the sandbox, I have no idea what that is. I think not a single file was

is. I think not a single file was modified yet. So it's just an empty

modified yet. So it's just an empty Nex.js page. But if you look at your

Nex.js page. But if you look at your Prisma Studio now, and if you actually start it, so let's do npx Prisma Studio.

You should now have a project. And

inside of that project, you should have There we go. I have a name uninterested plastic. So a new project was generated

plastic. So a new project was generated and I have two messages inside. The

first is the message from the user who asked test and then a response from the assistant which is a type of error because something went wrong because this is clearly not something the AI can

generate. Right? So let's try build a

generate. Right? So let's try build a landing page and let's click submit. And

what should happen now in the Prisma Studio here uh this one is that we should have a new project now modern London with one

message. As you can see let's just

message. As you can see let's just refresh. There we go. So build a landing

refresh. There we go. So build a landing page by user. We are running this and now we should have a successful example and all the messages for this project

will be stored in that project. So you

can see how our submit data was a project ID and the value build a landing page. So there we go. Now when I refresh

page. So there we go. Now when I refresh this again, I should get another message from the assistant with the task

summary. And this message also has a

summary. And this message also has a proper fragment. And inside of here we

proper fragment. And inside of here we should be able to see let's open in new tab this fragment. And in here I have

the sandbox URL. And I should now see the landing page. There we go. Perfect.

So now that we have this uh let's go ahead and just do one more thing so we can start building the UI for these messages. So let's go inside of source

messages. So let's go inside of source app folder. Let's create a new folder

app folder. Let's create a new folder called projects and in here open project ID. So this is basically a dynamic URL

ID. So this is basically a dynamic URL part. Uh it's important how you write

part. Uh it's important how you write this. So curly brackets uh square

this. So curly brackets uh square brackets are extremely important. And

then how you type inside is exactly how you're going to extract this value. So

be mindful of casing. Right

now add page.tsx

here and export uh do a page export like this and a div like this. And this will be project ID. And then to extract the

project ID. And then to extract the project ID, you simply create an interface props with params which are a type of promise.

And inside project ID which is a type of string. And then in here you can extract

string. And then in here you can extract the props. You can extract the params.

the props. You can extract the params. And since this is a server component, you can make this an asynchronous component and extract the project ID

from await params. And then you can set the project ID to be project ID. As simple as this. So how

do I know that it is project ID? How do

I know it's not project ID 1 2 3?

Because of how we named the folder. So

if you name this with a lowercase letter I, then you need to change this to lowerase letter I. So be mindful of how you name this dynamic folder. And once

you've done that, go back to your page here and go ahead and add router from use router from next navigation

like this. And then add the on success

like this. And then add the on success here which I'm going to transform into an arrow function simply because I prefer

them. No other reason. We only need the

them. No other reason. We only need the data here. And let's do router.push

data here. And let's do router.push

forward slash projects and then data id.

So how come that we have the data ID available for us? Because in the create procedure, we return the created project. So this

new project that was just created, we have its ID right here. So now if I do build a blue landing page and click

submit right here, there we go. I'm

redirected to project ID and that new project. And now in here I will load

project. And now in here I will load only the messages for that project. So

in the next chapter we will go inside of our modules messages procedures and we will modify the get many to accept a specific project ID and then query by

project ID instead of loading all of them. But that is for the next chapter.

them. But that is for the next chapter.

Amazing amazing job. So you just added project schema. uh in our uh entire

project schema. uh in our uh entire application we will have one more model in the database but this is pretty much it. Amazing. So we added the project

it. Amazing. So we added the project schema message relations new project on user prompt and we preserve the project ID in background jobs. Now let's go

ahead and commit this. So I'm going to open a new branch 09 projects.

I'm going to stage all of my changes. 09

projects. And I'm going to click commit.

And let's go ahead and publish the branch.

And then let's go ahead and open a pull request.

Just like that. And let's see the summary of this chapter.

And here we have the code rabbit summary. We introduced support for

summary. We introduced support for projects allowing users to create and view projects each with an associated initial message. We also added a

initial message. We also added a dedicated project page displaying the project ID for now. This will later be the actual interface where you will chat

with an AI and see the preview of your work. And in here we have a couple of uh

work. And in here we have a couple of uh recommended changes. So in here it

recommended changes. So in here it recommends throwing an error in the background job if it cannot find the project ID. And this is definitely the a

project ID. And this is definitely the a good idea. But I would rather we don't

good idea. But I would rather we don't even invoke a background job if we don't have a project ID because where do we even save this message then right so we

have to think of a different way to improve this but a good suggestion nevertheless and another suggestion regarding the migration since this is just you know

development migration I really don't uh care about this one since it's not really dangerous for our use case. So,

I'm going to merge this pull request.

And that marks the end of this chapter.

As always, make sure you go back to your main branch and make sure you click on synchronize changes. So, you pull that

synchronize changes. So, you pull that new merge. And once that is done, you

new merge. And once that is done, you can go inside of your source control button here, go inside of graph, and you should see that we just merged projects.

Amazing. See you in the next chapter.

In this chapter, we're going to develop the messages UI. This will include creating the project view, the messages container, message card, and the message

form components. And for the API

form components. And for the API changes, we're going to have to slightly modify the get many procedures of our messages. So before we do that, let's go

messages. So before we do that, let's go ahead and ensure that we are on the main branch. And you can click synchronize

branch. And you can click synchronize changes just to make sure everything is up to date. And in your source control, your last merge should be 09 projects.

So I'm going to go ahead and go inside of source inside of modules messages procedures.

And in the get menu, let's add the ability to add a project ID. So I'm just going to copy the input from the below create procedure. And I'm going to add

create procedure. And I'm going to add it here. And I'm going to delete the

it here. And I'm going to delete the value because it's not required here.

only project ID is required. And once we have the project ID, we can extend this to add a where. And let's go ahead and

add project ID to be input project ID.

Now let's go ahead and actually dstructure the input from here so we can use it properly. Just like this.

Perfect. So now we can load messages for an individual project. Let's go ahead and let's do that. So now I'm going to

go inside of source app projects project ID page.tsx

and since this is a server component what we are going to do is we're going to leverage prefetching. So I'm going to go ahead and do const query client

and I will do await get query client from the RPC server and this is not a promise. So we don't need a weight here.

promise. So we don't need a weight here.

You can usually see that if you type an await on something that does not need a weight, you will see little three dots here which will tell you that it has no effect on this. But you can also see

that when hovering on something, you will see that there is no promise wrapping this. For example, when I hover

wrapping this. For example, when I hover over params, you can see that there's a promise of wrapping this. So a weight makes sense, right? in here. Nothing

would happen if I used await, but we don't have to use await. And let's now add a void TRPC, which you can import from the TRPC server. Same same as get

query client. And let's go ahead and

query client. And let's go ahead and actually do void query client. Prefetch

query tRPC dot messages get many query options and pass in the project ID which we

structure from right here.

Perfect. So now what I want to do is I also want to add inside of my modules projects server procedures

I want to add a get one like this and I want to add an input

here and I want to call this ID ZR with a minimum value of one and a message ID is required.

So should you call this ID or should you call this uh project ID? Well, since

this is regarding fetching a single project, in my opinion, it is kind of redundant to call the property project ID here. Uh and let me just see what I

ID here. Uh and let me just see what I did incorrectly here. So this is not how you open this. You should add Z.Object

and then wrap this in parenthesis like this. This is the input. And then in

this. This is the input. And then in here you can go ahead and import uh extract this input and you would find

existing project here to be await prisma project find unique

like this and you would just do where ID is equal to input ID and then go ahead and return the

existing project and you can add if there is no existing project throw new TRPC error which you can import from TRPC server and the cool thing about

this is that you have strictly typed codes. So in this case this would be not

codes. So in this case this would be not found and then we can specify our message which can be project not found.

Great. So now we have a procedure to fetch an individual project by its unique ID. So we can leverage the find

unique ID. So we can leverage the find unique which uses the index ID.

Now let's go back inside of the page here and let's also prefetch for that.

So TRPC projects get one and instead of ID uh instead of project ID we are using the ID field because just think of it when we are fetching messages it makes

sense that the prop is project ID because it's referring to an entirely new entity. But when we are fetching

new entity. But when we are fetching projects, we already know that ID is referring to the project ID. That's why

in my case, it makes no sense to call this project ID. We already know it's a project. At least that's kind of my idea

project. At least that's kind of my idea of naming a convention here. In here,

basically, we are doing this just in case you were confused. But yeah, you can do a shorthand operator if the key and the value are named the same. So now

we are prefetching these two which means that we can now create our project view component. So I'm going to do that by

component. So I'm going to do that by going inside of modules projects and I will create a new folder called UI.

And inside of here I will create views.

And then inside of here I will create project- view.tsx.

project- view.tsx.

And I will mark this as use client. And

I will export const project view.

And in here I will create an interface props project ID. And I will call this a string. In here you could also

string. In here you could also technically use ID since we know what it's referring to but I originally built the project using this. So I just don't

want to alter the source code.

And now in here we are going to rely on getting the data from use suspense query

and from using const tpc usepc like this trpc dot projects and

here I have it get one query options and pass in the ID to be project ID and let's go ahead and remap this to project. Then let's copy this and let's

project. Then let's copy this and let's change this to messages get many and this will use the project id key and we

are going to remap this to messages and now in here we can return a div

project JSON stringify project and then below JSON stringify messages null too just like that just make sure you've

marked this as use client And now inside of the page here what you can do is you can change this to be hydration boundary which you can import from tanstack react query. You can pass

the state here to be dehydrate again from tanstack react query and simply pass in the query client.

Then inside of here render the project view component and pass in the project ID to be project ID. Just like that. And

there actually is another reason why I don't want to use ID here. Simply

because ID is reserved for u HTML elements, right? You often see things

elements, right? You often see things like form ID and then something. So

because of that, I want to explicitly use project ID here. And let's go ahead and wrap this inside of suspense, which you can import from React.

And let's give it a fall back of loading like this. There we go.

Perfect.

So now if you have your app running and if you go to localhost 3000 and if you create a new project here, build a yellow landing page

and click submit.

The project ID was just loading for a second. And as you can see the first

second. And as you can see the first thing we have is for me it's freezing tent that's the random name that we generated and then immediately below that I mean after that we can see an

array of messages. The first one is build a yellow landing page by the user and in a couple of seconds we will get another message which will basically be the response. And here we have it the

the response. And here we have it the task summary. It created a landing page

task summary. It created a landing page blah blah blah. Perfect. So this works just fine and it leverages pre-fetching in the server components. Just be

careful that your query options are exactly the same in the prefetch as they are in the use suspense query. So they

need to be identical. So make sure you didn't accidentally mess them up.

Now I'm going to add some resizable panels inside of this project view. You

already have this installed when we added all Shatsen components. So you can import all of these from components UI resizable. So you can control-click to

resizable. So you can control-click to confirm that you have it. It is inside of source components UI resizable. And

now let's go ahead and actually build uh our resizable panels. So I'm going to give this div here a class name of height screen. I'm then going to add a

height screen. I'm then going to add a resizable not handle panel group. And

I'm going to wrap these two elements inside.

I will give this a direction of horizontal and I will then add a resizable panel.

And let's go ahead and wrap the project in one resizable panel and then another one for the messages like this. Let's go

ahead and give this one a default. Let's

actually collapse this. Default size

will be 35. Minimum size will be 20. And

let's give it a class name of flex flex column and the minimum height of zero.

And now let's go ahead uh and let's do the following. In between these two

the following. In between these two resizable panels add a resizable handle and add width handle

like this. And for this resizable panel,

like this. And for this resizable panel, give it a default size of 65 and a

minimum size of 50 like this. So now you should have this type of resizable panel and you can already see how this is going to look. In here we're going to have our messages and in here we will

have the project preview. Right now it is the opposite but you know it's I just wanted to use it as an example. So

that's how we're going to do that. And

by default, you can see it has what I think is a kind of fair ratio. This size

for messages, this side for the preview.

You can of course change the default size to whatever you like. Uh but you know just make uh the the the total number of these two panels needs to add

up to 100. So you know just make sure you are using the proper calculations.

Great. So now that we have this, let's go ahead and let's develop this side of the resizable panel. So I'm just going

to change this to be to-do preview.

And this here will be our messages container.

So right now we have an error because messages container does not exist yet.

So now let's go ahead and do the following. I'm going to still stay

following. I'm going to still stay inside of projects, inside of UI, and I will create components. Now, you're

probably wondering why am I creating a message container, messages container.tsx

container.tsx inside of the projects module when I clearly have the messages module right here. Well, it's not the name that

here. Well, it's not the name that decides where you put something in module-based architecture. It is its

module-based architecture. It is its purpose. And this specific messages

purpose. And this specific messages container purpose will only be used inside of the project ID page. Right? So

this project's project ID page is obviously the project module. So just

because we are rendering a component called messages here messages container doesn't mean that it belongs in the message uh module, right? but something

that's re reusable like the message API that belongs in the message uh module but messages container is just a container to render messages in the

project so that's why this is the place I'm putting it in the name doesn't matter I can call this project message container maybe that would be uh a bit

more visually attractive but just to explain why I'm putting that here now let's go ahead and let's build the messages container. So we are actually

messages container. So we are actually going to do the following messages container and then I'm going to copy a couple of

things specifically this because this is where I will load the messages. If you

can, it will it will always be better to load the messages to use it to use use suspense query in a deeper component because the deeper component you use it

in uh the the faster the page will load and I'm going to show you why in a second. So let's call this use TRPC from

second. So let's call this use TRPC from TRPC client like this. And in here we need an interface which I can just copy from the project view here.

And let's go ahead and destructure the props and get the project ID like this.

And now we have the messages here. So

let's just return a div with JSON.stringify

JSON.stringify messages.

There we go. And now we can import the messages container here from components messages container. And we can remove

messages container. And we can remove the suspense query for messages like this. and pass in the project ID here to

this. and pass in the project ID here to be project ID. And now what's important is that you wrap this inside of suspense

as well and give this a fallback of loading messages like this. So now the cool thing that's

like this. So now the cool thing that's happening I don't know if you will now see this and yes uh yeah so let me try and demonstrate.

Yeah, it's kind of hard uh to do right now perhaps because I don't need this.

What if I comment this out? Yes, you can see that when you comment this out in the project view, the page loads much quicker. That is because if we are using

quicker. That is because if we are using a use suspense query inside of the project view, then it means that this suspense will fire and that blocks the

entire page. You can see that while that

entire page. You can see that while that big loading is active, let me just write loading project.

So while this loading project text is visible, the entire page is blocked. But

if you move the suspense in an deeper cont uh component like the message container like we just did with loading the messages here and wrap that inside of suspense. So let me now simulate by

of suspense. So let me now simulate by commenting this out. You can see that we are not blocking the entire view only the messages view. So that's why I told

you that it will be faster. It's not

really faster. It is just visually faster. So we are going to do the same

faster. So we are going to do the same thing for loading the project. So yes,

for now we can actually remove this because we will not be loading the project uh inside of the project view.

Let me just move the suspense right here. Perfect. So now let's go inside of

here. Perfect. So now let's go inside of the message container and let's develop it.

I'm going to start by giving the most outer div a class name of flex flex column flex one and a minimum height of

zero. I'm then going to add another div

zero. I'm then going to add another div with a class name flex one minimum height of zero and overflow y auto. And

then inside of here another div with a class name padding top padding top two and padding right of one. And then

finally inside of here I will go over my messages. I will get the individual

messages. I will get the individual message here and I will render a new component. So in here we're going to

component. So in here we're going to render message card component and you can remove the JSON stringify here. Now

let's give this a key of message do ID.

Let's give it content of message.content

roll of message roll fragment of message fragment. And now we have a problem.

fragment. And now we have a problem.

Fragment is not loaded here. So let's go ahead and fix that by going inside of messages get many procedure. It's inside

of uh modules messages server procedures. And simply do what we did in

procedures. And simply do what we did in the previous chapter. Add include. Make

sure you're doing this instead of get many. add include fragment true like

many. add include fragment true like this.

And now let's go back inside of the messages container here. And as you can see now we no longer have that problem.

Right? So now message fragment exists.

Let's add created at here to be message.created at

message.created at just is active fragment for now to be hardcoded to false. on fragment click

will be an empty arrow function and the type will be message.ype.

Now let's go ahead inside of the components and create the message card.

Again we are doing this inside of the projects module because even though these components are called message they relate more to the product to the project entity than they do to the

message entity. And inside of the

message entity. And inside of the message card component, we are now uh going to do this the uh the following.

First, let's create the props content which is a string roll which is a type of message roll. You can import from at generated Prisma. So this is the

generated Prisma. So this is the generated folder of Prisma which you can find in your source folder. And you can see that you don't really touch this folder, right? You don't modify this

folder, right? You don't modify this folder because it is automatically generated every time you do npx prisma

generate or npx prisma migrate dev which in background runs npx prisma generate right you can always do npx prisma

generate yourself this will simply update the entire prisma right so in case yours didn't exist now it will exist so message ro was directly

generated from our schema message role.

So if yours is called something else, you're going to have to import something else. Same thing for fragment from

else. Same thing for fragment from message here. And same thing for message

message here. And same thing for message type. So basically content ro fragment

type. So basically content ro fragment which can be null created at is active fragment which is a boolean on fragment click which accepts the fragment as the

value and type which is a message type.

Now, let's go ahead and let's export the message card here and let's assign all of those props from above and let's dstructure them all here.

Perfect. Now, inside of this, let's go ahead and do the following. If roll is equal to assistant, we're going to return

a paragraph assistant.

Otherwise, we are going to return a paragraph user. And let me just fix my

paragraph user. And let me just fix my uh typo here. So we have this uh I don't think I need this. There we go.

Like this. And it's okay that all of these things are unused. Now let's go back to the messages container and import message card from dot / message card. Let me just separate my imports

card. Let me just separate my imports here. No need for use client in this

here. No need for use client in this component simply because the uh project view where it's rendered is already use client. So its children will be as well.

client. So its children will be as well.

And as you can see I have two messages.

The first one is from the assistant and the other one is from the user. And I

think that in this case we would actually need the opposite to happen. So

let's go inside of the messages container. Go inside of messages get

container. Go inside of messages get many and change the order by to be ascending. So the first one should be

ascending. So the first one should be from the user and the second one should be from the assistant. All right. Now,

let's go inside of the message card and let's actually develop this. So, let's

do the user one first because I believe it is a little bit easier. So, we're

going to do user message here and it will have one prop which is content. So,

let's pass in content here. And we're

going to develop this uh just above this con user message.

And let's create an interface uses me user message props like this. And then

just extract the props here. It's just

content. And in here return a div with a class name flex justify and padding

bottom of four PR of 2 PL of 10. And in

here add a card from components UI card.

You already have this as well. It comes

with chat UI. You can find it in source components UI card. Now inside of the card render the content and give the

card a class name of rounded large background muted padding three shadow none border none maximum width of 80%

and break words like this. So the user message will be

like this. So the user message will be rendered every time the user sends a message and we should be able to see that now. Build a yellow landing page.

that now. Build a yellow landing page.

That was my first message and you can see how my message is moved into this corner. We are now going to render the

corner. We are now going to render the assistant output. So in order to do that

assistant output. So in order to do that we will render the assistant message like this.

assistant message and the assistant will have uh some different props. So we're

going to pass the content to be content fragment to be fragment.

It will have created at it will have is active fragment and it will have on fragment click

and it will have a type. Basically all

the other props are related to the assistant message. So now let's go uh

assistant message. So now let's go uh below the user message. Let's create an interface assistant message. And in here we can just add all of those props.

Content fragment which can be a type of fragment or null created at which is date is active fragment which is boolean on fragment click and type. I'm not sure but maybe these are identical to message

card props.

Uh it doesn't have roll. So yeah, one less prop. I'm not sure if this is the

less prop. I'm not sure if this is the best way to do this, but you know, I think it's fine. Now, let's go ahead and actually do const assistant message

like this. Let's destructure assistant

like this. Let's destructure assistant message props.

Oops. Yeah, I should call this props.

Yes, like this. And then inside, let's just add all of those things. Content

fragment created at is active on fragment. Click enter and type.

fragment. Click enter and type.

And inside of here, we are going to do the following. Let's add a div with a

the following. Let's add a div with a dynamic class name, which means open curly brackets and import CN from lib utils. If you don't remember this, but

utils. If you don't remember this, but we got this when we installed shot cnui.

And I told you we are going to use this when we need some dynamic classes and this is the first time we need that. So

the way you use this library is very simple. You open it up as a function. It

simple. You open it up as a function. It

can accept an infinite number of parameters. So the first parameter, the

parameters. So the first parameter, the second parameter, the third, infinite number.

What I like to do is I like to reserve the first one for my static class names.

So flex flex column group ex 2 and padding bottom of four. And then in the second argument, I like to do dynamic

ones. If type is equal to error, I'm

ones. If type is equal to error, I'm going to render it differently. I'm

going to render text red 700 and on dark mode text red 500 like this. And then

inside of here I'm going to add a div with a class name of flex item center gap 2 pl 2

and margin bottom of two. Now I'm going to add to-do add logo because we don't have it yet. And I'm going to add an image component here. Uh actually we can

do that only when we have the logo. So

let's add a span for now and our app name. In my case this will be vibe text

name. In my case this will be vibe text small and font medium like this. Then copy this span and in

like this. Then copy this span and in here you're going to need to install npm install date fns. This will be used to

parse dates. And let me show you my

parse dates. And let me show you my package json date fns 4.1.0. zero.

And I'm going to import something from date FNS. So import format

date FNS. So import format from date FNS like this.

And inside of here, I'm going to format created at like this.

And I will format in this format like this.

And then I'm going to slightly modify this to be text extra small and text muted foreground. And then then I'm

muted foreground. And then then I'm going to give it an opacity of zero. And

I'm going to give it transition opacity.

And since I have given this outer parent div a group class name, I can leverage that by doing the following. I can do

group colon my apologies group dash hover. So when the group is hovered,

hover. So when the group is hovered, change the opacity to 100 like this. And

that's how I'm going to make this appear when we hover on the parent element.

Perfect. And then outside of this div, let's go ahead and let's actually render the content. So div class name pl 8.5

the content. So div class name pl 8.5 flex plex column and gap y of four. And

inside of here, a span with content inside. And let's go ahead and make sure

inside. And let's go ahead and make sure we are using the assistant message. We

are perfect. And there we go. You can

see build a yellow landing page. And

then vibe answers at this time which only appears when I hover with a task summary like this. Perfect.

So now let's go ahead and continue uh developing this uh and let me just see.

So in here we have flex item center gap 2 PL2 margin bottom of two. Okay. I

think I think this is okay. I am just this this spacing seems a little bit odd. I'm

not sure this is how it's supposed to be. But yeah, go ahead and try and

be. But yeah, go ahead and try and collapse your page a bit. It should work fine. It should normally break words. It

fine. It should normally break words. It

shouldn't add any scroll bars except the the the one from up down, right? That

one should appear, but no one on the x-axis should not happen. Uh great. So

now let's go ahead and let's obtain our app logo.

So head to the assets page. You can see the link on the screen or you can use the link in the description. And in here you can find logo.svg.

I found this logo from logo Ipsum. So

these are amazing placeholder logos you can use for your projects. Uh and I use them in pretty much every project. They

are amazing. So I slightly modify them to match the color scheme of the project. You can download them or you

project. You can download them or you can copy the SVG since the code is in SVG and you can then go inside of your project and what I like to do is go

inside of public create a new logo. SVG

here and then I click this open file using VS Code standard text binary and I paste it inside and save it and that creates the logo or you can just

download it as a file normally without all that trouble. So now let's go ahead and let's add our logo to our message card. Specifically in the assistant

card. Specifically in the assistant message, I added a to-do here. Now let's

add an image here from next image. So

make sure you have added this import here.

And then we're going to add the following. Source will be

following. Source will be forward/lo.svg.

forward/lo.svg.

Alt will be vibe. Width will be 18.

Height will be 18. and class name will be shrink zero. And let's go ahead and try again.

zero. And let's go ahead and try again.

And there we go. So now this space makes more sense because the logo perfectly pushes the text to be aligned with the content right here. Amazing. And don't

worry about this task summary tag. We

will get rid of that later uh using something else. But this is basically

something else. But this is basically how our chat will look like. And if

you're wondering, the colors don't look exactly as your demo, don't worry. We're

going to change the entire theme of the project later. But this is what I wanted

project later. But this is what I wanted to achieve. So now what I want to do is

to achieve. So now what I want to do is I also want to add a little uh message on the bottom here. I mean a little form

on the bottom. Uh but just before I do that, I also want to create a fragment component. So after we render the span

component. So after we render the span content, let's check if we have the fragment and if type is equal to result

only then are we going to render the fragment card. The fragment card will

fragment card. The fragment card will accept three props. The fragment itself is active fragment and on fragment

click. And we can create the fragment

click. And we can create the fragment just above here. So first the props fragment card props fragment is active

fragment and on fragment click and then the fragment card component. So let's

just use the props and extract them here.

And then inside of here we're going to return a button but a normal HTML button like this. We're going to give it a

like this. We're going to give it a dynamic class name using the CN library.

In the first argument I will add flex

items start text start gap two border

rounded large background muted width fit padding three hover bg secondary and

transition colors and then I'm going to check if is active fragment And I will do the following

background primary text primary foreground border primary and hover bg

primary like this. And on click here I will call on fragment click and pass the fragment as the prop.

Inside of the button itself I will add code to icon.

So from lucid react, let me just fix this uh invalid fragment end here. I

don't need this. There we go. The code

to icon will have a class name of size four and margin top of.5.

I will then open a div with a class name flex, flex column and flex one. And

inside of here I will have a span which will render the fragment title.

and the class name text small font medium and line clamp one. Below this

another span with a class name of text small and the text preview.

I think we should already start to see this because this message from the AI assistant has the fragment and it is not an error. So we can see it right here.

an error. So we can see it right here.

Make sure that you are doing this on a successful response. So you have the

successful response. So you have the fragment generated in your database. If

you are unsure, if you still can't see it, npx Prisma studio to show you what I'm talking about. So your message, whatever one you're doing

should have a fragment. You can see how some of my messages don't have fragments because they are by user or they are errors. But the ones that are successful

errors. But the ones that are successful have a fragment, right? So that's what you need to do. You basically need to create uh a background job with a

successful generation, something that has a fragment. So now after the preview here, outside of this div, I'm going to add another div with a chevron write

icon from lucid react with a class name of size 4. So the same import place as code to icon. And let's go ahead and

give this a class name. flex items

center justify center and margin top of 0.5.

And I think that marks the end of the message card component. I think we have everything we need. Now the only thing I don't like is that this doesn't have the

pointer cursor. It doesn't look

pointer cursor. It doesn't look clickable. But you don't have to fix

clickable. But you don't have to fix that by adding the pointer to uh this because this is already a button. So

what we're going to do is we are going to change the global CSS so that it shows the pointer when this is hovered like this. I mean not this one but you

like this. I mean not this one but you you get the idea right. Perfect. So now

what we can do is we can create the form here at the bottom and that will uh complete the message container.

So let's go ahead and go inside of the components and let's create the message dash form.tsx.

dash form.tsx.

So this will be rendered at the bottom of the message container. Let's go ahead and just copy the props from the previous components and let's export

message form inside of here. Go ahead and assign the props and destructure the project ID and

return a div message form. And now let's go inside of the messages container. And

now we have to render this. So I'm going to render it uh after the last div here.

I'm going to open a new one with a class name relative padding 3 pt1.

And then I'm going to add message form and I'm going to pass in the project ID.

Project ID like this. So make sure you have added

like this. So make sure you have added this import. And now at the bottom you

this import. And now at the bottom you will see message form.

In order to complete the message form component, we're going to have to install a new package react text area autosize. So go ahead

and install this. And I'm going to show you the version. So, package JSON 8.5.9.

That is my version. And now, let's go inside of the message form. And we're

going to need a couple of things from React hook form. So, use form. And then,

we're going to need Zod resolver from hook form resolvers zod. And if you're worried where do these packages come from, we already have them. cook form

and form react cook form. So all of this already exist and they came with chatnui when we added all components and the new

one is this one text area auto size from react text area auto size and besides

this let's just see uh what else do we need let's also add use state from react

like this let's also add zod and let's add post from sonner and let's also add some icons. So that's going to

be arrow up icon and loader two icon from lucid react and from tanstack query we need use mutation use query and use

query client from tanstack react query then let's add cn from lib utils use trpc from at tpc client the button

component and form and form field from components UI form this is another shhatsenui component and when you installed that

which you did using the d-all command.

You also got use form and you got the zod resolver and also zod. And that is it for now. So now let's define

form schema here to be z.object. And

what you should actually do is you should visit one of your procedures in messages specifically find the create procedure and you should copy the value

from here. So you have the limit right?

from here. So you have the limit right?

So like this.

Now how you're going to call this value string um I really don't know. So you

can do value you can do content whatever you want. And let's go ahead and do the

you want. And let's go ahead and do the following now that we have this form schema. Con form use form pass in

schema. Con form use form pass in Z.infer infer type of form schema like

Z.infer infer type of form schema like this and add resolver here to be zod resolver and pass in the form schema

object and the default values will set the value to be an empty string by default.

Great. Now that we have the form, let's build the UI. So the outer div will be the form element from here from

components UI form and we have to pass the entire object that we created here using use form and then inside we need a

native HTML form element like this and in here we need the following. We need

onsubmit to be form handle submit and then we have to create a custom submit form. So const onsubmit here we'll

form. So const onsubmit here we'll accept the values which are basically this. So you can copy this from above

this. So you can copy this from above and for now just console log the values.

The reason we are doing this infer is because when you hover over you can see that it is exactly what you define here.

So now use that onsubmit and pass it here. So now this onsubmit will only

here. So now this onsubmit will only trigger this which will actually initialize the network call when the validation passes. So that's why we are

validation passes. So that's why we are wrapping it inside of here. Perfect. And

now let's go ahead and do a class name here.

CN relative border padding four padding top one rounded

extra large background sidebar dark bg sidebar and transition all like this and then if is focused

which doesn't exist yet we were going to do shadow extra small and for show usage we are going to do a rounded top none.

So now let's go ahead and just quickly uh fix these things. So for is focused it is an easy fix. All we are going to do is add a new use state here with is

focused and set is focused with the default value of false from use state react. And for the show usage I'm going

react. And for the show usage I'm going to manually set it to false for now. So

now you should have no errors here. And

periodically you can check on this just to see how it looks. Great. Now that we have this, let's go ahead and add the form field component which is a

self-closing tag. Just make sure you

self-closing tag. Just make sure you have imported it. Give this a control of form.

Give it a name of content and give it a render of field like this. And inside use the text area

like this. And inside use the text area auto size self-closing component. In

here you can immediately spread everything you have from the field above. And then go ahead and give it the

above. And then go ahead and give it the following and give it an onfocus and on blur to modify the set is focused state

like this. And the name should be value.

like this. And the name should be value.

My apologies. So already when you hover over this, I'm not sure if you can notice, but that's there's an ever so slight shadow change to the entire

object. Now we have to fix this so it

object. Now we have to fix this so it doesn't look so weird. So let's go ahead and give this a minimum rows of two and

a maximum rows of eight. And then a class name. Adding top four bore. Resize

class name. Adding top four bore. Resize

none. Border none. Width full. Outline

none. Background transparent.

And a placeholder of what would you like to build?

And then let's go ahead and do on key down.

get the event and check if event key is equal to enter and open parenthesis. We are also

holding control key or meta key. So this

will basically be control enter. We

prevent the default and we do form handle submit onsubmit oops onsubmit and pass the event as

well. So the onsubmit is this

well. So the onsubmit is this just like that. So now uh outside of this which is form field I believe. Yes

outside of form field but still inside of the form let's go ahead and do the following. Let's add a div

following. Let's add a div with a class name flex gap x2 items end

justify between padding top of two. Then

another div with a class name text 10 pixels text muted foreground and font mono. And just write test here simply so

mono. And just write test here simply so you see where that is. So it's right here at the bottom.

So this will now be the following. It

will be a uh keyboard sign. I think this is for keyboard. The

sign. I think this is for keyboard. The

the short name for keyboard. Uh render a span inside and render the following sign

like this and then enter. And that will turn uh like this. The command sign and enter.

Now let's style it.

The class name will be ML auto pointer events none inline flex height five

select none items center gap one rounded just rounded border background color

muted px 1.5 font mono text 10 pixels font medium and text muted foreground

ground and then let's go ahead outside of the KBD and let's do NBSP to submit.

So basically command enter to submit. We

are telling the user how to submit. And

now outside of this div add a button element. And this button element will do

element. And this button element will do the following. It will render arrow up

the following. It will render arrow up icon which we already have imported from Lucid React. There we go. And now we're

Lucid React. There we go. And now we're going to style it. Give it a class name

of CN size 8 and rounded full like this.

There we go. This is how it's going to look like. And now we need to add some

look like. And now we need to add some dynamic things here. So let's start by

adding our create message mutation.

So we need to add PRPC here. Use PRPC.

And then we need to add create message from use mutation ERPC messages create mutation options

like this.

And then you can extract the following.

You can then extract const is pending to be create message is pending.

Const is disabled to be is pending or if not form form state is valid. So if form state is not

valid like this. And let me actually move these two to the bottom here simply so I have all of these things in one place.

And now that we have the create message mutation, let's go inside of the onsubmit and let's make it an asynchronous method. And let's do await

asynchronous method. And let's do await create message dot mutate async and pass

in the value to be data. Actually this

is values. So values do value and the project ID like that. Perfect. And now

let's use the is pending and let's use uh the is is disabled. So first things first to the text area auto size

disabled if is pending like that. And then let's go ahead down

like that. And then let's go ahead down to this button. And the button will be a little bit different. So this one will be disabled if is disabled. So be

careful for the text area auto size. We

only disabled if it's pending. So only

if the network request is pending. But

disabled will be for this. So you can do is button disabled just to don't so you don't make a mistake. There we go. Uh and let's also

mistake. There we go. Uh and let's also do if is button disabled background muted foreground and border like this.

And then inside of here a turnary if is pending. In that case we are rendering

pending. In that case we are rendering the loader two icon which we already have imported with a class name of size

four and animate spin. Otherwise we

render the arrow up icon like this.

There we go. So now make sure that you you know restart your server here.

Actually I will restart the entire project as well. So, npm rundev, npx inestdev.

I will refresh this page here and I'm going to add build a blue landing page and I will press command enter. And

there we go. You can see that that has submitted this for a second. It was

loading. We still have to do the cleanup function. But if I look in my inest

function. But if I look in my inest developer server, you can see that this is successfully running. Amazing. And if

I refresh here, I should actually see my new message here. Build a blue landing page. Perfect. So now let's go ahead and

page. Perfect. So now let's go ahead and just add some onsuccess things to happen in the mutation options of the create message. Right. So what should happen

message. Right. So what should happen after we submit? So the first thing that should happen is on success here once we get the data of this new message. Let's

go ahead and let's first do form.reset

like this. So make sure that form is initialized above and then let's do query client which uh I'm not sure do we have it we don't. So let's let me just

add const query client to be use query client.

So you have this imported from tanstack react query. So in here what you're

react query. So in here what you're going to do is queryclient.invalidate

invalidate queries and then pass in TRPC messages get many query options

project ID data project ID or you can use the project ID from here yeah maybe that's even easier to do and then you can use the shorthand operator

that's the first thing we are going to invalidate then the second thing uh we don't have yet so I will add a to-do reinvalidate or invalidate

uh usage status. We don't have this yet, but we will have it later. And now add on error here. Get the error

and do toast dot error error dossage.

And I will add a to-do redirect to pricing page if specific error.

There we go. And the only thing I don't have left here is the use query. And I

will remove it for now because we don't really have the entity we need to call.

And I think that for now this is it. I

think for now this is everything. uh we

can do here and there we go we have a response now created a fully responsive production quality blue themed landing page perfect so now what I want to do is

just to end this chapter one more thing here I don't like how uh the first thing is when I load the page you can see I have to scroll all the way down and the

second thing is when I scroll the text visibly clips here you can see how it's cut so let's fix those two things and let's end the chapter. Both of these things will be in the messages

container. So, make sure that you have

container. So, make sure that you have some messages and you can zoom in a little so you have the scroll bar like I do.

The first thing will be a very simple self-closing div just above the place where we render the message form inside of this relative div. and give it a

class name of absolute minus top minus 6, left 0, right zero,

height of six, background gradient to bottom from transparent to oops to dash background forward

slash70 pointer events none. What this will do is it will create an ever so slightly white shadow. I'm not sure if you can

white shadow. I'm not sure if you can see it, but it kind of melts the the overflow so it doesn't look as obvious that the text is clipping here. If you

want to, you can improve this and change this to two background. And then you can see you can't you can't see the clipping at all. It's like it fades into some

at all. It's like it fades into some kind of fog, right? So just a slight effect to make this look better.

So it doesn't clip. Now let's do the thing that when we load we scroll to the bottom here. So in order to do that we

bottom here. So in order to do that we first have to add a bottom ref. So let's

do that here. const bottom ref will be use ref from react with a default value of null and the type of HTML div element

like this. Let me just move this to the

like this. Let me just move this to the top like that.

And then what we are going to do is we are going to change this to be use effect

which you can import from react and let's first do the following const last system or let's do last assistant

message and do data Find last. My

apologies. Messages. Find last.

Search through the messages and find the message whose role is assistant. And

that's how we are going to find the last message that the assistant sends. So

make sure you're using the find last API here. And if we are able to find this

here. And if we are able to find this last assistant message, what we are going to do first is we are going to set

the fragment uh to that assistant message. Now we don't have this yet.

message. Now we don't have this yet.

So actually I'm not sure if we can do that. So let me just do to-do uh and

that. So let me just do to-do uh and let's do set active fragment. Right? So

we're going to do this uh well maybe in this chapter, maybe in the future. I I

will see. But let's add messages for now like this. And then let's go ahead and

like this. And then let's go ahead and add another use effect.

And in here we will do messages.length.

And we're going to check if bottom ref question mark scroll into view like this.

And let me just check. I think um that for now this is okay. If I do a refresh here, uh looks like it's not working. So So it

should be scrolling me to the bottom, but it is not probably because I never added that. So, let's go ahead outside

added that. So, let's go ahead outside of here, add a self-closing div and give it a ref of bottom

ref.

So, now when you refresh, there we go.

You can see how you scroll down immediately.

Perfect. Um, so now, um, yes, I think I'm going to end the chapter here simply because it's already been an hour. So we're going to end here

and in the next chapter we're going to wrap this up by adding is active fragment functionality. We're going to

fragment functionality. We're going to add some loading states while we wait for the response. And we're also going to add the header here so that we can click the back button to go back to the

landing page and so we can uh access some settings here and see the project name. Great. So, we've already made some

name. Great. So, we've already made some great progress here. And you can add something uh like this if you want to see the error state.

And now you can see how the error state looks like when you send it something that it cannot generate. It will simply tell you something went wrong and it highlights the red color. Perfect. So,

I'm very very satisfied with this. So,

we've done this. We've done this, this, this, and even more than this. Now,

let's go ahead and open a new branch and merge this. So, 10 messages UI. I'm

merge this. So, 10 messages UI. I'm

going to open this. I'm going to create a new branch.

10 messages UI. I'm going to stage all of my changes and I will do 10 messages UI and I will commit and I will publish

the branch. A quick reminder that there

the branch. A quick reminder that there is a free code rabbit extension which you can use to improve your code quality. And now I'm going to go and

quality. And now I'm going to go and open this pull request here. And we're

going to review the summary of this chapter and everything we did.

And here we have the code rabbit summary. We introduced a chat interface

summary. We introduced a chat interface for project pages, including a message list, message input form, and support for assistant and user messages with styled cards.

Added support for displaying message fragments and interactive fragment cards. We implemented a horizontally

cards. We implemented a horizontally resizable panel layout with a dedicated area for future preview features. That

is exactly what was the point and goal of this chapter. And may I say we did a pretty good job because no comments,

only some nitpicking comments like we could save some time by doing project ID instead of project ID equals project ID.

So overall amazing amazing job. In here,

of course, we have an in-depth diagram explaining exactly how everything in this page happens, including pre-fetching, including invalidation,

including refetching, everything.

Amazing, amazing job. I'm going to merge this poll request. Once the poll request is merged, I'm going to go back inside of my IDE and I will go back to the main

branch. After that, I'm going to

branch. After that, I'm going to synchronize my changes and I will check the source control and the graph so I can see that I

successfully merged chapter 10. That

marks the end of this chapter, I believe. Amazing, amazing job and see

believe. Amazing, amazing job and see you in the next chapter.

In this chapter, we're going to continue the UI development from the last chapter. We pretty much completed the

chapter. We pretty much completed the messages container at that point, but we do have some things missing like the fragment selection and the loading state. But after that, we're going to

state. But after that, we're going to focus on the project header component, which is the component above the messages container, which will tell us which is the currently active project

and the buttons to go back. So, let's go ahead and first handle the leftovers from the previous chapter. As always,

make sure that you're on your main branch and clicked on synchronize changes just to confirm everything is up to date. So now what I want to do is I

to date. So now what I want to do is I want to go inside of my project view inside of projects UI views project view. And in here, let's go ahead and

view. And in here, let's go ahead and let's introduce an active fragment and set active fragment state

from use state and by default let's set it to null and the type can be a type of fragment from Prisma or null. So just

make sure you added this imports here.

Once you've added that, let's go ahead and let's modify the messages container component to have a few more procs.

Let's add active fragment to be active fragment. And let's add set active

fragment. And let's add set active fragment to be set active fragment. Now

go inside of the messages container and let's improve these props. So, I'm going to add the active fragment prop to be fragment or null. And make sure to

import the fragment and add the set active fragment right here. And then you can extract them in the new props here.

Active fragment and set active fragment.

Just like that. And then inside of use effect here, if we detect the last assistant message, call set active fragment

and set last system message. My

apologies, last assistant message dot fragment inside, but only if we have

last assistant message fragment.

Uh well actually since it's going to be null h yeah let's go ahead and we can just do

this it's okay and call this there we go. So now one of the fragments will always be selected. What we have to do now is we have to go to the message

card and set the active fragment question mark ID to be identical to message fragment question mark id and

set active fragment will um on fragment click will call set active fragment and pass the message fragment inside like

this. So now inside of your project here

this. So now inside of your project here when you click on a specific fragment it should be highlighted like this.

Perfect. And when you load the page since this is an error right now nothing is highlighted here. But if you try this again build a landing page for example.

I'm going to wait for a second for this to respond. And you're going to see that

to respond. And you're going to see that then when you refresh it will automatically select that fragment thanks to this use effect right here which

searches for the last message which role is assistant.

And perhaps we can even improve this by searching for the last assistant message with fragment. And then we can do this

with fragment. And then we can do this and message.fragment fragment and just

and message.fragment fragment and just turn this into a boolean and then just do this.

So you can see that now when I refresh this fragment is automatically selected.

Perfect. Exactly what we need. So now

that we have that, let's also create a loading state. In order to do that,

loading state. In order to do that, let's go outside of the use effect here.

Let's create a constant to find the last message. Instead of data, let's use

message. Instead of data, let's use messages like this. And then we are going to find

like this. And then we are going to find the last user message. So if if is last message user. So if last message role is

message user. So if last message role is user, it means that we are the one who sent the message last. So that's going to be the system we are going to rely on

for now to display loading. Later we can improve it more. So let's do this. Let's

go just above the bottom riff and let's do if last message is user add message loading state like this. Now let's create message

like this. Now let's create message loading tsx here and in here this is what we're

going to do. So import image from next image and import use state and use effect

from react. Now in here first define

from react. Now in here first define shimmer messages function.

And in here add an array of messages.

This can be anything you want. So I'm

going to add thinking, loading, generating, analyzing your request, building your website, crafting components, basically things like that.

And then what I'm going to do is I'm going to create a state for current message index and set current message index with the initial value of zero.

And then I'm going to create a use effect here like this.

And the use effect will do the following. It will create an interval

following. It will create an interval set interval.

And inside of this interval, every two seconds, I'm going to call set

current message index previous + one modulus messages.length

like that. And inside of here, I'm going to add messages.length.

And in the return method here call clear interval and pass the interval constant like this. And then inside of here you

like this. And then inside of here you are going to return a div and a span.

And inside render the currently active message like this.

Now give this a span a class name of text base text muted foreground and animate false and give the outer div

a class name of flex items center and a gap of two like this. And now finally let's export

like this. And now finally let's export const message loading.

Inside of here, we're going to return a div with a class name flex flex column group px of two and padding bottom of

four. Then a div of class name flex

four. Then a div of class name flex items center gap 2 pl2 and margin bottom

of two. Then we're going to render an

of two. Then we're going to render an image component with a source of logo SVG out of our project name, width of

18, height of 18 as well, and a class name of shrink zero. After that, a span with the name of our project with a

class name text small and font medium.

Outside of this div, we're going to open a new one with the class name pl 8.5 flex flex column and get y of four. And

inside render the shimmer messages component and then inside of the messages container here you can import

message loading component like this. So

now if you try and do build a yellow landing page you will see this thinking loading

generating analyzing your request. So

something for the user to look at while this generates uh and if you really want to immediately see the results of this.

So right now we have to refresh we have to wait for some kind of refetch. What

you can actually do inside of your messages container is you can add a refetch interval for example every 5

seconds. So now even without you

seconds. So now even without you refreshing it's going to refetch the messages every 5 seconds. And there we go we get a result. So we can add a

to-do here temporary live message update like this. But just

so you can start showing this to people so you don't have to refresh your page every time. So yes, now if you take a

every time. So yes, now if you take a look at your network request, every 5 seconds there there will be a network for refreshing the messages. But don't

worry, uh since we are using React query, a lot of this will be cached.

Great. So now let's go ahead and let's build a component which will be above this and it will be used to display the

project name and the ability to go back.

So I'm going to go back inside of the project view component and just above the suspense for loading messages I am

going to add project header component.

I'm going to pass project ID to be project ID like this.

And after you've done that, let's go inside of components and let's create project- header.tsx

header.tsx like this. Now, inside of here, let's go

like this. Now, inside of here, let's go ahead and add the following imports.

Link image used theme from next themes.

So, you already have this inside of your package JSON. This will

be used to enable dark mode.

Use suspense query from tanstack react query. Some icons chevron down chevron

query. Some icons chevron down chevron left edit sun moon icon. And then let's

add use tpc from tRPC client button from components UI button. And all of these imports from the drop-own menu.

the menu itself, content item, portal, radio group, radio item, separator, sub, sub subcontent, subt trigger, and menu trigger. All of those things. Now, let's

trigger. All of those things. Now, let's

go ahead and let's create an in interface props here. And let's go ahead and define project header right here.

Now, when we are inside of here, we can add tRPC. use TRPC and we can go ahead

add tRPC. use TRPC and we can go ahead and fetch our project using use suspense query TRPC projects get one query

options ID project ID and now we've done what we did initially right remember we had the project loading here but now we moved it here so it's time to do the

following first import the project header from docomponents project header and after that wrap it in its own suspense like this

and give it a fallback of loading project like this. And now that we have this,

like this. And now that we have this, let's go ahead and add a header tag right here. Let's give it a class name

right here. Let's give it a class name of padding 2 flex justify between items center and border bottom.

And let's call it header.

And let me just refresh here. Uh, and

looks like it's not showing now. Uh, it

is, but it is very small, I believe. So,

let's go ahead. Uh, and let me see.

Let's Oh, my apologies. No, it is not visible. We are not rendering anything.

visible. We are not rendering anything.

I thought it was just very small, but it didn't make sense.

Make sure to return it. There we go.

Now, we can see header in the text.

There we go. Perfect. So now let's go ahead and develop this header even further. So I'm going to add a drop-own

further. So I'm going to add a drop-own menu here. We have all of these

menu here. We have all of these components imported. Now inside of here,

components imported. Now inside of here, add a drop-own menu trigger and give it an as child property. This will allow it to become

property. This will allow it to become the button which is inside.

And then let's give this button a variant of ghost, a size of small, a

class name of focus visible ring zero, hover bg transparent, hover opacity 75,

transition opacity, and pl2 with an exclamation point at the end. In

Tailwind, this means important. We are

basically overriding some classes in here. You're going to render the image

here. You're going to render the image with the source of logo SVG out of the

project name width of 18, height of 18.

Then a span element with the project name, the project name coming from the query which we just loaded. This will

have a class name of text small and font medium. After that, a chevron down icon.

medium. After that, a chevron down icon.

And there we go. This now becomes a drop-down menu. It doesn't have the uh

drop-down menu. It doesn't have the uh proper cursor, but don't worry, we will fix that later.

Great. So, now let's go ahead and go outside of the drop-down menu trigger and let's add drop-down menu content.

And let's give this a side of bottom.

Let's give this an align of start.

Let's get the drop-down menu item here.

Let's give it an as child property.

Let's make sure we close the drop-own menu item component.

Add a link component here. Give it an href to the root page. Add the chevron left icon and a span element. Go to

dashboard.

And there we go. Now the first item is to go back. Perfect. Now we have a way to go to the landing page.

What I want to do next is I want to create a drop-own menu separator. So

let's do drop-own menu separator here.

There we go. And below that add a drop-own menu sub and then a drop-own menu subt trigger.

Give this a class name of gap 2.

Inside of this trigger, render a sun moon icon.

Give this a class name of size 4 and text muted foreground and then a span with the text appearance.

And now you have a submen here. And now

let's go ahead and go outside of the trigger and add drop-down menu portal.

Inside of the portal, add drop-own menu subcontent.

Inside of subcontent, add drop-down menu radio group.

Give it a value for now uh of light and on value change of an empty arrow function for now.

Now let's add drop-down menu radio item.

Give this a value of light and render a span light.

Now go ahead and copy this two times.

The second one will be dark with the text dark. The third one will be system

text dark. The third one will be system with the text system like this.

And now you will have the option to select different themes. In order to enable this, we first have to go inside

of our layout, our main layout in the app folder next to the root page. Right?

So this one with the body and everything and then in here add to the HTML tag suppress hydration warning and then

inside of body add a theme provider from next themes and encapsulate the toaster and the children. So just make sure you have

children. So just make sure you have added the import here. Let me just move this up here.

Oops. Looks like I did something incorrectly here.

Let me just do it again. So, I'm going to add theme provider and encapsulate the children.

Now, inside of here, I'm going to give it an attribute class. I'm going to give it a default theme of system and I'm going to give it the enable system

option as well as disable transition on change.

And now let's go inside of project header back. And in here

header back. And in here I'm going to add const set theme and

theme from use theme. You have imported this from next themes. You can remove the edit icon. And now let's go back to

our radio here. Set the value to be theme. Change this to be uh set theme.

theme. Change this to be uh set theme.

And I think that is pretty much it. If

you try clicking on dark mode, uh it should use the dark mode. Try refreshing

if it doesn't work. There we go.

Perfect. We now have dark mode. We will

of course improve the look of it later, but pretty impressive so far. Great. So

that marks the end of the project header for now. What we're going to do or start

for now. What we're going to do or start doing in the next chapter will be previewing the actual fragments and fix any potential issues that we have. This

will also include creating the code editor, right? Amazing job. So, let's go

editor, right? Amazing job. So, let's go ahead. I can see that we have some issue

ahead. I can see that we have some issue here. Every time I select this fragment,

here. Every time I select this fragment, very soon the bottom one starts to select. So, I'm pretty sure that

select. So, I'm pretty sure that something inside of my messages container uh oh yes, the refetch interval is probably causing this to uh

refetch every time. So, maybe a better option for now would be to not use it.

So, I'm going to comment it out. I will

add to-do. This is causing problems. Yes, it's definitely that refetch interval. So now by default no fragment

interval. So now by default no fragment is selected only you can select it. It's

okay to be like that now. Uh great in the next chapter we are developing this.

So let's go ahead and do what we usually do. Let's mark what we completed

do. Let's mark what we completed and let's open a new branch project header.

So I'm going to open a new branch here.

Create new branch 11 project header.

I'm going to stage all of my changes. 11

project header. I'm going to commit and I'm going to publish this branch.

Then I'm going to go ahead and go in my GitHub and I'm going to open a new pull request so that we can review all the things we did.

And here we have the summary. New

features. We added a dynamic project header with theme switching and navigation options. We also introduced a

navigation options. We also introduced a loading indicator with animated messages during message processing. We enabled

live updates for messages with automatic refreshing every 5 seconds. We improved

message interaction by highlighting and managing active message fragments.

Perfect. That is exactly what we did in this chapter. As always in here, we have

this chapter. As always in here, we have file by file walkthrough. And of course a sequence diagram this time including the periodically refetching messages

which we just added. Amazing. And as for the comments, we are very good again. No

comments except some nitpick comments.

Amazing job. Let's go ahead and let's merge this. And after you have merged

merge this. And after you have merged it, go back to your project, change to the main branch, and make sure to synchronize your changes. After you have synchronized your changes, as always,

you can click on the source control graph and confirm that you have just merged chapter 11. And I believe that marks the end of this chapter. Amazing,

amazing job. And see you in the next one.

In this chapter, we're going to focus on creating the fragment view component.

And this entire chapter is pretty straightforward. We just have to create

straightforward. We just have to create an ability to view that E2B sandbox URL.

So, let's go ahead and do that. As

always, ensure that you're on your main branch and synchronize changes to make sure everything is up to date. The last

chapter was chapter 11. So, now let's go ahead inside of source and basically just find project view. There we go.

Inside of this project view, go inside of your second resizable panel and in here render the fragment web component.

We are only going to render this if we have an active fragment. So if you have an active fragment only, then render the fragment web and pass in the data to be

active fragment. As simple as that. And

active fragment. As simple as that. And

you can turn this into a boolean like this. Perfect. Now let's go ahead and

this. Perfect. Now let's go ahead and let's go inside of components fragment web.tsx.

web.tsx.

Now in here let's create an interface props with fragment from generated Prisma. Now let's go ahead and let's add

Prisma. Now let's go ahead and let's add a couple of more things. Use state from react external link icon and refresh CCW

icon. And then we're going to use the

icon. And then we're going to use the button component from components UI button. Now in here let's go ahead and

button. Now in here let's go ahead and export function fragment web which accepts data and props. And in

here, let's go ahead and start by doing a div with a class name flex flex column full

width full height. And then inside of here, we're going to do an I frame.

And we're going to add key. Uh,

actually, we can't do this yet. My

apologies. So for now just do a class name height full width full sandbox

allow forms allow scripts and allow same origin and then loading will be lazy

and source will be data sandbox URL like this and then let's go ahead and import this

from component fragment web and I think that now when you click here you should be seeing a big error saying that sandbox was not found but try

creating a new prompt so build a landing page.

Let's go ahead and do that. And let's

wait for this to generate.

And once you get a response, you can click on the new fragment. And in here, you are now able to preview inside of the iframe the new landing page which

was just created. Amazing. Amazing job.

So you are pretty much halfway there, right? Great. So now let's add some

right? Great. So now let's add some features to make this seem like a little browser. So inside of this fragment web,

browser. So inside of this fragment web, we're now going to add a couple of things above the iframe. Add a div and

give this div a class name padding two border bottom background color sidebar flex items center and gap x of two. So

now just above here you have a little bar. Then in here add a button component

bar. Then in here add a button component and inside a refresh CCW icon. Give this

a size of small, a side of bottom. Oh,

my apologies. No, these are completely wrong props. Uh, variant of outline. And

wrong props. Uh, variant of outline. And

on click for now, just an empty function. And now you have a refresh

function. And now you have a refresh button here. Perfect.

button here. Perfect.

So after that, go ahead and copy this button.

And in here, you're going to have the following. You're going to have a span

following. You're going to have a span inside of here like this

which will render data sandbox URL. It

will have a class name of truncate.

And now let's go ahead and do the following. Collapse all of these props

following. Collapse all of these props like so.

And keep the variant and keep the size. So

let's just add a class name here to be flex one justify start text start and

font normal. So now you have a big uh

font normal. So now you have a big uh kind of like an address bar, right?

Showing the current fragment URL.

And then let's go ahead and just add disabled prop to be explicitly false.

And then after this button, let's add another one which will have the external link icon and give this one a size of

small a disabled if there is no sandbox URL variant of outline.

And on click will be an arrow function which checks if there is no data sandbox URL return otherwise call window open

data sandbox URL blank as the second argument. So it

opens in a new tab.

There we go.

Now let's go ahead and go inside of frame and let's add uh I keep doing the key but I keep forgetting to implement the key. Let's finally do that. So go to

the key. Let's finally do that. So go to the top here and add fragment key and

set fragment key and call use state like this. Then let's add copied and set

like this. Then let's add copied and set copied use state false.

Let's add on a refresh method.

Set fragment key previous previous + one con handle copy

navigator clipboard write text data sandbox URL

set copied goes to true and set timeout is fired with set copied set to false with a 2cond timeout. out.

Now that we have these two, let's go ahead and add on refresh here like so.

And for this one, let's give it an on click to be handle copy and disabled if there is no data sandbox URL or if we

just copied something.

And in here, I think it is good enough.

So now you should have buttons to open this in an external tab like this. You

should have buttons to copy this. So

when I paste, there we go. And you

should be able to refresh this. But

looks like the refresh one uh is not working. Let me just check.

working. Let me just check.

Yes, it's okay if this expires. That's

completely fine. It expires very soon because we don't want to spend our free credits on E2B. So I don't think this refresh is working and it's not working

because we need to add the fragment E here. So now when you hit refresh you

here. So now when you hit refresh you can see how it it blinks which basically means it is refreshing. Perfect. So

let's try build a calculator app.

And let's see that and let's see how that displays in something like this.

And here we have a calculator app.

Pretty pretty cool. Amazing. We can now refresh this. And there we go. You have

refresh this. And there we go. You have

a whole new refreshed page. Uh, perfect.

You can copy this. Amazing. So now what I want to do is I want to develop one simple component called hint. And we're

going to store that inside of source components. hint.tsx.

components. hint.tsx.

So not inside of the UI folder. Let me

just close it here. I mean, it doesn't matter. If you want to, you can put it

matter. If you want to, you can put it inside of the UI folder. And in here, we're going to mark this as use client.

And we're going to import everything tool tip related from components UI tool pip. You already have this in it is

pip. You already have this in it is inside of the UI folder.

So import all of these things and then create the following interface hint props accepting the children the text and then optional side and align which

accepts top right bottom or left and align start center and end.

And then let's go ahead and export const hint with some predefined props here.

So basically we have the children, the text, the side which by default will be top and align which by default will be center. And then inside of here what

center. And then inside of here what you're going to do is you're going to add the tool tip provider. You're going

to add the tool tip itself. And then

you're going to add a tool tip trigger like that as child property and the render children inside. And then you're

going to add tool tip content. You're

going to pass in the side prop. You're

going to pass in the align prop.

And inside you're going to render a paragraph with a text.

Just like that. That is our hint component. Now let's go back inside of

component. Now let's go back inside of the fragment web and let's wrap it around a couple of items. Starting with the external link icon. So simply wrap your button in your new hint component

like this.

And then you can add a text here and say open in a new tab and a side of bottom and then a line of start. Just make sure you have imported the hint from

components hint. And now when you hover

components hint. And now when you hover it say it tells you what it does, right?

Because just by looking at the icons it might not be clear. And now let's do that for the rest. So

find the copy button and wrap it in a hint like this. Click the copy with a side of bottom.

So now when you hover over here, you can see that you can click to copy and then do the same thing for this one

to refresh.

There we go. So now we can refresh as well. Amazing,

well. Amazing, amazing job. In the next chapter, what

amazing job. In the next chapter, what we are going to do is we're going to implement tabs here so we can switch

between a code preview and a actual web preview like we are doing now. Great.

So, let's go ahead and mark this as completed. A very simple chapter, but a

completed. A very simple chapter, but a very powerful and very rewarding chapter, may I say. So, let's go ahead and open a pull request. So, this is chapter 12.

I'm going to close everything. New

branch 12. Fragment preview. Is that the name?

12. Fragment preview. Is that the name?

It is fragment view.

Let's add and stage all of my changes.

Let me just click here. There we go.

Stage the changes. 12 fragment view.

Let's commit. And let's publish the branch.

And let's go ahead and open a pull request here.

And let's review what we just did.

And here we have this summary. We

introduced a reusable tool tip component for displaying contextual contextual hints. We added a web fragment preview

hints. We added a web fragment preview component with controls to refresh, copy, and open the preview in a new tab.

We enabled a live preview of the project fragments directly within the project view, including interactive controls and tool tips. Perfect. As always, an an

tool tips. Perfect. As always, an an in-depth walkthrough as well as a sequence diagram here and some actionable comments. So yes,

actionable comments. So yes, navigator.Clipboard.ext

navigator.Clipboard.ext

is technically a promise. So it can you can do on it and catch on it. So it is possible that the copy feature fails. So

it might be a good idea to add then and catch to uh at least display some kind of error at least internally for you so you know something is going on. This is

not a bad idea. And in here it allows improving uh it suggests improving accessibility for the iframe by adding the title and area labels. Great. I'm

satisfied with what we have. So, I'm

just going to merge this pull request.

And once it is merged, I'm going to go back here, main branch, and refetch.

And after it refetches, there we go. Fragment view is the last merged one. Amazing. That marks the end

merged one. Amazing. That marks the end of this chapter. So, let's go ahead and mark this as complete and see you in the next one. Amazing. Amazing job.

next one. Amazing. Amazing job.

In this chapter, we're going to implement the code view. This will be a slightly longer chapter in comparison to our last one simply because we have a

bit more components to create. But let's

start with adding tabs in our project view component so that we can switch between the fragment web component and code view component.

So as always ensure that you are on your main branch and you can synchronize changes.

The last chapter was 12 fragment view.

Now let's go ahead inside of our project view. And this time we're going to add a

view. And this time we're going to add a couple of components. So let's go ahead and just in between the fragment and the components UI resizable add tabs. Tabs

content tabs list and tabs trigger. from

components UI tabs you have them installed when you added chats UI and once you have added them it's time to use them so what I'm going to do is I'm

going to go below the project view and I will add tab state and set tab state I will add use state here and I will set

the options to be preview or code and by default it's going to be preview now Now that we have the tab state, let's go in

the second resizable panel here and let's encapsulate the active fragment within tabs.

Now in here, let's give the tabs a class name. Height full gap Y zero. Default

name. Height full gap Y zero. Default

value will be preview.

value will be tab state and on value change we'll get the new value and set tab state to be value as

preview or code like this.

Then inside of this tabs let's add a new div and let's encapsulate this once again.

This div will have a class name of full width items center padding two border bottom and gap x of two and then open

tabs list and again encapsulate the fragment. Inside of the tabs list give

fragment. Inside of the tabs list give it a class name of height zero padding zero border and rounded medium. And then

finally inside of here we can add tabs trigger.

The first one will be the value preview with the class name rounded medium.

We're going to render the I icon here which you can import from Lucid React.

And while you're here also import icon.

Let me just move them to the top. There

we go.

So, let me just fix this. Uh, just a second.

So, I think I just have to remove this.

There we go.

So, inside of this tabs trigger, add the eye icon like that and a span demo.

And then copy this trigger. This one

will be code with the text code and uses the code icon. And then outside of the tabs list,

go ahead and add a div with class name ML auto flex items center and gap x of

two.

And this button will uh I'm sorry this div will encapsulate a button from components UI button

which will uh not serve any purpose now but it will later. So give it as child

give it size small variant and for now give it well just default

a link from next link. So, make sure to add this with an href of pricing

and add a crown icon and upgrade text.

Great. And now

outside of that div and outside of this div as well. So move this outside. Add

tabs content.

This one will be for value preview. So

you can put this finally inside. And

then we're going to have another tabs content or value code. And this will simply be a paragraph to-do code.

There we go. So make sure tabs content is still inside of tabs. So now when you go inside of your app here, you should see a button to upgrade which should

lead you to 404 page. And you should see that you can switch between demo and between code. Uh the demo doesn't show

between code. Uh the demo doesn't show anything until you select a fragment.

Right? So you can see how you can switch between the two. So now let's develop the code part. So in here it will be

quite similar.

So let's start by doing the following.

MPM install prismjs.

This will be used to uh highlight code syntax. So let's go ahead and develop

syntax. So let's go ahead and develop this simple component inside of source components. So in here add code view.tsx.

And in here you're going to need uh just one more thing. So let's actually create a folder code view like this and move

this inside.

And you can change this to be index.tsx.

And then go inside of your uh vibe assets. You can use the link in the

assets. You can use the link in the description or you can see it on the screen here. And find code theme.css.

screen here. And find code theme.css.

So it's quite long. That's why we are not typing it. So go ahead and copy this and create it here. So code theme.css

and just paste the entire thing inside and save it. And now let's go ahead and develop the code view here. So you're

going to import everything from PrismJS.

And let me show you Prism.js. Oh, did I install it or not? MPM install

PrismJS.

Let me see.

PrismJS. It is installed. But I think I also need to do mpm install-d at types prismjs.

There we go. So, prismjs 1.3 and types 1.26.5 and now it works.

Below that, import use effect and then import the following things. Risenjs

components and then JavaScript, JSX, Python, TSX or TypeScript. You can

remove if you're not going to use Python for example.

And finally, import code theme CSS. So

just make sure this is in the same folder. Right.

folder. Right.

Then export const code view and create a simple interface props

which accepts the code which is a string and a language which is a string.

So assign the props. Let's dstructure

them. Code

and language. And inside of here, return a pre-tag.

Give it a class name padding two, background color transparent, border none, rounded none, margin zero, and the

text extra small.

And inside of here, add a code element, which renders the code.

And let's give this a class name.

And let's go ahead and use language dash lang like this. Usually if this was a TypeScript uh class name, you would

not do this because this type you shouldn't do this kind of half dynamic class name, right? You should instead do the full one. But this is not a Tailwind

class.

This is a class from Prism, right?

That's why you don't have to worry because you can see that it is exactly what it expects.

And now let's just use the use effect here for a very simple thing. So on load simply use prism and highlight all.

That's it. So that's going to be our code view component.

And if you go inside of project view, uh maybe we can already render it. Let's

try code view.

And let me try adding language to be JS or let's do ts. And let's do code.

Let me just try const a is equal hello world something like that. Let's see if we are able to preview that. And we are. There

we go. And I think that it will also affect dark mode. And you can see how the syntax is visible. Very nice.

And now let's actually uh use this in a file explorer because that's what we have to do next. So we're going to go

ahead and create the file explorer inside. Yes, let me just show you how

inside. Yes, let me just show you how you can import code view. So you don't have to go to index.

Index can be uh used like this. That's

why we named it index. So you can just target code view.

In case this doesn't work for you for any reason, you can just name this properly, name it code view again and then just import that way. And let me

just move it up here. There we go.

So now let's create the file explorer component.

I'm going to go inside of source components file.

Whoops.

File explorer.tsx.

And let's go ahead and let's prepare the imports. Copy check icon and copy icon

imports. Copy check icon and copy icon from lucid react. Use state use memo and use callback as well as fragment from

react.

Hint and button from components. Button

comes from shatzen meaning it has the UI prefix and hint is our custom component which we created in the previous chapter. Now let's import our new code

chapter. Now let's import our new code view which we just created.

And then let's go ahead uh and add resizable with resizable handle panel and panel group. And let's also import everything

group. And let's also import everything we need from breadcrumb. So both of this come from shatnui. So you have them

breadcrumb item list page separator and ellipses.

Now let's go ahead and let's define our file collection.

This is our file collection type. It is

basically a type of record string string. But I like to use this type

string. But I like to use this type simply because it uses the path as the key and then the content here. I think

this kind of visually makes more sense.

I think we did the same thing in our functions path string. Yes, this is exactly how we

path string. Yes, this is exactly how we defined our files here. So I like to do this because I think uh it visually looks better. Great. So now let's go

looks better. Great. So now let's go ahead and first create a function which can extract language from file extension. So get language from

extension. So get language from extension accepts the file name and returns a string. And what we do here is we simply target the extension part and

we take that part and we turn it to lowercase and we default to text if we were unable to do that. So basically if we enter something like app.tsx

we return tsx as the language. As simple

as that.

Perfect. Now let's go ahead and let's create uh a component which we are going to need in order to even render uh which file is currently active. So I'm just

trying to think what is the best way to build this so that you can see the results as soon as possible because there's a lot of components we have to build and I'm just afraid that um we might have to build for a lot of time

without seeing any results. So this is what I will do. We're going to do export const file explorer like this.

Let's create an interface file explorer.

Actually, let's just call this yeah file explorer props because we're going to have many components in this file. So I

want to name this explicitly to be file explorer props. It will accept files

explorer props. It will accept files which are a type of file collection like this.

Then let's go ahead and let's use this and we can destructure the files from here. So now what I want to do is I want

here. So now what I want to do is I want to add the return resizable group resizable panel group like this with the

direction horizontal and then resizable panel with a default size of 30

with a minimum size of 30 and a class name background sidebar.

And inside of here, a paragraph to-do tree view like this.

Then let's go ahead and let's add a resizable handle here with a class name hover background

primary and transition colors like this.

And then let's go ahead and do another resizable banner like this with a default size of 70 and

a minimum size of 50.

And in here what we are going to do is try and do files first in the array.

I'm just thinking of a way. Okay, I know what we can do now.

Let's create a state called selected files.

So const selected file set selected file use state.

And it can either be a string or null.

And let's go ahead and create a function inside of use state to get the file keys by using object keys and pass in the

files. And then return file keys.length

files. And then return file keys.length

length is larger than zero, we can select from the file keys the first in the array otherwise null. So this way we are going to pre-seelelect the first

file we can find. And now that we have this first file, I think it will be a little bit easier for us to build this UI. So inside of this second resizable

UI. So inside of this second resizable panel, check if we have the selected file and if inside of files we can find

this selected file. If we can do that, go ahead and render a div code view like this. Otherwise, let's go ahead and

like this. Otherwise, let's go ahead and render the alternative which is a div which says select a file to view its

content with a class name flex height full items center justify center and text muted

foreground and I think it's time to render this. So

let's go inside of the project view here and let's go ahead and render it instead. So remove code view and check

instead. So remove code view and check if you have active fragment question mark files.

Render the file explorer component and pass in the files to be active fragment.files files as and in here you

fragment.files files as and in here you can choose to use let me just copy from here

this type basically because the JSON type will be any right so we are now marking it as this make sure to import the file explorer and you

can remove the code view now because we're going to use it inside of the file explorer and I think that already just make sure you have a fragment selected here When you click on code, you should

have a to-do tree view and to-do code view. Perfect.

view. Perfect.

And I've had some trouble uh making this work. Oh, looks like it's working just

work. Oh, looks like it's working just fine. Okay, my version had some

fine. Okay, my version had some problems. I think looks like it works just fine nevertheless. Okay, now let's go ahead and let's actually develop the tree view and the code view. So, I'm

going to go back inside of the file explorer here. And I think it might be

explorer here. And I think it might be easier to develop the code view first simply because we already have the code view. So I'm going to go inside of this

view. So I'm going to go inside of this div here and I'm going to add a class name height full width full flex and flex column. And then I'm going to open

flex column. And then I'm going to open a new div. The class name border bottom bg sidebar px4 py2 flex

justify center.

Uh actually it will be justify between items will be center and gap x will be two.

Then let's add to-do breadcrump file breadcrump like this. And then add a hint component

like this. And then add a hint component wrapping our button component and give this a text of copy to clipboard and the

side of bottom.

And for this button right here, give it a variant of outline, a size of icon, a class name of ML auto on click to be an

empty arrow function, and disabled to be false. And inside of here, render the copy icon.

So now, when you click on code, you should have the copy to clipboard button. Just make sure you have both

button. Just make sure you have both button and the hint imported.

Great. And now below this div, we're going to open a new div with a class name flex one overflow auto and render

the code view.

In here, select the code to be files and then selected file. And the language will be oops

the language will use our function get language from extension selected file.

Whoops.

Like this. And by default, you can see that it selected a specific file. Right

now, I'm not able to scroll. We will fix this as well. by uh let's see it has overflow auto but I think that we are

missing uh something obviously because it's preventing us from doing this uh so let's see

everything here seems fine but yes I'm not able to scroll to the bottom here so how about I go inside of the project view here

and to the value code add a class name a minimum height of zero.

And I think that now when you select the fragment, there we go. Now I'm able to scroll in all directions, you should be able to scroll left and right and up and

down. So basically inside of your

down. So basically inside of your project view in the tab content for the value code, add a minimum height of zero. And this will allow you to scroll

zero. And this will allow you to scroll inside of the code preview. So this is now showcasing the very first file it selected. But now we have to build the

selected. But now we have to build the tree view so we actually see the file we selected and so that we can choose between other files. And then we need a

breadcrumb component here to render the current file. So let's go ahead and

current file. So let's go ahead and let's build the tree view next. So I'm

going to prepare that right here in the file explorer right above where we added a to-do to render the tree view. Let's

add tree view like this. And let's give it some props. Data is going to be an empty array. Value will be selected

empty array. Value will be selected file. On select will be an empty

file. On select will be an empty function. And now let's go ahead and

function. And now let's go ahead and let's prepare this things here. So what

I want to do now is the following. Uh I

want to go inside of source lib utils and I will export function convert files to tree items

and I'm going to create a JS doc like this simply so you can see what happens.

So this JS doc is quite useful. I don't

like I mean I don't use it usually but it's useful when I feel like things are not exactly clear. You can see how when you create a comment like this and when you hover over a function it actually

tells you that. So it converts a record of files to a tree structure. So we

accept files which is a record of file paths to content. So this is the input right source button tsx and then some content and this is the output that it

will return. So that's what we are

will return. So that's what we are building now. So let's go ahead and add

building now. So let's go ahead and add some props here. files which will be a type of path which is a type of string and a string

and it will return a tree item. So now

we need to create the tree item object.

So let me just uh do this like so I'm trying to think of a perfect place to add this to.

Um how about in source we just create types ds and let's export type tree item

and that will be a string or an array of string and tree item itself. So it can

reference itself right it can be a deeply nested array.

Now that we have this, we can go back inside of the utils file here. Uh, and

we can set the return method to be the return type to be tree item from the types. And it's going to be an array of

types. And it's going to be an array of those three items and it's going to be an error until we actually return that.

So I want to do this because this way we are certain that we correctly developed this. So let's start by creating an

this. So let's start by creating an interface tree node like this which is basically a key which is a string and

then tree node which is itself inside or null. Then let's define a tree which is

null. Then let's define a tree which is a type of tree node and it's going to be an empty object at first. Now let's go ahead and create sorted paths. So we are

basically sorting the files alphabetically by their path. And then what we're going to do is a for loop. So for const file

path of sorted paths.

Let's go ahead and this uh split the file path into parts by doing file path.split

path.split by a forward slash. The current one will be the current tree which is just the

empty object for now. And then for let index being zero, index being less than the parts length

minus one and index increasing by one for each iteration get the part. So parts and using this

index here if we cannot find the path the part in the current object we need to add it there like this

and then current is equal to current part like this.

And then what we have to do outside of here is add the file or the leaf node.

Right? So const file name is parts parts.length minus one. Current file

parts.length minus one. Current file

name is equal to null.

This will basically indicate that it's a file. This is quite confusing, right?

file. This is quite confusing, right?

But once you see how the file uh structure will look, it will make a little bit uh more sense. Uh okay. So I

think that this uh let me just see.

Yeah. So okay, we just finished this for loop, right? And now we have to uh

loop, right? And now we have to uh convert the node. So let's create an inner function to do that. function

convert node. The node it accepts is a tree node.

We have defined a tree node right here.

And the name is an optional string and it returns back a tree item, an array of tree items or a single tree item

like this. So let's get the entries to

like this. So let's get the entries to be object entries from a specific node.

If entries.length

is equal to zero, return the name or an empty string. Then define the children

empty string. Then define the children to be a tree item like so.

And set it to be an empty array for now.

And then let's go ahead and do for const they structure the key and the value of entries

and do the following. If value

is equal to null that means this is a file. So do

children.push

key.

Else this is a folder. So create a sub tree using convert node and pass in the value and the key. So we need to go deeper. We need to recursively call this

deeper. We need to recursively call this function again until we find a file. If

array is array subtree children dopush open an array of key and spread the

subree.

else children do push key and subtree like so. And then let's go ahead outside

like so. And then let's go ahead outside of this four and return the children like so. And then let's define the

like so. And then let's define the result here to be convert node tree and

return array is array result otherwise result inside of the array like this.

And if you've done it correctly you should have no errors here.

Now I completely understand that this was a very complex task. And if if you are worried that you did it incorrectly, don't worry. Uh I have added my entire

don't worry. Uh I have added my entire utilus files to my public assets. So you

can just find it here and you can copy it from here. For example, I can copy this entire file and I can paste it in here like so. So now I have this convert

node and I have the convert files to three items. Right? So if you want to do a double

Right? So if you want to do a double check or you just want to copy file uh because it's easier, you can do that.

Don't worry. Uh yes, a slight mistake here. My apologies. Uh so don't worry, I

here. My apologies. Uh so don't worry, I will fix I will fix this file so it doesn't use this import because our tree item comes from types.

Yes. So import type three item. Uh I

will fix that instead. I think I can fix it right now.

There we go. Like so.

So when you copy, you won't have that problem. Uh and I will double check by

problem. Uh and I will double check by copying this file again. Pasting it. Uh

oops. Copy it again.

I cannot seem to copy it. Let me try one more time. I think the copy button is

more time. I think the copy button is still using the old one.

Okay. Finally, no errors in the utilus file. Okay. So, now that you have the

file. Okay. So, now that you have the convert files to tree items, we can go back inside of the file explorer.

And in here, uh, we're going to have to create tree data. So, const tree data will be use memo

and return convert files to tree items and pass files here. And in here add files as a dependency. So just make sure you imported our newly created function

here. Uh you can either write write that

here. Uh you can either write write that function yourself as we just did or you can copy it from the source code or from the assets folder. Uh great. Now that we

have this and we have the tree data, let's go ahead and let's add the const handle file select to be use call back.

And inside of the use call back, we're going to check the file path to be a type of string.

And if files file path exists, set selected file to be the file path.

And add files here. There we go.

Perfect. And now inside of the tree view, add the tree data here.

And on select, add handle file select like so. And now it's time to develop

like so. And now it's time to develop the treeview component. So I'm going to go inside of components tree view.tsx.

Let's go ahead and create the interface tree view props which uses the tree item from our types like so. And now let's export const tree

like so. And now let's export const tree view.

Let's assign the props. Tree view props.

In here we get data value end on select.

And in here let's go ahead and let's return a paragraph tree. Actually maybe

we can do JSON stringify data just so we can see what we created. And let's import the

tree view from dot slash treeview.

And if you go back to your app here, select a fragment and click code. There

we go. I have app folder and page.tsx.

So those are for me. But if I go into one of my older ones, well, looks like all of these are pretty simple. So it's

going to keep using just uh a simple example. But if you tell it to build

example. But if you tell it to build something complicated, uh you will have more items here. So this represents a folder and this represents a file. And

now I mean this is basically how the file structure should look like. And now

we're going to use that to properly render the tree view.

So, uh, let's go ahead and let's import everything we need from the sidebar component.

So, that's going to be the sidebar, the content group, group content, menu, menu button, menu item, menu sub, provider, and rail from components UI sidebar. You

already have this when you added Shatsen UI. And now let's go ahead here and

UI. And now let's go ahead here and let's render that. So starting with the sidebar provider

and let's render sidebar with collapsible to be none and class name to be width full and then sidebar content

like so.

inside of the sidebar content. Sidebar

group sidebar group content sidebar menu like that.

So you shouldn't see anything now simply because we didn't add anything in the menu. So now what we have to do is we

menu. So now what we have to do is we have to develop the tree component. So

let's do that below here. Const tree

will have an interface tree props which will accept item which is a type of tree item. It will accept selected value

item. It will accept selected value which will be an optional string or it will be null on select which will be an optional function which accepts the value

which is a type of string and returns a void and parent path which will be a string.

So let's go ahead and add tree props here and the structure the item selected value

on select and parent path like so.

Inside of here, let's first do a destruction of the name and the rest of the items from an array. Array is array

item. If it is, we can render the item.

item. If it is, we can render the item.

Otherwise, put the item in the array.

Then let's get the current path. This

will check if we have the parent path and it's going to render dynamically using backd parent path forward slashname otherwise just the

name of the file. If we don't have any items, uh this means that this is a file. So

let's add it's a file const is selected here will be selected value equals current path

and in here we're going to return sidebar menu button with is active to be is selected and

class name will be data active true background transparent and I'll click Here we'll call on select with a question mark because it can be optional

and pass the current path inside of here. Render the file icon

uh file icon from lucid react. Just make

sure you have it like that. And below

that a span and the name. And this will have a class name of truncate like that. and then go outside of this

like that. and then go outside of this if clause and this means it's a folder and in here

return sidebar menu item with collapsible uh we now have to import the things from collapsible as well. My apologies I

forgot about that. So just add collapsible content and trigger from components UI collapsible. you also have those components.

So, let's go ahead uh down here and instead of the sidebar menu item, open collapsible like that.

Uh let me just fix this collapsible. Give it a class name of

collapsible. Give it a class name of group forward slash collapsible.

and then add the following class name which is a little bit longer and looks weird. So basically open curly brackets

weird. So basically open curly brackets and this entire thing is inside of that curly brackets and and then if data

state is open for this component target the button target the SVG and target the first child and rotate it by 90

and set the default to open inside of here collapsible trigger with the prop as child

Add a sidebar menu button component with the chevron right icon from lucid react and give it a class name of transition transform. So this is the icon that we

transform. So this is the icon that we are going to rotate by 90 once we open this collapsible

and next to it add a folder icon from Lucid React and then a span with the name of the folder and the class name truncate

and then outside of the collapsible trigger use a collapsible content and inside of this a sidebar menu

sub and inside of here go over items.mmap get the sub item and the index and render the tree again.

That's right. We are rendering itself again.

And in here set the key to be index item to be sub item.

Selected value to be selected value.

On select to be on select oops parent path to be current path like that. And that's it for the tree

like that. And that's it for the tree component. What we have to do now is we

component. What we have to do now is we have to actually use the tree component.

And we're going to do that by going back here inside of the sidebar menu and simply do data.m map item and index.

Render the tree component.

Pass in the key to be index. Item will

be item. Selected value will be the value. On select will be on select and

value. On select will be on select and parent path will be an empty string. So

let's see what we didn't use. We didn't

use sidebar rail. So, we forgot that.

So, let's go down here after sidebar content and render sidebar rail.

Now, let's go inside of the file explorer. Oh, we already have tree view.

explorer. Oh, we already have tree view.

So, there we go. Here we have it. And we

can open and close it. Uh, but looks like our select isn't really showing. It

isn't working. So, let's fix that.

Or maybe this is the selected state. I'm

not exactly sure. So I'm going to try and develop something a bit more complicated. So I'm going to try to

complicated. So I'm going to try to prompt it to something to create more files.

Okay. So what I did is I asked it to build a landing page with each part in its own component. And that generated a much better result as you can see. So

what I'm going to do is I'm going to expand this. I'm going to go back to the

expand this. I'm going to go back to the code and yeah, I still am not able to select this. So, let's go ahead and look

select this. So, let's go ahead and look at what we forgot to do. So, okay, these breadcrumbs are unused. That's okay. Uh,

we're going to use them in a moment, but it seems like this handle file select uh is not working properly. So what I'm

going to do is I'm going to first start by adding a console log and rendering the file path.

So I'm going to open my inspect element here and okay. Oh, so it looks app name is being sent. Okay, so

that definitely doesn't exist. Let's go

inside of a tree view and let's see what I did wrong. So in here we have on select parent path

and then the name. Yes, this this doesn't look correct. I think I meant this in the current path.

Let me refresh and check.

Select the fragment check.

There we go. So I'm I'm not sure if you can see. Well, you can definitely see

can see. Well, you can definitely see the code is changing, right? And also

the file is is a little bit bold. So

basically the problem was in the tree view component the current path I hardcoded name when what I should have been doing uh is put it inside of curly

brackets like this.

Excellent. So now that we have this uh the good thing is no more no need for to do anything more in the tree view. Uh

now let's go ahead and let's enable the copy button and let's create the file breadcrumb here. So I want to do the

breadcrumb here. So I want to do the file uh breadcrumb thing first. So I'm

going to go inside of the file explorer.

Um let's go just above it here. So const

file breadcrumb like this.

Let's go ahead and create an interface file breadcrumb props which accepts an individual file path.

Let's extract the file path from here and let's go ahead and get the path segments by a forward slash and let's

limit the maximum number of segments to be four. So if the path goes deeper than

be four. So if the path goes deeper than four segments, we're going to be responsive about it. Now let's do con render breadcrumb items

like this.

And in here if path segments length is lower than or equal than

maximum segments add a comment show all segments if four or less.

So let's return path segments here. Map

segment index const is last will be if the index is equal to path

segments.length minus one and in here

segments.length minus one and in here return a fragment.

Give it a closing tag.

Give the fragment a key of index. The

fragment is imported from React right here. Inside of the fragment, add a

here. Inside of the fragment, add a breadcrumb item.

Check. Oh, let me just fix bread breadcrumb inside check is last if it is render

breadcrumb page and inside the segment and give it a class name of font medium.

If it is not last, we're going to render a span element and segment inside and give the span a class

name text muted foreground.

And then outside of the breadcrumb item, if it is not last, again add a breadcrumb separator like so.

And this was inside of this if. So let's

now add else.

We are going to show the first element and then ellipses if we have more than four of them. So the first segment is what we care about.

Path segments first in the array.

Last segment.

So path segments.length

minus one. So it's only those two that we care about.

And once we get those two, let's simply return a fragment breadcrumb item

a span first segment with a class name text muted foreground. And then let's go

ahead and add the breadcrumb separator and the breadcrumb ellipses like so. So

render the breadcrumb separator and then a breadcrumb item which renders the breadcrumb ellipses. All of these are

breadcrumb ellipses. All of these are imported from the same thing. Then let's

add another breadcrumb item here with a breadcrumb page and render the last segment inside and give this a class name of font

medium.

And finally outside of this function return breadcrumb

and then breadcrumb list and then render breadcrumb items like that.

So a little bit of effort here to create nice and responsive file breadcrumbs.

And now let's go ahead and let's render them. So that's going to be rendered in

them. So that's going to be rendered in the code view.

So there we go. To-do file breadcrumb just above this hint.

Let's render a file breadcrumb.

And let's give it a file path to be selected file.

And there we go. App contact tsx features footer hero navbar. And if you create one that's very deep, it will

show a maximum of four folders before it uses the responsive mode and it will just replace the folders in between with an ellipses which is basically three

dots. Perfect. Now let's implement the

dots. Perfect. Now let's implement the copy feature.

So we already did this before. So we can copy it from that place. Handle copy use call back. If we have the selected file

call back. If we have the selected file right to the navigator clipboard with files selected file and now we have to add the set copied state.

So let me just add it here above the selected file. Copied and set copied.

selected file. Copied and set copied.

Perfect. So now we have handle copy. And

now let's use the handle copy for this button right here. Handle copy. And

let's paste in copied. copied.

If it's copied, we will use the copy check icon.

Otherwise, the copy icon like this. And you should no longer have

like this. And you should no longer have any errors in your code because we are using everything. So when I click copy,

using everything. So when I click copy, it turns into a different icon now. And

you can see that I just copied that entire thing. Amazing. We just developed

entire thing. Amazing. We just developed a super amazing file explorer. So now

I'm going to try to prompt it to create a deeper structure. But this is pretty much it for uh this chapter.

All right. So this time I told it build a landing page with each part in its own component. Use deeply nested folders.

component. Use deeply nested folders.

And you can see that I definitely got that. So inside of the app folder I have

that. So inside of the app folder I have a landing and then I have features and then I have the file. And you can see our breadcrumb in action now. But for

example, you can see that I can break it, right? I if I open this too much, it

it, right? I if I open this too much, it breaks. So what you can do is you can

breaks. So what you can do is you can remove the maximum segments here to be three. And it will be reasonable to

three. And it will be reasonable to change the comment here as well. And

this is how it will look like. Then let

me just refresh. I think the error was because of the hot reload. And let's

click on the fragment here. So let me select features.

Uh okay. So you can see it works. But it

seems like we have some problem here.

Uh, okay. List cannot be a descendant of list. Uh,

list. Uh, okay. It's a hydration error. It's not

okay. It's a hydration error. It's not

exactly too big of a problem, but I'm not um I'm not too sure how to fix that at the top of my mind right now. So, I'm

going to leave it like this. I think

it's not too big of an issue. It's a

small hydration error, but you can see how it looks, right? And if you click on something simpler like page, you can see that it will display the entire thing.

But for something complicated, it will just show you the first and the last segment. So you can decide if you want

segment. So you can decide if you want to show that for three segments or for four segments, right? Whatever makes

sense for you. Amazing, amazing job. So

I think this was uh a much harder chapter, but I think it was worthwhile.

We have an actual uh file explorer now that we can copy files from and explore everything that was created. We can

scroll. Definitely an impressive result.

So now let's go ahead and mark these things as completed and let's open a new branch. So 13 code

view. Let me collapse this. Open the

view. Let me collapse this. Open the

source control. Opening a new branch here.

13 code view. Just to double check that's the chapter's name. Once we are on the new chapter, I'm going to stage all of my changes. 13 code view. I'm

going to commit and I'm going to publish this branch. As always, a reminder,

this branch. As always, a reminder, there's a free code rabbit extension you can use to review your files. And now,

let's go ahead and let's see our pull request. So, I'm going to open this new pull request here. And let's

review our summary.

And here we have the code rabbit summary. We introduced a code viewer

summary. We introduced a code viewer with syntax highlighting and GitHub dark theme. We added a file explorer with

theme. We added a file explorer with three view breadcrumb navigation and a copy to clipboard functionality for code files. We enhanced the project view with

files. We enhanced the project view with tabbed navigation allowing users to switch between a live demo and the code view of project fragments. That is

exactly what we did in this chapter. As

always, a walkthrough of file by file and of course a sequence diagram. So in

here we have this very complicated component which we build the file explorer which renders the tree view and then finally the code view. So in here you can see it generated the entire

sequence diagram for that component along with the prisjs code highlighting.

So very very good. We have a few comments here. Um this one is a good

comments here. Um this one is a good comment. It's basically telling us to

comment. It's basically telling us to also add an actual type of check. The

reason it's telling us this because uh it could be anything but then at the same time we know that it's always going to be an object. It's going to be a type

of JSON, right? So, I think this is a little bit redundant.

Adding it would not hurt, but I think it's okay the way it is right now. I'm

not sure about this change. I think uh it works just fine like this. And in

here, I accidentally added two semicolons in the file explorer props.

So, yes, of course, we can remove that.

I'm going to do that in the next chapter.

And there we go. Let's merge this pull request and let's go back here and let's go ahead and go back to main and let's synchronize our changes again.

And once you've done that, go inside of source control graph and just confirm that you merged the 13 successfully.

Amazing, amazing job. That marks the end of this chapter and see you in the next one.

In this chapter, we're going to develop the homepage. This will include creating

the homepage. This will include creating the home layout, the homepage component which consists of project form and project list. Let's go ahead and let's

project list. Let's go ahead and let's start our app. Running npm rundev in one terminal and starting in justestdev server in the other. And now let's go

ahead and make sure we are on the main branch. And you can click on synchronize

branch. And you can click on synchronize changes just to make sure everything is up to date inside of your source control. The last merge should be number

control. The last merge should be number 13.

Now, let's go ahead and let's fix one thing that's been bothering me. So,

right now, uh I'm loading my previous project here. You can see that when the

project here. You can see that when the project loads, no fragment is selected.

This is because in the messages container here, we commented out this use effect which selects the last fragment because it was causing problems. You can see that when I enable

this, then it works. This is selected.

But it's annoying because if I want to select this one and look at it, you can see that it automatically moves it after 5 seconds. Why after 5 seconds? Because

5 seconds. Why after 5 seconds? Because

we refetch every 5 seconds. So this use effect can obviously be improved. So

let's go ahead and make it a little bit simpler. I'm going to go ahead and

simpler. I'm going to go ahead and remove everything in here for now. And

I'm going to start by adding a new ref right below the bottom ref. add last

assistant message ID ref which can be a simple use ref of a type of string.

And now inside of this use effect here let's go ahead and find the last assistant message. So last assistant

assistant message. So last assistant message we'll use messages find last and then simply find the message whose role

is assistant. So messages.findlast find

is assistant. So messages.findlast find

the last API is that we are using and then in here we're going to open an if clause and what we are going to do is we're

going to check if last assistant message fragment exists and if last assistant message do ID is not identical to last

assistant message id refer.

Let me just remove this. So if that's the case only then are we going to call set active fragment and do last assistant message. fragment and then we

assistant message. fragment and then we have to update last assistant message id ref.curren to be last assistant message

ref.curren to be last assistant message id and this way we won't have any unnecessary updates and we can remove this to-do here. So let's go ahead and

do a refresh again and there we go. So

you can see how it selects the fragment the last assistant message it it can find. But if I manually select this one

find. But if I manually select this one let's wait for 5 seconds and you can see that nothing will change it right simply

because this last assistant message ID ref is stored. So the only time that we are going to override users selection is

if an actual new message arrives. I

think that's an okay UX. If you want to, you can uh improve this logic even further by creating two separate states.

One for the automatic selection of the active fragment and one for the user selection of the active fragment. And

then you can overrule one over the other if that's something you prefer. Because

you can see now nothing can change the fact that this fragment is active unless I do build a yellow landing page.

So if I add this still nothing is happening. I'm still uh having this

happening. I'm still uh having this older fragment as selected. Only after

uh this finally responds with some new content will the new fragment be automatically selected because it is

constantly looking for the last new assistant message. So, our message

assistant message. So, our message wasn't able to trigger that use effect.

And if it simply calls the refetch request and it receives the exact same messages, we compare the last message ID

with our ref ID. And if it's the same, no, we don't change anything. And there

we go. You can see how it works. I

didn't change anything. It just

generated a new landing page and it selected that fragment. That is the exact behavior we hoped for. Amazing.

So, what I want to do now, which you know, you can choose if you want to or not. I just want to remove this handle

not. I just want to remove this handle from here. I don't like it. So, this is

from here. I don't like it. So, this is what I'm going to do. I'm going to open both the project view and I'm going to open the file explorer. And the only thing I want to do in the file explorer

is copy the class name from the resize handle. And then I'm going to go inside

handle. And then I'm going to go inside of the project view. I'm going to find the resizable handle here. remove the

prop with handle and just paste the class name here. And there we go. Now I

have this type of resizable. And there

seems to be some kind of problem. You

can see when when you have two resizables active, you can only move one of them, right? So you can't move this one. I'm going to explore at the end of

one. I'm going to explore at the end of the tutorial if that's something we can fix. There might be some solution, but

fix. There might be some solution, but you know, it's it's not too big of an issue. Great. So now let's go ahead and

issue. Great. So now let's go ahead and let's actually uh build the homepage.

So we're going to go and do the following. Inside of your source app

following. Inside of your source app folder, create a new folder home. This

is a route group. This will not be a part of the URL, but it can hold things like layouts. So let's go ahead and

like layouts. So let's go ahead and build a simple home layout here. The

first thing we have to do in a layout is create props which hold the children.

And then we have to do a default export like this. And in here we assign the

like this. And in here we assign the props. And we extract the children. And

props. And we extract the children. And

then inside of here, let's add instead of div, let's add main.

And let's give the main a class name of flex. Flex column, minimum height of

flex. Flex column, minimum height of screen, and the maximum height of screen. Like so. And in here, let's add

screen. Like so. And in here, let's add a div with a class name flex one flex

flex column ex four and padding bottom of four. And inside render the children.

of four. And inside render the children.

And then what I want you to do once you have this layout tsx it is important that this is called layout. This is a reserved file name just like a page,

right? So, it's important that you use

right? So, it's important that you use layout and it's important that you do a default export here. What I want you to do now is I want you to uh move the page

tsx from the app folder, the global one, and drag it inside of the home folder.

So, move it inside. And sometimes this can trigger some unsaved files. So, if

you get any unsaved files here, you can just close them. And if it asks you if you want to save it or not, you can just click yes. If nothing happened, you can

click yes. If nothing happened, you can just continue. What basically happens if

just continue. What basically happens if that does happen to you is cache, right?

The hot reload is currently active. So

sometimes the cache inside of this folder gets confused when you move a page that's currently uh active. So what

did we do now? Well, if you go and click back to the dashboard, nothing changes, right? That's because what we just did

right? That's because what we just did is we created a layout for all of our uh homebased pages. Right now, this doesn't

homebased pages. Right now, this doesn't make too much sense because we only have one page, the homepage, right? But later

in here, we're also going to have pricing and we're also going to have login forms. So that's why instead of copying this code every single time into

each page, we're just going to uh create a nice little uh reusable layout like so. Now in here, let's go ahead and

so. Now in here, let's go ahead and let's do the following. I want to create a uh self-closing div like so. and give

it a class name of absolute inset zero minus Z 10 height of full width of full

bg background on dark use bg radial-ashgradient like so and then inside of here write 39

9 so 3 93 E48_1 pixel comma transparent

and then underscore one pixel. So this

is all one class name dark background radial gradient transparent right. So

all of this is one class name. What's

important is that when you hover over this if you have the tailwind extension you should see the underlying CSS. If

you accidentally add space somewhere that breaks the class. You can see how now it's not working. So just be careful. Don't add any spaces. I mean,

careful. Don't add any spaces. I mean,

this is not important. This is just for a cool effect you're going to see in a second. Uh, and now what I want you to

second. Uh, and now what I want you to do is I want you to copy this again, paste it, but without the dark prefix here. And you're going to change the

here. And you're going to change the color of this to not be this one, but instead be da d2.

And this can still be transparent. And

then just add another one. background

dash size 16 pixels underscore 16 pixels. And now you will see a bunch of

pixels. And now you will see a bunch of dots all over your page. So now let's go ahead and let's actually develop this.

So I'm going to go back inside of my homepage right here and we're going to do the following. I'm going to remove all of these things here because we're

not going to need any of them.

I'm going to well I'm just going to clean the entire thing. I don't even need use client here. I'm going to open a div and I will add a class name here

flex flex column maximum width of five excel maximum width of auto and width

full. I will then add a section with a

full. I will then add a section with a class name space Y6 py

of 16 pixels. My apologies 16 VH to Excel will be py 48.

Now inside of here I will add a div with a class name flex flex column and items center.

In here, we're going to render an image from next image with the source of logo

SVG out of vibe width of 50, height of 50, class name of hidden MD block.

Outside of this div encapsulating that image, I will add an H1 build something with vibe or the name of your project.

And we're going to put this heading give this heading class name text to Excel medium text 5 Excel font bold and text

center. And you should already be seeing

center. And you should already be seeing something here. Now below this heading

something here. Now below this heading add a paragraph create apps and websites

by chatting with AI and give this a class name of text large medium text extra large text muted foreground and

text center.

There we go.

And now below that add a div with a class name maximum width of 3 Excel MX auto and width full and nothing will

appear now. That's because we have to

appear now. That's because we have to create a new component called project form. Now the cool thing about project

form. Now the cool thing about project form is that you already built this. You

just don't know it. So what we're going to do is we're going to reuse one component that we already have and we're going to go inside of modules projects

UI components and in here we have the message form. Now technically we could

message form. Now technically we could modify this message form with a prop.

You know I could just pass a prop here like is homepage is landing page and then we could modify the CSS. But

honestly, I would rather keep components separate than creating this magical components which can be used a million times, right? I'm okay with copying my

times, right? I'm okay with copying my code if it's for one, two, three instances. I'm I would rather do that

instances. I'm I would rather do that than creating this ambiguous abstract code that's impossible to keep track of.

Right? So, this is what I'm going to do instead. I will copy that message form

instead. I will copy that message form and I'm going to go ahead inside of modules and I will create home module.

And inside of here UI and then components and then in here I will create project form.tsx.

And then I will copy everything inside of the projects UI components message form and I will paste it here like this.

And then I will remove the props because we don't need them. And this will now be called project form. There will be no props for this. the value will still be

the same but it will not be creating a message. It will be creating the

message. It will be creating the project. So this will be called create

project. So this will be called create project.

So let's go ahead and see what we have to do. We will reset the form. Uh

to do. We will reset the form. Uh

actually we don't have to reset the form and I'll tell you why. Because on

success we're going to uh reinvalidate anyway. So let's go ahead and do this.

anyway. So let's go ahead and do this.

After we reset the form, the only thing we should actually oh I'm sorry after we successfully create a project, the only thing we should do is we should call PRPC.pro

PRPC.pro and we should just refetch get many.

That's the only thing that should happen and then also we should invalidate the usage status. So we can leave this to do

usage status. So we can leave this to do and the same thing for this. But also

one more thing that should happen here is that we add router use router from next navigation. Let me just move this

next navigation. Let me just move this here.

When you successfully do this, let's do router.push

router.push and we push to the newly created project. So that's going to be forward

project. So that's going to be forward slash projects data id. As always, we have this data because

id. As always, we have this data because in the projects create procedure here when we create the new project and then we invoke a background job, we return

that new created project. So we have access to it right here. Great. So on

submit we'll be calling create project and we don't need the project ID here at all. For the is pending we will have

all. For the is pending we will have create project is pending like so we can remove the show usage uh from here

entirely. We don't need it on the

entirely. We don't need it on the homepage. So you can remove this show

homepage. So you can remove this show usage here like so. Uh what would you like to build can stay the same to submit? to submit. Honestly, I think

submit? to submit. Honestly, I think everything else here works just fine.

So, yes, just a slight modification here. And now, let's go ahead and use it

here. And now, let's go ahead and use it inside of our app homepage.tsx.

Let's import project form from modules UI components project form. And I think we also need to add use client here because it's imported in a server component. So, it wouldn't work. And

component. So, it wouldn't work. And

there we go. This is how it's going to look like and you can already try it. So, build a landing page. I like to use this example

landing page. I like to use this example because I think it's super simple and works almost every time. And there we go. You can see what happens. So, from

go. You can see what happens. So, from

the landing page, we create a new project with build a landing page initial message. Perfect. So, if you

initial message. Perfect. So, if you want to, you can wait for the result.

Uh, I know it's very fun to always see the results. So I I completely

the results. So I I completely understand if you want to, but I'm going to go back to the project form. And what

I'm going to do now is I'm going to show you how you can create some predefined prompts for the users so that they can easily click on them here. So it's so that they can see the results faster. So

this is what we're going to do. We're

going to do this inside of the project form. So in here,

form. So in here, go outside of this native form elements and create a div

with a class name. flex wrap justify center gap 2 hidden MD plex and a maximum width of 3XL.

And now in here what you should do is you should create something called project templates.

So you can go inside of the public assets folder which you can see the link for on the screen or you can use the link in the description and in here you can find uh constants.ts. TS. And in

here, I just created a bunch of project templates for you. Uh, and you're going to have to, you know, test each of these out depending on the model you will use and what works for you and what doesn't

because it's a good idea to showcase your project templates on something that you know will always work with your AI model. Right? So, I'm going to put this

model. Right? So, I'm going to put this inside of home. I will create new constants. DS and I will paste that

constants. DS and I will paste that here. So basically something like build

here. So basically something like build a Spotify clone, build an Airbnb clone, build a store page, YouTube clone, file manager. And I'm just using very

manager. And I'm just using very descriptive prompts here because it will work better if you give it a good description. But the cool thing is that

description. But the cool thing is that you know you have full freedom to improve the prompt in any way. In here

when I select build a Netflix clone, the full prompt will be build a Netflix style homepage with a hero banner. uh

use a nice dark mode compatible gradient here. Movie sections, responsive card,

here. Movie sections, responsive card, and a model for viewing details using mock data in local state, use dark mode, right? So, it's a very descriptive uh

right? So, it's a very descriptive uh prompt. But depending on what model you

prompt. But depending on what model you use, you might be able to do it with just build a Netflix clone, right? It

will just depend on the prompt that you're using and the model that you're using. For example, Claude Sonet

using. For example, Claude Sonet understands uh your instructions very very well. But with OpenAI, I sometimes

very well. But with OpenAI, I sometimes have to tell it, you know, if you're using dark mode, make sure you use Next themes because you have Shhatzen

installed, right? I have to tell it uh a

installed, right? I have to tell it uh a more in-depth about what's going on. So,

make sure you have this project templates. And now what you're going to

templates. And now what you're going to do is you're going to iterate over them.

So, project templates, which I've just imported from dot dot /constants here, dom. And for each template,

dom. And for each template, I'm going to return a button component, I'm going to give the button a key of template dot title. And then I'm going

to add some additional attributes to the buttons. So variant of each will be

buttons. So variant of each will be outline. Size will be small. Class name

outline. Size will be small. Class name

will be background white and dark background sidebar.

On click here on select will be called which we don't have yet. So let's just leave it as

have yet. So let's just leave it as empty. And then let's put template

empty. And then let's put template emoji. And let's put template title.

emoji. And let's put template title.

And let's see that now. And there we go.

So you can see that now beneath this big input bar uh you can select any of these. So let's go ahead now uh and just

these. So let's go ahead now uh and just properly space these things out. So what I want to do is I want to wrap my form inside

of a section with a class name space Y six like so. and just encapsulate all

the way to here like that. And then you can indent the entire thing and now you have a nice space in between. And now we have to create the

between. And now we have to create the ability to actually select this. So for

this I'm going to add const on select content string form set value content or whatever you

use. Let's see. So we use uh value is

use. Let's see. So we use uh value is the one we use. So set value to be content or you know you can just put

value here. A lot of value. Uh and

value here. A lot of value. Uh and

what's important you do is you enable all three should to true should validate to true and should touch to true. This

will basically simulate it to be in the same state as if the user actually typed this. So now what you have to do is you

this. So now what you have to do is you have to add the on select to the buttons. So call on select and pass in

buttons. So call on select and pass in the template.prompt

the template.prompt like so. So now when you click on build

like so. So now when you click on build an admin dashboard there we go you can go ahead and run this. So I suggest that you try running this and also keep in

mind some of these are larger tasks so they might actually time out right. So

be mindful of that. The good thing about uh ingest is that if it notices a rate limit, it won't retry immediately. It

will it will retry with exponentially longer pauses between each retry, which if you're using Open AI is perfect because Open AI has reasonable timeouts.

So when you hit a limit in Open AI, they punish you with like 2 seconds of waiting time. So inest will wait for

waiting time. So inest will wait for even longer than that. And if it happens again, it will wait for even longer. So

you don't have to worry. Ingest and OP and I are quite a good combination. Uh

and you can see that with this longer prompt right where I told it, let me just see create an admin dashboard with stat cards, placeholder, all of those things, blah blah blah. And here it

takes a bit of a longer time. You can

see almost a minute. But as I said, you can speed these things up by using a different model. You can create a

different model. You can create a smoother prompt, right? A lot of things you can do. So let's just see this result. I'm very curious if it will work

result. I'm very curious if it will work or not. And there we go. So almost the

or not. And there we go. So almost the exact same thing as we saw uh in the initial demo. Amazing. And you can see

initial demo. Amazing. And you can see the code here. Very very good. So I

would suggest that you, you know, try a couple of these and if some are obviously failing, well, you can try and, you know, fix them in the prompt or you can simply replace them with something simpler because if you're

actually building this as a business, it's a good idea that you, you know, allow the user to select something that will 100% work, right? You don't want to give them something that might work or

might fail, right? Perfect. And I'm just super interested. Let me just go back

super interested. Let me just go back here. I want to change this to dark

here. I want to change this to dark mode. I want to see how this looks like.

mode. I want to see how this looks like.

Looks pretty good. Great. Uh but I actually prefer working in light mode.

So let's go ahead now and let's develop the bottom part which is the project list. So so far we created the project

list. So so far we created the project form and the layout. Now let's create the project list. In order to do that, we have to go back to our page dsx where

we render the project form and we have to render the project list outside of this section. So project projects list

this section. So project projects list like this.

And then let's go inside of our home modules here. So home UI components

modules here. So home UI components projects list.tsx.

projects list.tsx.

Let's mark this as use client. And let's

import everything we need here. So link

from next link and image from next image format distance to now from date FNS

use query from tanstack react query and use tRPC from TRPC client and button from components UI button. Let's export

cons projects list here and let's start by defining the RPC.

Then let's define data projects to be use query DRPC projects get many query options

like that and then in here let's return a div with a class name full width background color of white dark

background color sidebar rounded extra large adding eight border flex X flex

column gap Y 6 SM gap Y4.

Then let's add an H2 element which will just say previous vibes or saved vibes.

I thought it would be fun to call old projects vibes because the project name is vibe, right? You can of course just say old project, saved projects,

whatever you want. So text to Excel and font semi bold. Later this will say Antonio's vibes or whoever is logged in.

But since we don't have out yet, we can't display that just yet. So now

let's just import the projects list simply so we can start seeing the progress. So right here at the bottom

progress. So right here at the bottom you should see saved vibes right here.

It should look like this.

So now let's go ahead below this and let's create a div with a class name of grid grid columns 1 SM grid columns

three and gap of six and then in here check if projects.length

this should be a question mark so if projects.length length is equal to zero.

projects.length length is equal to zero.

In that case, let's display a div with a class name all span full and text center. And inside a paragraph, no

center. And inside a paragraph, no projects found and a class name text small and text

muted foreground.

Otherwise, let's do projects do map get the individual project here and then return a button.

Give this button a key of project ID, a variant of outline, and a class name font normal, height auto, justify start,

full width, text start, and the padding of four, and give it an as child prop. Then go ahead

and add a link here with a dynamic href forward slash projects project.

ID and then inside of here create a div with a class name flex item center and

gap x of four. Then add an image here with a source of logo SVG out of vibe

width of 32 height of 32 and the class name object contain. Below the image, add a new div with a class name flex and

flex column.

Inside of that div they have an H3 element with project.name name inside

with project.name name inside and give the H3 element a class name of truncate and font medium and below it a

paragraph using format distance to now which we imported from date FNS project updated at

add suffix true and give the paragraph a class name text small and text muted foreground and that is it. So in here now you can

see all of your previous vibes. So you

can go ahead and visit them. And in here the source code is of course preserved.

Great. So I believe that that marks the end of this chapter where the goal was to build uh a the goal was to build uh a landing page

and we added the templates, we added the project list. We added the ability to

project list. We added the ability to you know look at this older projects and I think we did an amazing amazing job here. Obviously there are some things

here. Obviously there are some things still missing like the navbar but we will do that later when we add authentication.

So, what I want to do in the next chapter is I actually want to improve the theme of this project because my original theme in the demo was some kind of yellowish color. So, I'm going to show you how I modify the theme to make

it look like that. And I'm super interested in the result of this. So,

I'm just going to wait. Uh hopefully it will work. If not, you know, it's just a

will work. If not, you know, it's just a lesson that these AI models are a bit undeterministic. You can't really rely

undeterministic. You can't really rely on them too much. But if you spend you know more than uh I built this app in a span of a month right so I couldn't

really spend too much time learning proper prompt engineering but if you actually use this for your business you are most certainly going to spend a lot of time on this and you will learn

prompt engineering and you will learn how to improve the prompt and how to fix this little mistakes because in comparison to what you've just built an

app failing is really not a big issue.

You can learn how to speed it up. You

can use a new model. Uh you can spend, you know, more credits. You can

basically do a billion solutions, but the boilerplate is here and it's working.

So, for example, you can see that I've gotten an error for this file manager.

You might not get an error. Again, it's

a very simple fix. It forgot to add use client to the top of the file. We can

see that in the file grid, it was supposed to add use client, but it didn't. right? Or it should have added

didn't. right? Or it should have added it to the page. So perhaps this can be a very very easy fix. You know, you can maybe tell it inside of the prompt right

here. You can somewhere add a rule that

here. You can somewhere add a rule that it must add use client.

How about this? Let's add

always add use client to the top of page tsx.

So because we are not expecting this to make any API calls right. So then I can maybe remove this and I can just extend

it and any other relevant files which use browser APIs or react hooks use effect. Okay, I

won't add too many tokens now. But for

example, you can do things like this.

And I think that already uh this should work much much better. And I purposely want to retry it now just to see if that will fix. I'm trying to teach you that

will fix. I'm trying to teach you that you know you don't have to use this prompt. You can make your own prompt.

prompt. You can make your own prompt.

Like I built this prompt and I have no idea about prompt engineering. I just

started very simple and then I extended and I extended and I extended. Right? So

I just added this file safety rule to always add use client at the top of page.tsx

page.tsx simply because uh if it does that it doesn't have to worry about adding it to the other places. So let's see if this will fix the problem or maybe some new

problem will arrive.

And finally I managed to get it to work.

So this was very funny. It actually

failed uh again, right? It forgot to add use client again. But look at this. It

added it but it didn't add it at the top of the file. So you can see how funny these AI models are. Sometimes you will lose your mind trying to tell it to do something right. So this is what I did.

something right. So this is what I did.

I modified this always add use client to the top the first line of app page tsx.

So this way it understood me and it did an interesting thing this time. You can

see that it understood what I wanted now and also it decided to create a whole new separate file where it created everything. I'm not sure why it needed

everything. I'm not sure why it needed to do that. Um, but let's see what it created because I think that this is very interesting actually. Can I rename

this? Okay, I can't do that. Oh, I can.

this? Okay, I can't do that. Oh, I can.

One, two, three. Save. Oh, it works. I

can rename. I can delete. Or can I? I

can.

This is actually super impressive. Can I

delete entire folders? Looks like

something's wrong with the models. Keep

in mind that sometimes the problems aren't in code, but the problems are in the iframe, right? Sometimes you might have to visit a live example. Wow, this

is actually a very very nice example of a file manager. But yeah, you can see that I had to struggle a bit with this, right? I got a very good result in the

right? I got a very good result in the end. But you know, the prompt can always

end. But you know, the prompt can always be better. Again, I'm not a prompt

be better. Again, I'm not a prompt engineer. I have no idea what I'm doing

engineer. I have no idea what I'm doing when it comes to prompt engineering. So,

spend some time learning that and you will get even better results than uh what I am in this tutorial. But I still managed to get extremely impressive results.

Great. So I believe that that marks the end of this chapter now. So 14 homepage.

Let's go ahead and close everything here. And I will go and create a new

here. And I will go and create a new branch. 14 homepage

branch. 14 homepage like so.

I'm going to stage all of my changes and I will create a commit. 14 homepage. I

will commit and I will publish my branch.

Perfect. Now, let's go ahead and open a new pull request here.

And let's create a pull request.

And let's wait for the summary to arrive.

And here we have the summary. We

introduced a new homepage layout with a visually enhanced background and responsive design. We added project

responsive design. We added project creation form with template section, validation, and keyboard shortcut support. Basically, a copy of our

support. Basically, a copy of our message form, right? We implemented

project list view showing saved projects with quick navigation and relative timestamps. We provided a set of

timestamps. We provided a set of predefined project templates for faster project setup. We also fixed the

project setup. We also fixed the fragment handling uh to prevent repeated state updates. Exactly. Uh and we also

state updates. Exactly. Uh and we also updated the resizable handle styling for a smoother and more interactive user experience. And it also detected our

experience. And it also detected our prompt change where we clarified the requirement for the use client directive in relevant files. Excellent. So in here

as always we have a more in-depth walk through. In here we have a sequence

through. In here we have a sequence diagram explaining exactly how all of those things happen. And in here we have some comments. So it suggests adding

some comments. So it suggests adding some loading states here in the project list. Uh we could very much do that. We

list. Uh we could very much do that. We

could even leverage our pre-fetching and suspense. We'll see how we're going to

suspense. We'll see how we're going to handle that later. And in here, it suggests also adding is dirty check. I'm

not sure if we need that. I think I completed the project without it. So, I

think we don't need this. So, I'm going to merge this pull request here. And

after I've done that, I'm going to go back inside of the main branch. And I'm

going to click on synchronize changes.

And after that my graph here will update and it will show me that pull request 14 uh was just merged. Amazing amazing job.

I believe that marks the end of this chapter and see you in the next one.

In this chapter we're going to learn how to change the theme of our project and I'm going to show you two ways you can do that. The first one is to simply

do that. The first one is to simply visit my public assets folder or if you have access you can use the source code.

Basically just visit the link you can see on the screen or the link in the description and from in here you can find globals.css

find globals.css and in here you can click copy or you can you know manually select and copy things and then go inside of your source

app globals.css

app globals.css ensure that you are on your main branch and you can synchronize changes if you aren't sure. Make sure that the last

aren't sure. Make sure that the last merge was 14 and simply replace the entire globals CSS file. So in here uh

alongside changing all the colors, this will update as well. This is basically what enables button to have a cursor pointer just in case you were wondering.

So this is new and basically the colors were modified. And if you take a look at

were modified. And if you take a look at your app now, you will see that we have this new orange color. And if you go to the dashboard, you will see that it's

more of a yellowish color. So this is the one that I like. But I want to show you exact place where I found this and

how you can create your own uh CSS theme. So for now, what I'm going to do

theme. So for now, what I'm going to do is I'm just going to revert this simply so it is the old global CSS. You don't

have to, right? If you like the theme, you can copy from my uh GitHub assets, you can use it. But let me show you how I even found that theme. I basically

used tweak cn.com. Again, you can use the link in the description or the link you can see on the screen. And in here, you can go inside of try it now. And you

can basically click here and find a bunch of different themes for UI. And I

think it is super cool. And the one I selected was cloth, right? And in here you can check how it looks in light mode and how it looks in dark mode. I think

this is an amazing project. It has so many themes you can try from. So I

purposely want to pick uh some theme that I haven't tried before. Let's see

how about I try this claymorphism. So

the way you would do it is you would click on code and in here you can see that they are taking care of Tailwind versions right so I would take Tailwind

version 4 and if you want to you can just use the CLI to do it but you can also just copy this and then you will

have to replace your root your dark and theme inline. So let me show you how you would do that. So

starting from the theme inline root and dark, we select all of these and you can remove them. So this is how the global

remove them. So this is how the global CSS looks now. And you just paste the new one here. And that changes the entire look of your app. As you can see, it looks very different now. Right? So

if you like this one, you can use this one. Right? I personally like the look

one. Right? I personally like the look of Claude. So I'm going to select Claude

of Claude. So I'm going to select Claude right here.

code uh code and I will click copy and then the same thing you basically select the root the dark and theme inline and

you can delete it. So this is how it should look like and paste your new ones here and then your app should look like this. I think this is a very very nice

this. I think this is a very very nice look. Uh and it has nice borders.

look. Uh and it has nice borders.

Everything just looks nice with this style. Again, I don't know how well this

style. Again, I don't know how well this website will be maintained. I don't know if this will be available, you know, 2 years from now. I hope it will because it's an amazing project. But then again,

Shhatzen can update a lot and they will probably update the app accordingly to that. So, because of that, I am offering

that. So, because of that, I am offering you my globals.css

which you can copy from the source code or the global assets and just paste the entire globals.css inside. And if you're

entire globals.css inside. And if you're using this my globals.css, you will notice that now buttons have cursor pointers. They look clickable, right?

pointers. They look clickable, right?

Each of these buttons look clickable.

That is because of this part. Let me

show you this one. So if you don't have this u it will not come with uh tweak CN. So it doesn't come with this. I

CN. So it doesn't come with this. I

added this myself in my global CSS. So

basically this is a way to enable cursor pointer for all buttons which are not disabled. So you can add this little

disabled. So you can add this little snippet if you want to and then your buttons will have proper uh cursor pointers. I just think that this looks

pointers. I just think that this looks way better than everything than anything else. Right now let me try and go to one

else. Right now let me try and go to one of my previous projects where I have a lot of fragments. You can see how now fragments look clickable. Right? They

have a proper cursor on them. Great. I

am super satisfied with this one. So, I

will leave it at this. And while we are here, there is just one more thing I want to do. So, just go in any of your projects and let's go ahead and do the following. Select your theme. I would

following. Select your theme. I would

recommend using my global CSS and then later at the end of the project, you can modify it to whatever theme you like, but it will be easier for you to have the exact same result as me. So, that's

why you can use my global.css. Keep in

mind that this is for let me show you next version for next 15.3.4 four, right? So, if you are watching this two

right? So, if you are watching this two years from now, I have no idea if it will work for you and whatever is the latest version, but if you're using a similar version like me, uh or if you're

using the exact version as me, it will work. Great. So, now let's go ahead and

work. Great. So, now let's go ahead and do one more thing. Let's go inside of our button inside of source components UI button. And in here, I want to add a

UI button. And in here, I want to add a new variant called uh terriiary.

I don't know how to pronounce this to be honest. I never I always mess this up.

honest. I never I always mess this up.

But basically, it's going to be background primary with 25% opacity. In

dark mode is going to be BG primary again, but 30% opacity. Text will be primary. Shadow will be extra small.

primary. Shadow will be extra small.

Hover will be BG primary 20.

BG primary 20. And on dark mode, hover will be BG uh let me just check

on dark mode hover will be BG primary 25%. And now go inside of your project

25%. And now go inside of your project view here and in these tabs find the upgrade button and give it a variant of this

new one. However you pronounce this,

new one. However you pronounce this, right?

And then when you look at it, it will look like this. It's a kind of uh lighter version. And let me switch to

lighter version. And let me switch to dark mode and still looks good. And of

course, check your app in dark mode to see it looks fine. I very much like this look of the app more than all the other themes, but you're of course free to

choose your own. And I like how this becomes orange now when you resize your panels. Uh great. So this was a very

panels. Uh great. So this was a very very easy chapter. So let's go ahead and just quickly merge this. So mark this as

complete and 15 theme. I'm going to open a new branch here. Let me just see what did we all change. We'll change this to 30 this new variant here. And we changed

our theme overall. So I'm going to create a new branch 15 theme. I'm going to add all of the

15 theme. I'm going to add all of the changes. 15 theme. I'm going to commit

changes. 15 theme. I'm going to commit and I'm going to publish the branch. And

there's really no need for any review because this was a super simple uh change. So I'm going to immediately

change. So I'm going to immediately merge this pull request. So we speed things up.

There we go.

So just three simple changes and we can immediately merge it. We don't need to wait for any review this time. It's much

simpler. And after that's done, let's go inside of main and let's synchronize our changes. And then your last merge here

changes. And then your last merge here should be 15 theme. Amazing amazing job and see you in the next chapter.

In this chapter, we're going to add authentication to our project. This will

include creating a clerk account, setting up clerk, creating the necessary components to display the authenticated state, creating protected TRPC

procedures, and updating the Prisma schema. Let's start by creating the

schema. Let's start by creating the clerk account. You can use the link you

clerk account. You can use the link you can see on the screen or the link in the description.

And once you get to the landing page, you might see something interesting in here where they mention the companies that use clerk. You can actually find

injust the company that we are using for our background jaws. And you can confirm that yourself by going in their signin screen and searching for clerk inside of their network tab. And in here you can

see that they are actually making requests for clerk. And I just think it is super interesting that such a uh great company uses the same authentication system that we are going

to implement in our project right now.

So let's go ahead and do that. Once you

create your account here, you will be redirected to the dashboard. And in here you can click create application. I'm

going to call this application Vibe. And

I'm going to enable email and Google.

You can of course enable all of these other providers if you want to. And I

will click create application.

After we do that, we have to install the Nex.js clerk package. But just before you do that, ensure that you are on your main branch. Ensure that your last

main branch. Ensure that your last change was 15 theme and that you have synchronized all of your changes.

Now let's go ahead and let's run npm install lurk nex.js. And once it's been installed, I'm going to show you the exact version that I will be using, 6.23.0.

Now that we have that, let's go ahead and add the environment variables to our environment file. So, I'm going to go

environment file. So, I'm going to go ahead and add clerk and paste these two.

I like to wrap them in parenthesis, but I think this might depend on the system.

I think Windows might have problems with this. Uh, but I think maybe even not. I

this. Uh, but I think maybe even not. I

think all of these will work just fine.

But yeah, in case you were wondering, I like to wrap them in parenthesis. They

don't have to be in parenthesis. So, all

of these could actually be without parenthesis if that's something you prefer. I just feel like the syntax

prefer. I just feel like the syntax looks better with parenthesis.

Uh, I keep saying parenthesis, I mean quotes. Sorry. Uh, okay. Now, let's

quotes. Sorry. Uh, okay. Now, let's

create our middleware file. So, that's

going to be inside of the source folder.

Create middleware.d DS. Make sure to not misspell this. Middleware.ds.

misspell this. Middleware.ds.

It's a reserved file name. We import

clerk middleware from clerk next.js server and we export default clerk middleware middleware and we add a matcher. So, uh, we target all of these

matcher. So, uh, we target all of these files here.

Excellent. So, now that we have this, let's go ahead and let's add the clerk provider to our layout. So, I'm just going to import clerk provider

to our root layout. So app folder layout. Let's go ahead and import

layout. Let's go ahead and import clerk provider from add clerk next.js and I'm going to wrap the entire

application inside of a clerk provider.

Yes, make sure you wrap your TRPC React provider inside of clerk provider as well. Like that. So let me just confirm

well. Like that. So let me just confirm that I use the correct package and just confirm that they've done this as well.

Perfect. And now let's go ahead and let's do npm rundev here. You don't need to start your uh ingest right now because we will be doing some other

things. So what I did is I went to the

things. So what I did is I went to the end here and I clicked on next steps.

Utilize your own pages for authentication. The account portal is

authentication. The account portal is the fastest way to add authentication.

So let's click continue to the next GS guide. And the first thing we're going

guide. And the first thing we're going to do is we're going to add this uh sign in pages. So let's go ahead and do that.

in pages. So let's go ahead and do that.

I'm going to go inside of source app home here. And now I'm going to create a

home here. And now I'm going to create a new folder called sign in. And then

inside I will create another folder which will use the catch all route sign in like this. It needs to be exactly

like this. And then page.dsx inside. And

like this. And then page.dsx inside. And

now let's go ahead and let's import sign in from clerk nextjs and let's do a page

export here with a div which will have a class name of flex flex column maximum

width of 3 excel mx out and width pool and then let's add a section which

includes a class name space Y 6 padding top of 16 VH and to Excel padding top of 48.

Inside of this section, add a div with a class name flex flex column items center. And inside of here, render sign

center. And inside of here, render sign in. Whoops.

in. Whoops.

Sign in like this. And once you've added this, you can go ahead and copy this and you can add sign up here like so. And

then change this to be sign up as well.

Go inside of the sign up page and replace sign in import with sign up import.

And then let's go ahead and do the following.

So we're going to set this to be public route. So, we are going to go inside of

route. So, we are going to go inside of our middleware.ts.

our middleware.ts.

We're going to import create route matcher right here. And we're going to define a

right here. And we're going to define a constant is public route using create route matcher. And we are going to

route matcher. And we are going to target sign in.

And then we're going to change this expert default clerk middleware to include an arrow function which checks if the current request is not a public

route and then it will redirect to the protect page.

And then what we have to do is we have to modify our environment variables.

So let's go ahead and go inside of environment variables here and let's add that next public clerk signin URL is

forward slash signin and the fallbacks will be an empty forward slash. So next

public clerk sign in fallback redirect and next clerk sign up fall back redirect urls. Perfect. And now I think

redirect urls. Perfect. And now I think that already you should be able to see this. If you go to localhost 3000, I

this. If you go to localhost 3000, I think you should immediately be redirected to this page, right? And if

you try to visit any other page like try to visit some older project like projects 1 2 3, you get immediately redirected back to the signin page. So

all pages are now protected. We are of course going to slightly modify this by going inside of the middleware and let's

modify this array of public routes to also include a forward slash and I like

to use these types of quotes and we also uh actually yeah I think this uh

we also need forward slap API inest like this. So make sure you add this

like this. So make sure you add this otherwise background jobs will not be able to work. So we need to allow inest to be contacted.

Great. So once you've done this you should now be able to visit the localhost 3000 page. There we go. You

can see that now we can visit this but we still can't visit the individual project page. So uh and yes I am in dark

project page. So uh and yes I am in dark mode. You might be in light mode. It

mode. You might be in light mode. It

doesn't matter. So now let's go ahead and do the same thing that we did.

Let me just go here. The same thing that we just did for sign in. We are now going to do to the sign up page. So we

just did this. So we don't have to do it. I told you like already that you can

it. I told you like already that you can just copy the sign in and do it right here. But what we need to do is we need

here. But what we need to do is we need to add sign up to the list of our public routes. So let's see. Did we do that or

routes. So let's see. Did we do that or not? We didn't. So let's add it.

not? We didn't. So let's add it.

There we go. So sign up is now added to the list the same as sign in. And then

we also need to add all the environment variables here. So let's go inside of

variables here. So let's go inside of environment here. And let's just add

environment here. And let's just add some so next clerk next public clerk sign up URL and the redirect URLs for

sign up fallback and sign in fallback.

And looks like these are duplicates. So

yeah, I think you only need one of these and one of these. So yeah, you can do remove these two and just move these two. There we go. At least I think that

two. There we go. At least I think that they were duplicates, right? I think

they were. Uh, and now you should be able to go uh manually. You can just enter any other route. Try going to projects one to three. You will be

redirected. And if you click sign up,

redirected. And if you click sign up, you should be taken on the same layout.

As you can see, it loads the sign up page. So now you can switch between the

page. So now you can switch between the two. Perfect. Uh so now what I want to

two. Perfect. Uh so now what I want to do before we even log in, I want to go back to localhost 3000 here and I want to create a navbar.

So let's go inside of our home module.

So that's going to be inside of source modules home UI components. And in here,

go ahead and create navbar esx.

Let's go ahead and mark this as use client. And let's import link from next

client. And let's import link from next link. Let's import image from next

link. Let's import image from next image. Let's import all of these from

image. Let's import all of these from clerk next.js. Signed in, signed out,

clerk next.js. Signed in, signed out, sign in button, and sign up button. And

then let's import button from components UI button. Let's export con navbar here

UI button. Let's export con navbar here and let's return a nav element.

Give this nav element a class name of padding 4, background color transparent, fixed top zero, left zero, right zero,

zindex of 50, transition all, duration 200, border bottom, and border transparent.

Inside of this div uh nav add a div with a class name of maximum width 5 xl mxal

width full flex justify between and items center.

Add a link inside with an href to a forward slash with a class name of flex item center and gap of two. And in here

render an image with a source of logo SVG out of vibe width of 24 and

height of 24 as well. Add a span with a text vibe inside. Add a class name font

semibold and text large like that.

And let's go ahead and let's go inside of our layout in app folder home layout and let's render it just so we can start

seeing some results. So navbar

from modules home UI components navbar and you should now see the vibe right here at the top and the fun fact it should also appear if you go into out

screens as well as you can see. So now

you can always use it to quickly go back. So now let's continue developing

back. So now let's continue developing the navbar here. The first thing we're going to add after the link is we're going to add signed out state like this.

And then inside add a div with a class name flex gap 2 and add

sign up button and add a normal button inside with a variant of outline and a size of small

and render sign up inside. You can copy this and then

inside. You can copy this and then change this to sign in button. This one

will say sign in and this one won't have the outline variant. It will just have a size small. So there we go. Now you have

size small. So there we go. Now you have sign up and sign in buttons that you can access only if you're logged out of course. And

then if you are signed in let's just add a paragraph to do user control.

And now we should be ready to log in. So

I'm going to click sign in here and I'm going to continue with Google.

And once you confirm your Google login, you will be redirected back here. And

you should be redirected on the landing page with the text to-do user control like that. And you can see how now we

like that. And you can see how now we can also load these apps. So, the reason they previously weren't even loading uh is because in the middleware, we didn't

allow the TRPC to be a public route. So,

in my case, I'm not going to have any public TRPC procedures. But if you want to, you can also add TRPC here

like this. Let's just fix this TRPC. And

like this. Let's just fix this TRPC. And

then if you well, you can't log out now.

So, let's just create a component to log out now. and then I will demonstrate

out now. and then I will demonstrate this. It's completely fine to add this

this. It's completely fine to add this here because we are going to protect TRPC routes based on their procedure type as well because right now if you wanted to have any public API routes

they don't exist. We prevent any anything other than this to be a public route.

Uh also you can create the exact opposite right you can call this is private route and then all of these will be private routes right and then you would just modify your logic you would

remove the exclamation point and you would do this so if you have the majority of the public routes and minority of the private ones you can just reverse the logic of the clerk middleware that's the cool thing about

this it doesn't have to right we just called this is public route we could have called it anything and we could just put private routes inside and then use the reverse logic here, right? It's

not like you need to add the public ones here. So, this is especially useful if

here. So, this is especially useful if you have a bunch of public routes, then just do the reverse logic, you know.

Great. Now, let's add the user control uh component.

So, I'm going to go ahead and close everything. I'm going to go inside of

everything. I'm going to go inside of source components and I will create user control. DSX. I will mark this as use

control. DSX. I will mark this as use client and I'm going to import user button component. Here I will export

button component. Here I will export const user control and I will create an interface props

here to show name which is an optional boolean and in here I will add the props and show name.

Then in here I'm going to return the user button component which is a self-closing tag.

And I will modify well first I will pass the show name prop and then I'm going to modify the appearance prop to include the elements

and then get the user button box to be rounded medium with an exclamation point at the end which basically means important

user button avatar box rounded medium with size eight eight and user button trigger rounded

medium like so.

So I don't have to type this any every time I have uh created it in a component like this. So now let's go back inside

like this. So now let's go back inside of our navbar and inside of signed in render user control and pass in show

name prop. So just make sure you have

name prop. So just make sure you have imported user control and there we go.

Uh, and in here you now have this uh name which is barely visible because I'm in dark mode. Don't worry, we're going to fix that as well. But from here you

can access your entire account, your security, uh, all of those things and you can also sign out from here. And you

can see that now since I enabled inside of my middleware TRPC, I can fetch them, right? But if I remove this and refresh,

right? But if I remove this and refresh, I'm not able to fetch them because all the network requests for TRPC are failing. As you can see, all of them

are failing. As you can see, all of them are failing. But we will protect our

are failing. But we will protect our TRPC routes in a different way. So it's

completely okay in my opinion to allow this. In fact, you can even just allow

this. In fact, you can even just allow your entire API like this. then you

don't have to worry about injust or DRPC specifically because our API should uh it should be protected in a different way in the first place. Let's just do a

sanity check. What do we have here? We

sanity check. What do we have here? We

have ingest and we have uh TRPC. So

inest needs to be publicly available simply because inest will contact this no one else. Right? And if you're wondering how this works, they probably have some kind of uh security header

which is checked every time you access this route because you can see that we're using the serve from ingest next.

So inside of here, they probably have their own request handler that does all the security features inside. And as for TRPC, uh well, we are going to be the

ones who are going to have to protect each individual route here. And that's

what we are going to do with the protected procedure. So I would actually

protected procedure. So I would actually recommend allowing all API endpoints to be public routes. So now let's go ahead

and let's uh create the dark mode for this because you can see that when I log in of course it just looks weird. So

what we're going to do is we're going to go and create a hooks folder.

So go inside of source. Uh do we we already have hooks? Great. So this came with chats and UI. And now create use current theme.ts.

current theme.ts.

So we already have use theme from next themes and we already use it in the project header use theme. The problem

with this is that it has uh the following possible values. It can be system dark or light which is fine for

this radio group. But if the value is system, what exactly is that? Is it light or is it dark? We

is that? Is it light or is it dark? We

don't know. That's why we have to create a custom use current theme here where we can extract the theme and

system theme. Specifically

system theme. Specifically use theme like that. And then if theme is dark

or if theme is light, we can just return the theme as usual. Otherwise, return

the system theme like that because you you cannot always just return the system theme. This only makes

sense if the theme is system, right? If

it's dark or light, then we don't care.

we can just return whatever the value is. But if it is something other than

is. But if it is something other than dark or light, it means it is system. So

then we cannot return theme. We have to return the actual value of the system theme. And now we can go inside of the

theme. And now we can go inside of the user control right here. And we can

adapt it as follow const current theme use current theme like this.

And then inside of here, uh, let's also import. I think we need to install a

import. I think we need to install a package first. So, let's just do npm

package first. So, let's just do npm install at clerk forward slash themes.

And I'm going to show you my package json here.

2.2.51 is my version. And then from that package, you can now import

dark from clerk themes. And then very simply in the appearance here set the base theme to check if current theme is

equal to dark use dark otherwise use undefined.

And now you can see the text is visible and this is now in dark mode.

So you might be thinking could I have just added that to the clerk provider because clerk provider also allows for the appearance and base theme. Well, you

can, but the problem is the theme provider for SHAT CN needs to be inside of the body and clerk provider needs to

be outside of the HTML. So, we kind of have a conflicting situation here, right? Um, you can try moving both the

right? Um, you can try moving both the clerk provider and TRPC react provider here maybe, but I'm not sure how that

works, right? Uh I'm not sure if it

works, right? Uh I'm not sure if it matters but uh from all the documentation I've seen these two need to be outside of HTML. I'm not sure I

could be wrong but basically if you are able to move these two like this inside then you can create an abstraction

around cleric provider and then you can do the same thing like this. But for now I'm going to leave it like this simply because this is what worked for me initially.

Great. So we now have this in the user control and we can now go inside of sign in and do the same thing because right now if I sign out and if I go here you

can see that this uses light mode. So

let's go inside of sign in page right here. Let's go ahead and mark this as

here. Let's go ahead and mark this as use client and let's import use current theme from hooks use current theme which we just

created. And let's import dark from

created. And let's import dark from clerk themes.

Now in here we can extract the current theme and for this sign in let's add appearance here

base theme we'll check if current theme is equal to dark and use dark otherwise use undefined. And let's also modify the

use undefined. And let's also modify the elements a bit by adding card box here.

Do not have any border, not have any shadow and be rounded for LG like this. And there we go. You can see

like this. And there we go. You can see how now this is in dark mode. And when

you click on sign up, you can see it still uses the old theme. So let's just go ahead. We can just copy the entire

go ahead. We can just copy the entire file, go inside of sign up, paste the entire thing, and replace the import to be sign up. I think that's faster.

And there we go. Now both of our sign in and sign ups have the proper team.

Great. So let's see where we are. What

did we do? We created a clerk account.

We updated updated our environment. We

added clerk provider signup screens middleware. Perfect. We added home

middleware. Perfect. We added home layout in the navbar. We created the user control component. Now let's create protected tRPC procedures. and let's

update the Prisma schema.

So I just want to do one more thing with the user control component and that is inside of the project view. So go inside

of this component here and in here after the tabs list here we added this ML auto flex and this button to upgrade. Now

next to it also add user control like this and don't add the prop show name. So just make sure that you

name. So just make sure that you imported user control and let me show you how that will look like now. So now

if you of course sign in. So let me just enter an account here and let me just go to uh any random

project here.

You can see that I have my user button right here. And let me just switch to

right here. And let me just switch to light mode to see everything still works fine. There we go. You can see that now

fine. There we go. You can see that now I can log out and access my account information from here as well. And let's

just double check the light mode to see everything works fine. Everything works

just great. Perfect. So now what we have to do is we have to create the protected TRPC procedures.

So in order to do that, let's go inside of source tRPC and let's go inside of init. And now in here, we're going to modify the TRPC

context here. So what I'm going to do is

context here. So what I'm going to do is I'm going to remove this comment and I will return

al to be await out from clerk next.js server. After that,

server. After that, I mean just below it, I'm going to export type context which uses awaited return type type of off create trpc

context. And you can actually find these

context. And you can actually find these exact instructions in clerk documentation here. Let me just find

documentation here. Let me just find tRC. Maybe I can find it. Integrate

tRC. Maybe I can find it. Integrate

clerk into your next.js plus tRPC app.

Let me see if that is uh what I'm looking for here.

Let's try again. TRPC

I think this might be it. So yes, you can see that they instruct you to wrap the clerk provider around the TRPC provider. So we already did that, right?

provider. So we already did that, right?

We are using TRPC React provider simply because the documentation has changed since then, but it's the same thing.

It's important that the clerk provider is wrapping around the TRPC provider.

And now in here we are basically doing this in the create context and we are creating the context type here like that. And in here you can find all the other things that we are going

to do. Uh for example they have a

to do. Uh for example they have a specific instruction to now add create context to this TRPC route. But if you look at the TRPC route

we already do that right? So you don't have to worry about that. You can just follow what I do now. So I'm going to go after this T initialization and I will

do const is outped like so and I will do T dot middleware and from here the structure next and

context and check if not context out user ID

in that case throw new TRPC error code.

Uh let me just see what exactly is the problem in this one. Oh, so I need to import TRPC error from TRPC server.

Okay, make sure you do this.

Then let's do unauthorized here with a message of not authenticated like that.

Nothing. uh and then after this if clause return next and extend the context to include out.

Now we have to fix this problems that out doesn't exist. You can do that quite easily by going back to this t here and simply add dot context

add the context type and execute it. And

you can see that now we have the al property here which we added here.

Perfect. And it's important that we also cache this in React. So this doesn't need to be called every single time. And

we are simply relying on the user ID from it here. So make sure you don't do any mistakes here because now we're finally going to go ahead down here and

do export const protected procedure to be t.procedure.

be t.procedure.

Whoops. T.procedure.

is authored.

There we go. Now we have our protected procedure.

So now it's time to replace a lot of our previous uh well procedures with this new one. So thankfully we don't have too

new one. So thankfully we don't have too many modules. So let's start with the

many modules. So let's start with the messages procedures here. Let's see. At

first we have get many instead of base procedure. What you can do is honestly I

procedure. What you can do is honestly I don't think there will be a single public procedure here. So what I like to do is I like to highlight base procedure

and then I press command D or control D and then this just selects all of the other ones. Right? So 1 2 3 and I can

other ones. Right? So 1 2 3 and I can remove them and I can add protected procedure like this. And nothing really changes

like this. And nothing really changes now. So I'm just now using protected

now. So I'm just now using protected procedure for each of these for get many for create and I think that's that's the only two instances and you might think

but nothing really changed now that's right but look at this if I'm using so just for example I will bring back base procedure here for the get many I'm now

using base procedure if I try to extract context from here and if I try to do uh let's for example imagine that we can

query the messages by user ID. Imagine

if I do user ID here and do context.

And then I try to do you know user ID in here. It can tell me that the user ID is

here. It can tell me that the user ID is string or null. So that means what I have to do is I have to first check if there is no context.out user ID and then

I have to throw new tRPC error blah blah blah. But that makes no sense. we just

blah. But that makes no sense. we just

created the protected procedure which does that for us and then uh passes the context further. So instead what we do

context further. So instead what we do when we know that something has to be a protected procedure that we will always throw an error if the user ID is missing we can now just use the protected

procedure and this time the user ID is a type of string.

see the difference. Base procedure tells me it can be string or it can be null.

But protected procedure tells me this is definitely a string because it 100% exists at this point. So that's why we're replacing things with a protected

procedure. And then at this point it

procedure. And then at this point it doesn't matter that our API is allowing the public route for API. Right? An

important thing you should know, you should never never ever ever rely on the middleware for authentication.

So what I'm doing here is just a nice user experience, right? It is easy to redirect the user using the middleware.

But this isn't my line of defense.

This isn't what I'm doing to throw errors. That's why I have a data access

errors. That's why I have a data access layer called TRPC. And in here I have my protected procedures. So if this

protected procedures. So if this middleware fails even even in this case I explicitly allow the middleware to

allow API routes. I'm still very much protected because I'm protecting my data access layer and you should do the same.

Never ever rely on the middleware to protect your app. If you want to use the middleware for nice user experience like we are, of course you can do that. But

it shouldn't be your last line of defense. You should have a data access

defense. You should have a data access layer and you should protect your routes individually because let's imagine this middleware breaks which can happen.

Nex.js had a middleware security issue just a few versions ago and people who were shocked that that happened know got a very big security lesson. You

shouldn't rely on the middleware. Even

if the middleware was perfect, you should not rely on it. Uh don't confuse this middleware with this is also technically a middleware right we we

just created a middleware this middleware and this middleware are two different things they are not comparable okay what I'm trying to tell you is

don't try and do API and then projects and then create don't do this this is not enough for you to protect your API routes

you should protect your API routes inside of the routes themselves like I am doing right now. So this is a different type of middleware. They're

just using the same word. They're using

the same keyword middleware here and middleware here. Right? I'm trying to

middleware here. Right? I'm trying to explain the difference. So whenever you use the middleware, it should only be used to improve user experience like

adding redirects which is very a very nice thing to use the middleware for.

But if your middleware breaks and if the user actually ends up being able to visit my individual project page, I'm still just going to throw a bunch of

errors because when we try to load the messages, it will be a protected procedure and it will just throw the user an error saying, "Hey, you're not authorized. I don't know how you access

authorized. I don't know how you access this API, but you cannot see that API."

That's how authorization should work, not by the middleware. The middleware is just the first layer of security. The

actual layer of security is the data access layer. In our case, the RPC. I

access layer. In our case, the RPC. I

hoped I cleared that up. Sorry for going on this rant, but it is important for you to understand that. Great. So now

let's go ahead and once we finish the messages, let's go inside of projects server procedures. And I think that we

server procedures. And I think that we have to do the very same thing here. I

don't see a single thing that can be a base procedure here. So I'm just going to replace All of these instances I think this is the last one. Yes, with

protected procedure. So let me show you the exact changes. Get one, get many, and create. So three procedures in this

and create. So three procedures in this case. And when I search for base

case. And when I search for base procedure now, not a single one exists except the actual instance here in the init file. And when I search for

init file. And when I search for protected procedure, I have eight results in three files. the third one being in it file. So that's how your

project should look as well. So now only authorized users can access this API routes and it absolutely doesn't matter that we are not protecting it in the middleware.

Perfect. So now let's go ahead and let's add Prisma schema update because now we finally have the user ID. So that means

we can go inside of Prisma schema here and we can modify some things. So let's start with the

some things. So let's start with the project. The project from now on will

project. The project from now on will have a user ID which will be a required string like this.

And since other entities relating to the project, all other entities end up being related to the root project. We don't

really have to add it to the message as well. You can of course do that if you

well. You can of course do that if you want to uh if you have any architectural reason for doing that. Of course, you can add individual user ID for the

fragment, individual user ID uh for the message, right? But for the same reason

message, right? But for the same reason I'm not adding project ID into the fragment because the fragment is related to the message which has the project ID already. I'm not going to be adding the

already. I'm not going to be adding the user ID uh to my uh other entities because it is enough that the user ID is in the project. And once I've done that

I'm going to shut down my app and I'm going to do npx prisma migrate reset. So

again only do this in development. We

are clearing up our database because we are in development and we can do that.

And once we've done this, let's go ahead and do npx prisma migrate dev. And once

it connects, let's simply call this user ID or out.

Here we go. I'm going to call this user ID. And there we go. Now I'm going to do

ID. And there we go. Now I'm going to do npm rundev. And I'm going to run npx

npm rundev. And I'm going to run npx inest cli latest dev. So both things should be running. And just double check

that it was able to connect to API inest because as I said if you accidentally don't allow your API here then inest

will not be able to connect. There we

go. You can see that now it's 404. So

that's wrong. That's why you need to make sure to allow all of your API routes and then after some time let's check again. It can connect to it.

check again. It can connect to it.

Exactly what we need. So now let's go ahead and fix all of the issues that we have because we do have them. We just

got a new rule and that is that each project needs to have a user ID. So

let's go ahead inside of our modules and let's go inside of projects server procedures and we can already see some errors here such as the create error. So

in the create protected procedure we now have to also pass the user ID which we can easily extract from the context here because we are using a protected

procedure. So context.out user id as

procedure. So context.out user id as simple as that problem fixed. Now what

we have to do next is you also have to modify the get many. So whose projects are we loading? Very simple. The

currently logged in users projects. So

let's add a wear here.

like this user ID matches context out user ID and now we are only loading this currently logged in users project and same thing

for get one simply extract context from here and we can only load the existing project if we have a matching user ID

if we don't this will be null and we're going to throw the error not found so even if the user is logged in and manages to surpass this protected

procedure, we will still be able to throw the error because we have no idea which project with that specific ID and that user ID they are looking for. So we

just throw we have no idea what you're talking about. We've never seen that

talking about. We've never seen that project before. So full security in our

project before. So full security in our application. And now we have to do the

application. And now we have to do the same thing but for messages. So let's go inside of procedures here and let's check get many. So in here I'm going to

extract the context as well and I'm going to try and do project user ID and it seems like I can do that. So let's

simply add context out user ID. There we

go. So just like that we are now also protecting all of our messages. But

since we don't have direct user ID in the message we have to go through the project ID first.

Perfect. And for the create uh well we have to do the same thing but a little bit differently here. So what we're going to do here is we're going to do

con existing project first await prisma project find unique where

and simply add the ID to be input project ID and user ID to be context

pal user ID like this.

I mean maybe we can somehow do it from here. I don't know. I'm not that good

here. I don't know. I'm not that good with Prisma, but you can do it in two separate queries. You know, it's not the

separate queries. You know, it's not the end of the world. And if there is no existing project, let's throw new TRPC error here

code not found with a message project not found. So I have no idea what where what project do you want to create this message into. Right? And

make sure you have imported the TRPC error from TRPC server. So this way, even if they somehow surpass the protected procedure, we are still not going to allow them to just create

messages in someone else's project because they need to match the exact user ID who created that project. Uh

great. So we now have this done right here. And then we can safely do the

here. And then we can safely do the created message. You can even then use

created message. You can even then use maybe it's even safer to use existing project ID for the project ID. So it's

only this one which we can query from our database with the correct user ID that we're going to insert this message into.

Excellent. And you don't have to worry about these background jobs because we are protecting them before we even trigger them.

So that is for the create method. Let's

see. Did I fix it for the get many? And

I think that should be it. So obviously

we should now just test our app to make sure things are still working. Uh so

let's go ahead and do the following.

Let's go inside of our projects list now. And I just want to do a slight

now. And I just want to do a slight modification here. And that modification

modification here. And that modification is that I'm going to load the current user from use user which you can import

from clerk next.js. And then this will allow us to do the following. We can

then do user question mark first name and then apostrophes users vibes and then in here also do if

there is no user return null. So we

don't even load the project list if we are not logged in. We can of course do this in a million ways but I think this is just simple enough for now. Uh great.

So you can see that when I am logged out nothing happens here. Great. So what I want to do first is I want to go inside of my project form

and in here in my on success specifically on error I should redirect the user to the out

screen as well if this fails because right now when I type test I'm just getting an error not authenticated. I

mean, you could technically argue that's good enough, but uh let me show you what you can do. So, you can do router and then you can just push the user to sign up or sign in. That's an easy way you

can do. But here's a cool thing. You can

can do. But here's a cool thing. You can

actually do con clerk use clerk from clerk.js.

clerk.js.

So, just make sure to add this import.

And once you have it, you can do the following.

If error data.code is equal to unauthorized,

data.code is equal to unauthorized, you can do clerk.open

sign in like that. Uh, and let me just so error data is possibly undefined. So

maybe I need to do this. There we go.

And let me just move this to the top. So

this way if this fails, it will open the signin model. So I write test.

signin model. So I write test.

And there we go. It opens this nice model. I think it looks cool. If you

model. I think it looks cool. If you

want to, you can also just do, you know, router.push

router.push sign in. That also works.

sign in. That also works.

There we go. So, whichever one you prefer. I just thought I would show you

prefer. I just thought I would show you this cool alternative.

Uh, great. So, I'm pretty sure that we don't have to do anything more besides test the app. So now I'm going to log in here.

And as you can see, it says Jones Vibes and no projects found. And I will do build a landing page here.

And let's see, will I get any errors? I

don't think I'm getting any errors at all. I do want to just check my

all. I do want to just check my functions.ts just to confirm I'm not getting any errors in here. Even though

we shouldn't be getting any errors at all. This is a background job. This

all. This is a background job. This

doesn't need any user ID.

I think everything is just fine here and I think that we will be able to normally load the messages. But let's wait and see the result.

And there we go. Once I am logged in, you can see that I can normally create my messages. I can even refresh this and

my messages. I can even refresh this and I can load my messages. I can see my fragments. So all of this is obviously

fragments. So all of this is obviously working. Perfect. Uh and let's try

working. Perfect. Uh and let's try something fun. I'm going to copy the URL

something fun. I'm going to copy the URL here and I will log out. And then I'm going to go ahead and paste that URL. So

obviously I'm getting redirected. But

let's say inside of my middleware I accidentally do projects and I do this. All right. So let's see what happens then.

You can see that even if the middleware fails, nothing useful is shown to the user and finally an error is thrown. This is of

course not ideal because we are missing an error boundary. uh we're going to fix this details in the last chapters, right? Obviously, it's not ideal that

right? Obviously, it's not ideal that this types of errors shown even though this wouldn't show in production. This

is what you would see in production, which is not any better, but we will I'm going to show you how to add proper error boundaries. I just wanted to show

error boundaries. I just wanted to show you that you can't fool this system we just created, right? You will either get redirected or you will be hit with a

bunch of errors because you don't belong on that website. Amazing. Amazing job. I

think that officially marks the end of this chapter. So now let's go ahead and

this chapter. So now let's go ahead and let's open a pull request. So 16

authentication.

I'm going to create a new branch.

16 authentication.

I'm going to stage all of my changes. 16

authentication. I'm going to commit and I'm going to publish the branch.

Now, let's go ahead and open a pull request and let's review our changes.

And here we have the summary. We added

user authentication and theming support using clerk, including sign in and sign up pages with theme aare styling. We

introduced a navigation bar with authentication controls and user display. We added a user control

display. We added a user control component for displaying user information and actions. We integrated

authentication checks into project and message features, ensuring users can only access their own data. We improved

project and message lists to only display personalized content and enforce userbased access. Exactly what we did in

userbased access. Exactly what we did in this chapter. As always, file by file

this chapter. As always, file by file walk through here and a whole sequence diagram explaining how our new clerk provider and authentication TRPC

procedures work. And we did a very good

procedures work. And we did a very good job this time. The only comment is in the migration SQL. Um, we don't really care about this because we are in

development phase and no other comments.

Amazing, amazing job. Let's go ahead and merge this pull request. And once we've done that, let's go ahead and go back to the main branch and let's click on synchronize changes and okay. And in a

few seconds, you will see that 16 was just merged authentication.

That marks the end of this chapter.

Amazing, amazing job and see you in the next one.

In this chapter, we're going to implement billing and the credit system into our project. In order to do that, we first have to enable billing in

clerk. We then have to create a pricing

clerk. We then have to create a pricing page. After we've done those two, we can

page. After we've done those two, we can start and add rate limiting or usage or credit system in our application. This

will include adding some new models to Prisma schema and creating the util for rate limiting. After that, we're going

rate limiting. After that, we're going to have to create the usage component which will show to the user how many credits they have. And finally, we're going to have to update some procedures

to actually call this util for rate limiting to trigger credit spend. So,

let's start with enabling billing. So,

this is the first time I'm using clerk billing. It was announced recently and

billing. It was announced recently and the moment I heard it, I just knew it had to be an incredible developer experience. And I think that you will be

experience. And I think that you will be shocked at how good it is because we all know that Clerk's developer experience is immaculate. They have completely

is immaculate. They have completely solved the issue of uh complicated code.

Everything regarding Clerk is super easy, super fast and super simple to do.

And billing is one of the most complicated parts of building the authentication, especially if we are doing it with Stripe. So, you're going

to be so impressed by the fact that we won't even need a web hook for this entire process. More so, we won't even

entire process. More so, we won't even have to build a single component besides our custom usage component, which actually has nothing to do with billing.

Let's go ahead and do that. You can find information about clerk billing in their documentation here. You can scroll and

documentation here. You can scroll and find billing. And in here, you can

find billing. And in here, you can select B2C. You can also do B2B but in

select B2C. You can also do B2B but in this case it will be uh B2C SAS. The

first thing we have to do is enable billing. So we have to go to billing

billing. So we have to go to billing settings. This will redirect you to your

settings. This will redirect you to your project. So you click into configure and

project. So you click into configure and down here billing settings.

Go ahead and click create a plan. And

the first thing we're going to do is we're going to create a free plan. So

I'm going to go ahead and open this. And

in here you can set the name of the plan and you can set the slug like this. So

this is what we are going to use in our codebase to check if the user is on that plan. Right? So we're going to check if

plan. Right? So we're going to check if free user is active. That means the user is currently on the free tier. Uh and in here make sure to check publicly

available. This basically means that

available. This basically means that this will appear on the pricing table.

So you can create that and then you can click add another user plan and for example call this pro like this and give

it a monthly fee of $29. And if you're wondering can I easily combine this into an annual discount? Yes, you can just enable annual discount and in here set

it to something like 25. So, if they pay monthly, it's going to be $29 a month.

But if they choose to pay annually, we're going to reduce the price to $25 per month. So, you're going to get $300

per month. So, you're going to get $300 instantly from them. And in return, they're going to have a bit of a cheaper plan. You can, of course, uh assign the

plan. You can, of course, uh assign the price to whatever you want and click save. So, right now in your subscription

save. So, right now in your subscription plans whoops.

right here you should have two subscription plans, one free and one pro which is build monthly or annually. So

in here, let me just go and click on the settings here. And in here, let's just

settings here. And in here, let's just click enable billing. There we go. So

now billing is enabled. And in here, you can choose your payment getaway. So if

you want to, you can add your own Stripe account, but you can also choose clerk's payment getaway, which is a zero conflict payment getaway. it is ready to process and test payments immediately.

And this is amazing. You're going to see how simple this is. So, to recap, go inside of configure, go inside of billing settings, and make sure that you

click enable. Make sure that you have a

click enable. Make sure that you have a message billing is enabled. After that,

go ahead and create two subscription plans right here. And now, let's go ahead and let's add a pricing table to our app.

So, as always, make sure that you are on your main branch.

Make sure that you synchronize your changes. And if you want to go inside of

changes. And if you want to go inside of source control and confirm that the last change was adding authentication.

What we have to do now is we have to implement the pricing page. So, let's go inside of source app home and let's create a new folder called pricing

inside page.tsx. tsx

inside page.tsx. tsx

like this. Let's mark this as use client. And then let's go ahead and

client. And then let's go ahead and let's import image from next image. And

let's import pricing table from clerk next.js.

And let's go ahead and export this page.

So I'm going to export it like this. And

inside of here for now, let's just do pricing table. Let's just do that and

pricing table. Let's just do that and nothing more. If you now go into your

nothing more. If you now go into your app, just make sure you have it running.

And if you go to forward slashpricing, uh it will redirect you to the login page. So, let's just make sure to add

page. So, let's just make sure to add that here. So, forward slpricing

that here. So, forward slpricing like so. Now, you should be able to go

like so. Now, you should be able to go to localhost 3000 pricing.

There we go. we have a date a pricing table. So we didn't have to code a

table. So we didn't have to code a single component. All we have to do is

single component. All we have to do is style it a little bit now. So let's go ahead and do that. So I'm going to go back inside of here and I'm going to add

a class name flex flex column maximum width of 3 Excel MX auto and full width. I will then add a

section around our pricing table and I'm going to create a div inside of here with an image component and then in here I will add an H1 element and then I'm going to add a

paragraph. Now let's go ahead and style

paragraph. Now let's go ahead and style this. Starting with the section which

this. Starting with the section which will have space Y 6 padding top of 16 VH

on to Excel padding top of 48.

Then on this div encapsulating the image let's give it a class name of flex flex column and items center. For the image

itself, give it a source logo SVG, alt vibe, width 50, and height 50 as well.

And give it a class name hidden MD block.

Then in the H1, go ahead and add the text pricing. And give this a class name

text pricing. And give this a class name of text extra large like this.

on MD text 3 Excel font bold and text center for the paragraph. Enter the

text. Choose the plan that fits your needs.

And give this a class name text muted foreground text center text small and MD text base.

And now let's just go to the pricing table appearance.

And let's add elements here. Pricing

table card. And let's change this to use the border shadow none rounded large. Just just make sure

rounded large. Just just make sure you're putting the exclamation points here. And just like that, we have a

here. And just like that, we have a pricing table that clearly reflects the two plans that we created in clerk dashboard. So now let's go ahead and

dashboard. So now let's go ahead and let's modify the descriptions of these and let's show some features which will be active once we upgrade.

So let's go inside of our clerk dashboard here. Go inside of configure

dashboard here. Go inside of configure and in here subscription plans. Select

the free one and let's go ahead and give it a description for getting started like this and click save.

And then in here, let's add a feature.

And let's go ahead and call this feature five monthly credits.

Like that. Make sure it's publicly available. And let's click create

available. And let's click create feature. And let's click save. And just

feature. And let's click save. And just

by doing that and refreshing, you will immediately see the new description reflected here. And you will also see

reflected here. And you will also see the new feature five monthly credits.

And now if you wanted to this is where you will add even more features for example public projects something like that. So for example we don't even have

that. So for example we don't even have private or public projects all projects are private in our case but for example here you can see how that would look

like. And now let's go ahead and let's

like. And now let's go ahead and let's modify the other plan which is the pro plan. So go back here

plan. So go back here back inside of our subscription plans and select the pro plan. So this one can

have the description of for more projects and usage and then go inside of the features here

and let's go ahead and add a new feature called 100 credits per month like this.

Let's go ahead and let's add private projects like that. Let's add custom domains.

like that. Let's add custom domains.

Basically, whatever you plan to, you know, extend this application with, you can add here or maybe some collaboration like three editors per project. All

right, just think of a bunch of features you would add to your app. And let's do uh remove the vibe badge. Imagine that

we would create some kind of feature that adds the vibe badge. And click

save. And you can of course, you know, drag and drop this however you like. I

just wanted us to add a lot of features here simply because the the pricing table looks better if you add more features here. And I absolutely love how

features here. And I absolutely love how this looks. So now that we have uh this

this looks. So now that we have uh this finished, let me just go ahead and do one thing. I want to go

one thing. I want to go inside of layout right here and I want to modify the clerk provider and its appearance here. And I want to add

appearance here. And I want to add variables here. Color primary. And I'm

variables here. Color primary. And I'm

just going to set it to the light mode of our cloud theme, which is this.

And when you save this, you can see that immediately it will pick up the theme that we are using throughout the project. And I just think this looks

project. And I just think this looks amazing. Now, let's just go back to the

amazing. Now, let's just go back to the pricing here. And let's enable dark mode

pricing here. And let's enable dark mode if we need it. So, I'm going to import dark from Clark themes. And I'm going to

import use current theme. Make sure this is marked as use client. Go ahead and add

this. And then simply in the appearance,

this. And then simply in the appearance, go ahead and add base theme. checking if

the current theme is dark, then use dark, otherwise it's undefined. And now

this page will support dark mode as well. Uh, and if you try and subscribe,

well. Uh, and if you try and subscribe, you can see that you're redirected to login. So you don't have to immediately

login. So you don't have to immediately uh try and subscribe simply because I want to demonstrate the entire upgrade process. You can see that when I log in,

process. You can see that when I log in, by default, every user is in the free tier. You didn't even have to write the

tier. You didn't even have to write the code for that. you automatically, we automatically added this user to the free tier. If you click subscribe here,

free tier. If you click subscribe here, you can see how nicely this looks. As I

said, you don't have to do this now. If

you can, fine. Sure. But you're going to have to create a new account to test out our usage uh tryyouts. So, you can see how this looks. I think it's just, you

know, amazing. Uh, and one thing that I

know, amazing. Uh, and one thing that I want to fix that we forgot about is when I scroll, I want this navbar to stop

being transparent because it just looks weird. So, let's quickly fix that by

weird. So, let's quickly fix that by going inside of source hooks and let's create use scroll.ts like this.

And inside of here, let's go ahead and do import use state use effect from React export const use scroll.

And let's add the threshold to be 10.

Define the state is scrolled and set is scrolled.

Use state by default is going to be false. Call use effect

false. Call use effect with for now an empty dependency array like this. And inside create a handle

like this. And inside create a handle scroll arrow function which will call set is scrolled to be

window scroll Y which is above the threshold.

And then let's add a window add event listener here to listen for scroll and handle scroll.

And then simply call handle scroll. And

in the return method call window remove event listener scroll handle scroll just like that. And add the threshold inside.

like that. And add the threshold inside.

And all you have to do is return is scrolled.

There we go. So now that we have use scroll in our app, we can go back to the navbar inside of home module UI

components navbar. And in here you can

components navbar. And in here you can now easily get is scrolled. Use is

scrolled.

Use scroll. My apologies from hooks. Use

scroll. and then make this a dynamic class name by wrapping it inside of curly brackets and adding the CN util.

So we have to import CN from lib utils.

I'm going to keep the static classes as the first argument and then I'm going to check if is scrolled. Let's do back bg

background and border border.

So now if you scroll ever so slightly, you can see that the navbar starts to stop being transparent and a

border appears. Amazing. Now that we

border appears. Amazing. Now that we have this and now that we have the billing, let's go ahead and just check a couple of things. So now this upgrade button should take you to the pricing.

If yours doesn't, make sure you check the project view here. And make sure that you have a button. Let me just find

it. Here it is. Button with a link

it. Here it is. Button with a link redirecting to pricing like that. And

now it's time to create the usage model.

So in order to do that, we're going to have to install a package called rate liimiter flexible. So let me just close

liimiter flexible. So let me just close this and this and let me do npm install rate limiter flexible like this. Let me

show you the version.

So I'm using 7.1.1.

That's my version.

Let me do npm rundev. And then in here, I'm going to go ahead and do the following. Inside of Prisma schema,

following. Inside of Prisma schema, I'm going to create uh a new model called usage. Right here at the bottom,

called usage. Right here at the bottom, model usage.

Let's go ahead and give it a key, which will be a type of string, and that's going to be the ID. and then a points

which will be integer and finally expire which will be an optional date time. So this will be my usage

date time. So this will be my usage model. Now let's go ahead and let's do

model. Now let's go ahead and let's do npx prisma migrate dev. We don't need to clear our database because this is not really conflicting with any other

models. We're just adding a new one. So,

models. We're just adding a new one. So,

I'm going to call this migration usage.

Let's go ahead and add the name usage.

And there we go.

Now that we have the new usage model here and the new migration ready, let's go ahead and let's implement uh the usage tracker. So, this is what I'm

usage tracker. So, this is what I'm going to do. I'm going to go ahead and create a new lib that I'm going to call

usage.ts. ts. And inside of here, I'm

usage.ts. ts. And inside of here, I'm going to go ahead and add an import for the rate limiter. So, specifically, it's

going to be rate limiter Prisma right here. And let's go ahead and do export

here. And let's go ahead and do export async function get usage tracker like this. And for now I'm just going to

like this. And for now I'm just going to define const usage tracker to be new rate limiter Prisma

store client will be our Prisma from the database like this and table name will be usage. So just

make sure it matches exactly the model we named here.

And for the points, let's go ahead and by default, uh, give everyone five points.

And for the duration, let's go ahead and let's do 30 days. So, 30 * 24 * 60 * 60.

Now, what I like to do is I like to do const free points. And let's go ahead and give everyone free points. So, you

can replace this like so. Then let's do the cons duration to be 30 * 24 * 60 * 60. So you can add a little comment

60. So you can add a little comment 30 days for example.

And now that we have this, let's just return the usage tracker.

And now let's go ahead and let's create a function called consume credits. So

export async function consume credits and in here let's go ahead first uh and

let's extract user ID to be await out from clerk next.js server like so and make sure to execute this.

If there is no user ID, we can throw new error here. User not authenticated.

error here. User not authenticated.

That's the first thing. And then let's go ahead and do const usage tracker to be await usage tracker. Basically this

function which we defined above. And

then in here const result to be await usage tracker dot consume.

pass in the user ID and then how many points do we want to take from them. So

for that I'm going to define const generation cost cost to be one. So let's

go ahead and add that here. So we're

going to subtract one point from the user every time we consume credits. And

then let's just return result.

And then let's create the last function.

Export asynchronous function get usage status.

Again extract user ID from await out.

If there is no user ID throw new error user not authenticated.

And then let's do const usage tracker here to be await get usage tracker and then result will be await usage

tracker dot get user id. So we are looking at how many points we have left.

There we go. So we are basically using this very very cool library which can inject directly into Prisma. Uh, and I'm just going to open the documentation now

for it so you can read more.

So here it is. Node rate limiter flexible.

Basically node limiter flexible counts and limits the number of actions by key and protects from DDOS and brute force attacks at any scale. It works with W key radius, prisma, dynamo, process

memory cluster pm2 memach myql, sqlite and posgress. Also works in the browser. It offers atomic

the browser. It offers atomic increments. All operations are in memory

increments. All operations are in memory or distributed environment. Use atomic

increments against race conditions. So

if that's something you were wondering about, yes, we solved the problem of race conditions by using this package.

It is extremely fast. It is flexible, ready for growth and it is friendly.

Now, should it be used exactly the way I'm using it? Right? I'm using it as a simple rate limiting for premium credits. I haven't really seen any

credits. I haven't really seen any advice not to do it. But since it solves uh a bunch of problems out of the box and it's just an npm package, I thought

it was a no-brainer to use it. Uh given

the fact that we can easily add it to Prisma, right? So that's why I chose

Prisma, right? So that's why I chose this package. Uh I explored a bit what

this package. Uh I explored a bit what we should use. Should we develop our own and this ended up being the best decision. Great. So now that we have

decision. Great. So now that we have these three functions, a functions to get the usage status by the current user ID key, a function to consume credits

for the current user, and the overall function to get the usage tracker, which right now doesn't make too much sense.

This could have been a constant, but don't worry, it will make sense later.

So now let's go ahead and let's actually create the procedure for the rate limiting here. So I'm going to go ahead

limiting here. So I'm going to go ahead and go inside of source. I will create a new module called usage and in here I'm going to

create server and then I'm going to create procedures.ts.

create procedures.ts.

Let's go ahead and do get usage status from lib usage. Let's import create trpc router from trpc init and protected procedure.

Then let's go ahead and export constage router to be create trpc router status is going to be protected procedure query

asynchronous method and then in here let's open a try and catch block return null in the catch block and in the try attempt to get the

result from await get usage status and return the As simple as that. So we we don't really worry about catching these errors and displaying something since this is a

query, right? So once you've done this,

query, right? So once you've done this, go ahead and add that to your TRPC routers here. So usage usage router.

routers here. So usage usage router.

There we go.

And yes, if you want to, you can move everything usage related into this module. Perhaps you can move it out of

module. Perhaps you can move it out of the usage. Um,

the usage. Um, for now I will leave it here. But yeah,

if you want to you can create a lib here. I think it will make more sense

here. I think it will make more sense actually. Uh, okay. Now that we have

actually. Uh, okay. Now that we have this, let's go ahead and let's create the usage component. So this one will be

interesting. Let's go inside of source

interesting. Let's go inside of source modules projects UI components and let's create usage.tsx.

create usage.tsx.

So in here I want to create an interface props which accepts the points and milliseconds before next refresh.

And for the imports let's go ahead and let's import link from next link. Let's

go ahead and let's import the crown icon from lucid react and let's import format duration and interval to duration from

date fns. And finally the button from

date fns. And finally the button from components UI button. Now in here, let's go ahead and let's add the usage like so.

Let's return a div with a class name rounded top extra large bg background

border border bottom zero adding 2.5.

another div inside with a class name flex items center and gap x of two

in here a div which will have one more div inside and this inner deal div div will have a class name of text small and let's go ahead and simply render the

number of points that we have and then let's just say you have that many free credits remaining and you can change this into a paragraph so we don't use so

many divs. And then after that add another paragraph with a class name of text extra small

and text muted foreground and inside resets in then add a space like this

open curly brackets format duration inside of it interval to duration

and set the start to be new date and set the end to be new date and inside date

dot now plus milliseconds before next and then add a new prop here I mean a new param in this interval to duration

uh my apologies format duration function which takes the format to be months days and hours so it's going to display in

those intervals.

And I think that's it. I think that's all we need. And then outside of this div right here, go ahead and add a button and a link

inside. The link will have an href to

inside. The link will have an href to the pricing page. We're going to render a crown icon and text upgrade. The

button will have an as child size small variant will be the new one that we created tertiary I I guess class name ML

auto now that we have this let's go ahead and display the usage prop

in order to display it we have to go inside of our messages form component so it is inside of projects UI

components message form and then let's go ahead above this and let's do show usage and end and then render the usage

like so. Import the usage from dot /

like so. Import the usage from dot / usage passing the points to be zero and milliseconds before next to be zero.

Just make sure you have imported the usage component.

So now go into any random project that you have. So I'm going to go ahead and

you have. So I'm going to go ahead and go inside of this one that I already have. And once this loads, nothing

have. And once this loads, nothing changes. But if I go inside of the

changes. But if I go inside of the message form and if I change the show usage to true.

And if I refresh, you will see zero free credits remaining, resets in nothing, and we have a button to upgrade. That's

what I wanted to see. And now what we're going to do is we're actually going to fetch the usage from our new router. So

let's go ahead to the top here and before the form, let's do const data usage to be use query which you can

import from let me just find tanstack react query.

Here it is.

And in here pass TRPC usage status like this query options.

And once you have the usage, let's go ahead and define the show usage to be double exclamation point and then usage.

And then in here you're going to do if you pass the points to be usage remaining points and in here usage milliseconds before next.

And now let's refresh.

And it looks like it does not exist yet.

I think that is because yes, so it's not going to exist right now because in order for this to be written to the database, let me just start npx Prisma

studio so you can see what I'm talking about. We have this new model called

about. We have this new model called usage. Right now we have the fields key

usage. Right now we have the fields key points and expire but nothing exists here. It will not be created by itself.

here. It will not be created by itself.

It will be created after the very first consume credits function is called. So the first time we do dot

is called. So the first time we do dot consume and take some points that's when it's going to be stored in the database.

So let's go ahead and let's do that. So

the first one we can do it for is the messages procedures. So let's go inside

messages procedures. So let's go inside of messages server procedures and in the create here let's go ahead and do that.

So before we even create the message here let's go ahead and let's do await and

let's call consume credits like so. And now this will already work.

like so. And now this will already work.

But I want to do I want to just wrap this into try and catch because get usage uh usage.conume

will have an error object right because there is an error that we have to catch and that is the error which says you have no more points. So we have to catch

that here.

So let's do it by wrapping this inside of try like so and then open catch and let's get the error. And the first thing we're going to do is we're going to check if

error is actually an instance of error.

This is this basically means that something else happened. Right? This

doesn't mean that we hit a rate limit.

This just means something literally failed. Maybe it's the database

failed. Maybe it's the database connection, right? Because this works by

connection, right? Because this works by connecting to the database. So maybe

that failed. So it would be incorrect to just throw a TRPC error saying rate limit exceeded if consume credits fail.

That's why in here I will throw new ERPC error here with the code

bad request and a message something went wrong. Right? So I have no idea what

wrong. Right? So I have no idea what happened here but it's not something we expect otherwise it is so if it is not an

instance of error that means this is the rate limit response. So in here add a code too many requests and add a message

um let's see you have no more or maybe you have run out of credits something like that basically a message indicating to the user that they have no

more points and now just for fun I'm going to modify my usage here and I'm going to set uh two free points so let's go ahead and try it out now I'm

going to do build a landing page.

Something that reliably works for me.

And we should have done uh one thing here immediately. Oh yes, we forgot the

here immediately. Oh yes, we forgot the ingest API. We forgot to do that. npx

ingest API. We forgot to do that. npx

inest cli. Whoops. My bad. And let me just refresh this. And you can see that now when you refresh you you have one free credit remaining which resets in 29

days and 23 hours. Do I have my Prisma Studio running? I do. So if I go here

Studio running? I do. So if I go here now in the usage and reset once we call this consume method, you can see that I have a key which is the user ID with

some prefix here. And I can see when this will expire and I can also see how many points I have spent so far. So I

only spent one point so far. And

basically that is how this is going to work. So just for fun I will do build a

work. So just for fun I will do build a landing page again. And this time uh I will refresh and I will have no free credits remaining. So if I try one more

credits remaining. So if I try one more time I should get the error you have run out of credits and we just got that error. Amazing. You successfully

error. Amazing. You successfully

implemented usage. Now you probably noticed a little bit of a weird thing here. Uh and that is that uh we call

here. Uh and that is that uh we call consume credits before we even know that this succeeded.

So if you want to, you could move this consume credits function into the background job and then only consume the credit after you successfully save the

result to the database. It will depend on what you want to protect, right? If

you want to protect your resources, you will most likely add this before you even call uh something like inest. and

you don't even want to spend any open AI credits if someone doesn't have enough credits, right? But if you were to pass

credits, right? But if you were to pass this in the background job, you would also need to pass the user ID in the event data. So just be mindful of that.

event data. So just be mindful of that.

And then you would also have to modify the consume credits overall because in here we rely on it using ALF and I'm not sure how this will work if it is invoked from a background job. I just think that

it will throw user not authenticated because background jobs by default are not authenticated. They are like web

not authenticated. They are like web hooks. Right? So that's why I decided to

hooks. Right? So that's why I decided to do it in the procedure rather than in the background job. What we have to do is this to-do right here. So let's

remove this. Let's add queryclient dot invalidate queriesc usage status query options like this. So

now when we create a new message, we automatically invalidate the queries.

And one easy way to reset this is to just go inside of your Prisma Studio and just bring back the points for your user to be zero and click save. And this way

you will not have spent any points. So

you are now back at three credits remaining. So if I go ahead and do test

remaining. So if I go ahead and do test and send the message now, it should automatically upgrade. And there we go.

automatically upgrade. And there we go.

You can see now it says one. So that is thanks to this invalidation here. And

now we can also fix this to-do here as well. So if error.code

well. So if error.code

error data question mark code is equal to too many requests, let's go ahead and do router. We don't have router. So

do router. We don't have router. So

let's add it. Con router.

Use router from next navigation. Make

sure you add this import.

And just do router.push. push forward

slash pricricing like that. So now when you hit too many requests it will yeah you can see this is kind of the not perfect thing. If you spend your credit

perfect thing. If you spend your credit on a bad query we take the credit away from you and you don't get the result.

So yeah not exactly perfect but I think it's pretty good so far. Right. Let's go

ahead and just I purposely just going to use stupid queries now just to get that error. There we go. So once I run out of

error. There we go. So once I run out of credits, I'm redirected to the pricing page. Perfect. Now there is one more

page. Perfect. Now there is one more place where we need to do this exact thing. So I'm going to go inside of

thing. So I'm going to go inside of messages procedures just so I can copy the try and catch for my consume credits

here. And now let's go ahead and go

here. And now let's go ahead and go inside of projects server procedures.

procedures right here and find the create right here and simply call that

try catch before you create a new project and import consume credits like this and then go inside of project

form component and we have to do the same thing.

So first things first query client invalidate queries and pass in gRPC usage status query options

and then in here if error data code is too many requests do router.push/pricing.

do router.push/pricing.

There we go. So now you have the exact same thing happening from here. So if

you try test from here same thing happens. you have run out of credits and

happens. you have run out of credits and you are redirected here. Amazing. But

right now, if we were to upgrade, nothing would change. So, let's go ahead and fix that. So, in order to fix that, we have to go inside of our usage in the

lib here. And then what we have to do uh

lib here. And then what we have to do uh is we have to get inside of here the

status. So has and let's do await out

status. So has and let's do await out hon has premium access will be has plan

and then pro. So how do I know it's pro?

Well because inside of the clerk configuration here the slug is pro. So

maybe has row axis would be better. And

then what I'm going to do is I'm just going to add const pro points 100 like this. And then if I have has proaxis,

this. And then if I have has proaxis, it's going to be row points. Otherwise,

it's going to be three points like that. And here's a quick tip. If you also want to change

quick tip. If you also want to change the duration uh which I wouldn't recommend you know there there's also if you look at all of these apps chat GPT

uh claude lovable bolt replet I've noticed that not all of them have annual plans and the reason for that is it is safer for them to bill you monthly uh

simply because they don't know how many credits you can spend right so that's why I recommend not changing the duration for the proaxis. But even if you wanted to, the way this rate limiter

works is that it will not update the duration, right? So you can update the

duration, right? So you can update the points in the middle of an existing uh database record for rate limit, but you cannot do it for expiration just if you

in case you were interested. But in this case, it doesn't really matter because it is safer for us business-wise to track monthly usage and do monthly billing, right? Even if a user is on an

billing, right? Even if a user is on an annual plan, we're just going to give them the same amount of points, 100 points per month.

Great. Now that we have this done, uh let me just check. I think this is the only place here uh where I have to do that. And now

that. And now let's just do a comparison. So when I click on one of my projects, it says zero free credits remaining. I will

click upgrade. I will click subscribe.

Pay with the card.

That's it. That was clerk billing. I

don't know if you just saw that, but that was it. I can now go inside of my manage account billing and I can find that I am on this plan. And from here, I

can see cancel subscription. I can

switch. I can remove monthly, whatever I want. So, I think this is insanely good.

want. So, I think this is insanely good.

And let's see if it worked. So, right

now, uh I think that there we go. I have

to refresh, right? And then it says 96 free credits remaining. So we are have officially upgraded, right? I can now send another broken message. And it

works. So we successfully added 100 points. It seems like it has subtracted

points. It seems like it has subtracted the existing points we spent during the free trial. So that's something we could

free trial. So that's something we could improve, but overall it works. When the

user is pro, we use a larger amount of points here. So now what we have to do

points here. So now what we have to do is we have to change the text. This is no longer free credits. This is just credits now. And

credits. This is just credits now. And

we can also remove all of the upgrade buttons. We no longer need them. So

buttons. We no longer need them. So

let's do that. I'm going to go inside of the usage.tsx

the usage.tsx here and let's see. So this is from out which means that in here we can access

has from use out from clerk next.js. JS

I have has here so I'm going to change let's go ahead and do const is has pro access has

plan pro has question mark plan pro so let's check if has pro access then it's an

empty string otherwise it is free so now it should just say 95 credits remaining.

No free credits. And let's hide this button if I don't have Pro Access.

So, only show this for users who don't have Pro Access. There we go. And let's

do the same thing in the project view.

So, I'm just going to copy this. Go inside of the project

copy this. Go inside of the project view. I'm going to add it here.

view. I'm going to add it here.

I'm going to import use out from clerk nextjs.

I'm going to move it here.

And then if I don't have pro access, I'm going to show this button right here. So

if I don't have pro access only, then show the button.

And there we go. Now the button doesn't exist. If you want to use the reverse

exist. If you want to use the reverse logic, if you want to create const is free tier, then you would do has plan.

And be careful here. It's not free, right? You always have to go inside of

right? You always have to go inside of here, inside of your plans, select the plan you want, and then check the slug.

It's free user. This is the one you want. That's the mistake I made when I

want. That's the mistake I made when I developed. So, I'm just warning you. But

developed. So, I'm just warning you. But

in here, we used this type of logic, so it's fine. And I think that that might

it's fine. And I think that that might be it regarding billing. It was that simple to do. I'm super impressed by this. No web hooks, nothing. I mean, 90%

this. No web hooks, nothing. I mean, 90% of this chapter wasn't even adding billing. It was adding usage rate

billing. It was adding usage rate limiting, right? So amazing, amazing

limiting, right? So amazing, amazing job. I am super satisfied by this. So

job. I am super satisfied by this. So

let's go ahead and merge all of this. I

believe this chapter is called 17 billing and let's just mark things. So

we enabled billing, created pricing page, added rate limiting, updated the Prisma schema, created the util, we created the usage component and we updated procedures to trigger credit

spend. Yeah, one more thing I wanted to

spend. Yeah, one more thing I wanted to show you. If you want to, you can also

show you. If you want to, you can also implement something like usage procedure and then in the middleware you could check for the uh consume status. Maybe

that would be something fun for you to try and then you would have a more abstracted routers and procedures to work with. Maybe homework for you if you

work with. Maybe homework for you if you want to after you finish the project.

Okay. So, I'm going to go ahead and I will create a new branch here.

17 billing like this. I'm going to stage all of my changes. I'm going to add 17 billing commit here and I'm going to publish the branch. Just a quick

reminder, there's a code rabbit free extension which you can use and it will review all of your files for you.

Now, let's go ahead and let's open a pull request here and let's see the summary of our billing changes.

And here we have the summary. We

introduced a usage and credit tracking system for users with separate limits for free and pro plans. We added a new pricing page with dynamic theming and a

detailed pricing table. We added a usage component to display remaining credits and reset time with an upgrade prompt for nonpro users. Navbar now dynamically

changes style based on the scroll position. As per the enhancements,

position. As per the enhancements, project and message creation now deducts credits and prevents actions if credits are exhausted, redirecting users to the

pricing page when necessary. Usage

status is now displayed and updated in relevant forms and components. The

upgrade button is only shown to users without a pro plan. Perfect. So, that's

exactly what we did. As always, an more in-depth walkthrough here. And we have a sequence diagram uh explaining exactly what happens. Right? So when user tries

what happens. Right? So when user tries to submit a and create a new project or a message, we call the mutation. In here

we call the consume credits and in here we call the database to check and update the usage. And then if we have credits

the usage. And then if we have credits available, we return with succeed with success and we proceed with the creation. And then we simply return the

creation. And then we simply return the creation results. But if credits are

creation results. But if credits are exhausted, we throw too many requests and we redirect the user to pricing.

Amazing. Exactly what we developed. And

in here we also have the diagram for our get usage status method which simply reads the usage and it returns it.

Perfect. So in here we have some actionable comments. This one recommends

actionable comments. This one recommends adding a default false in case has doesn't load, which is actually a good advice. We could do that

advice. We could do that in here. It recommends wrapping this

in here. It recommends wrapping this instead of try and catch and fall backs to soon.

So, this can happen if the dates are incorrect. And yeah, this this might be

incorrect. And yeah, this this might be a good idea because it's kind of u weird that the entire page fails just because

the date renders incorrectly. So we

could actually do this in the next chapter. Uh so we ensure some kind of

chapter. Uh so we ensure some kind of security here so it doesn't ruin the entire experience. And then in the

entire experience. And then in the usage.ts for all of the function, it recommends adding error tracking. But we

don't have to do that simply because we track in the TRPC.

So I'm going to merge this pull request.

I'm going to keep in mind the changes it suggested. And then I'm going to change

suggested. And then I'm going to change back to my main branch here. And I'm

going to click on the synchronize changes button. And okay. And then I

changes button. And okay. And then I will confirm that we just merged that right here. 17 billing. Perfect.

right here. 17 billing. Perfect.

Amazing. Amazing job. We have finished billing. and see you in the next

billing. and see you in the next chapter.

In this chapter, we're going to implement agent memory. Right now, our agent and our conversations have no context. The agent doesn't know the

context. The agent doesn't know the history of our conversations. It doesn't

understand our previous messages. Each

message is technically a new project.

Let's test that out. As always, ensure that you are on your main branch and feel free to synchronize the changes.

The last chapter was 17 billing. Make

sure you have npm rundev and injust running. And what I want you to do is I

running. And what I want you to do is I want you to create a very simple build a landing page. The one we already did

landing page. The one we already did hundreds of times. So go ahead and build a simple landing page.

And here we have a very simple landing page. What if I just send it a message

page. What if I just send it a message make it red?

What we would expect to happen is that it would understand that my previous message was build a landing page and it will now change it to red. But the truth

is that won't happen. Right? So it

didn't modify the landing page. It's

simply updated the page to be a red themed page. If I add add a calculator

themed page. If I add add a calculator there, it won't understand what I mean, right? But what we want to is basically

right? But what we want to is basically a landing page like this colored in red or if I tell it build a

calculator in the hero segment, I want a little calculator here instead of the rocket. So let's go ahead and make that

rocket. So let's go ahead and make that possible. The reason we really really

possible. The reason we really really need this is not for continuous conversation. The more important reason

conversation. The more important reason is AI can make mistakes. You already saw that sometimes it forgot to add use client. And we want to allow our users

client. And we want to allow our users to say, "Hey, you made a mistake. You

forgot use client." Because if I just give it that right now, it will have no idea what I'm talking about. it has no idea that previously it created this. So

let's improve that. Let's fix that. What

we're going to do is the following.

We're going to go inside of our functions.ts and in here let's go right after we do

sandbox ID and let's do const messages and let's do await step.r run

get previous messages. It is an asynchronous arrow function like this.

And then let's do con formatted messages and give it a type of message which you can import from inest agent kit. So I

can put type message here as well and set it to be an empty array. And now

let's fetch the messages using await prisma messages and let's do find many

where project id is event data project id like that and let's add order by

created at descending and then for con message of messages let's go ahead and push each message to

this new array. The reason we are doing it like this is so that we have this type which cannot be uh broken. So

formatted messages dot push type text

ro if message ro is assistant it will be lowerase assistant otherwise lowerase

user like this. And the content is going to be message dot content. And let me just see what uh what is the problem

here. Formatted messages. Oh, it should

here. Formatted messages. Oh, it should be an array of message. My my apologies.

There we go. And then let's go ahead and let's return formatted messages.

There we go. So right now it it is going to have context of the entire conversation. and it will know exactly

conversation. and it will know exactly what we told it. It will know exactly what it responded back to us. So now

let's go ahead and let's create a const state to be create state from agent kit again. So create state from inest agent

again. So create state from inest agent kit.

Let's give it a type of agent state.

So we already have agent state defined right here. We have summary and files.

right here. We have summary and files.

And let's go ahead and do the following.

The first object in here will be summary make it empty and files make them empty.

And then in the second object you will have messages which will be previous messages.

And now we have the state. So now we have to add this state uh to a couple of places. So let's go ahead and find our

places. So let's go ahead and find our network execution right here. And let's add default state

right here. And let's add default state to be the state from above. And in the result when we run it, let's add state

and make it state or you can use the shorthand operator like this. Perfect.

So let's go ahead and do this again.

Build a landing page. Let's do that.

Build a landing page. And let's follow the context here. As you can see, we now have a step get previous messages. And

you can now see that we included all of the new all of the older messages even the responses and the user messages here. So now the code agent as you can

here. So now the code agent as you can see has messages. So it knows exactly what we ask it to do now. So let's go

ahead and wait for this result right here.

We should see it any second. And you can see how it preserved the red color because that's what we asked previously.

So it already knows the context. And now

this is what I'm going to say. Add a

calculator in the hero segment.

So I didn't tell it to create a landing page. I didn't tell it anything other

page. I didn't tell it anything other than this. And let's see how well it

than this. And let's see how well it will do.

Of course, it can make a mistake even at this point. But now it has the context.

this point. But now it has the context.

It knows that add a calculator in the hero segment is the message after build a landing page and it is a message after

uh created a polished fully responsive red themed landing page. So let's see if it was able to do this or not.

We should see any second now. Uh and

looks like it wasn't able to do it. Let

me refresh just in case and let's see the code.

Looks like we didn't add it. So,

let I just want to make sure that I didn't accidentally maybe do the incorrect order of loading the messages here.

Created at descending. I think that should be okay. Let's try again. You

didn't add the calculator component in the hero page. add the calculator component.

So in the previous examp when I tried this privately off camera, it worked for me. So you can see that AI is sometimes

me. So you can see that AI is sometimes a bit unpredictable, but I just want to tune it, you know. Uh we might even have to modify the prompt for this. We might

have to tell it you have context of all older messages. You can use older

older messages. You can use older messages, right? But you can already see

messages, right? But you can already see a slight improvement, right? Because

right now when we asked it to create that landing page, uh it's it made it red, right? So it understood the

red, right? So it understood the context, but I have a feeling it is still not understanding entirely what we want. Let's see if this will be better.

want. Let's see if this will be better.

And there we go. We now have a very very simple calculator in our landing page.

Great. So this is exactly what we asked.

Let's try and do make it green now just to make sure that this is the last message it sees. Right? I want I'm okay with adding state. I just don't want to

make it so that it conf it's confused about what is the latest message. So

let's follow along in the running here.

So yes, make it green is the first message in the array here. Maybe that

should be the opposite. I don't know because maybe it's now thinking that this the last message in the array is the newest one. And let's see the coding

agent. I think the coding agent

agent. I think the coding agent understands the same thing maybe or not.

Yeah, in here it also the last message here is build a landing page. So I keep thinking that maybe or maybe not. Yeah, you can see that it preserved the fact that it's a landing

page. It added the calculator and it ma

page. It added the calculator and it ma changed it green. So obviously now it understands what we are doing. So I'm

going to go ahead and add a little to-do here.

To-do change to ascending if AI does not understand what is the latest message.

But I think that it works pretty well. I

think that it understands that make it green refers to the landing page which I scolded it for because it didn't add the calculator. So now it has both the

calculator. So now it has both the calculator and it is green. And I think that's exactly what we wanted. So let's

try and just do one more time make sure to use separate component files.

And while this is happening uh let's go ahead and do the following. I want you to visit my uh public assets folder or the source code. You can use the link in the description or the link you can see

on the screen. And in here I added a new file called additional prompts. And in

here I have the response prompt and the fragment title prompt. So go ahead and copy this entire file. Go inside of your prompt.ts and at the end of it or if it's easier

you know at the top just add those two and export both of them. So export the fragment title and export the response prompt. We're now going to use this to

prompt. We're now going to use this to create two more agents so they create better responses and so they create a proper name here.

So let's see what it did. There we go.

So it understood the context. It it

seems to again made it red. Uh I keep thinking that the way we are loading these previous messages maybe isn't perfect. So, you're going to have to

perfect. So, you're going to have to tweak that a little bit or maybe you can somehow um maybe you can somehow modify it so that

it knows you can add it in the content like maybe let's see I'm thinking of keeping a track of the index and then maybe we can modify the

content and like say first message and then the message content like something like that and then maybe In here we can keep track of index and

then replace this with the index for example. Maybe that can instruct it

example. Maybe that can instruct it better to understand what's going on.

But uh what's important is that it at least understands the last message right that's what I want to make sure. So I

just told it to use separate components and that's exactly what it did. It

preserved the fact that it's a landing page but it made it uh into separate components. Let's do make it green or

components. Let's do make it green or let's do make it yellow. this time you know keep testing it make sure that it's listening to you and you can play around

with changing the order here you can use that idea that I told you uh you can even explore you know in justest documentation about the state maybe in there we can find something uh I will of

course research off camera and in the next chapter I will tell you more information if I find out anything new but what I want to do now is the following

in the function here after the network finishes. So right here after we get the

finishes. So right here after we get the result, I want to go ahead and I want to

do uh the following. So I want to create an agent called fragment title generator and that will be create

agent like this. And let me just see I already

like this. And let me just see I already forgot how do we create agents.

So the name will be something and then the description and then the system and then model. Okay. So I will just copy

then model. Okay. So I will just copy this.

Let's add it all over here. So the name will be fragment title generator.

A description will be a fragment title generator.

And for the model we can use open AI and you can use a cheaper model. You can use 4.0 for example. And you can remove the default parameters here. So in here use a cheaper model because this will just

generate text and in here go ahead and use the fragment title prompt which we just added and also now import the response prompt.

So let's go ahead and use this. Now

after this go ahead and copy it and now this will be response generator.

Change this to be response generator and a response generator and change the system here uh to be

response prompt. And now you're going to

response prompt. And now you're going to define two outputs. The first output here will be t uh fragment title

await fragment title generator run. And inside of here you will pass

run. And inside of here you will pass the result state data summary.

And then you're going to copy this. And

this will be response response generator from the same thing.

And then you're going to go inside of save result.

And for the fragment title, go ahead and do the following. Fragment title dot uh first in the array dot type is equal to

text fragment title first in the array dot content otherwise just fragment like this and let me just see content is

this um okay let me just build a little function uh so we don't have to do this in one

file so we have fragment title output and we have response output here.

So I'm going to just collapse this and then in here I'm going to do con generate fragment title

and I will do if fragment title output type is not equal

first in the array of course the text return fragment ment otherwise let's go ahead and do if

fragment title generator first in the array content and let's check maybe if is array

can I do this uh array is array I think fragment title output because this can

be an array Okay, then let's return fragment title output first in the array.content.m map

array.content.m map

text join like this.

So basically this should always return a string and otherwise just return in the else here just return the content

and let me just see so this is a type of string here uh and well you can just add another safety one fragment And okay, but this is unreachable. So I don't know why

is unreachable. So I don't know why exactly uh string is not uh okay. Yeah, my bad.

And now go ahead and copy this and call this generate response. And basically the same thing.

response. And basically the same thing.

So response output.

And in here we're going to just set the default to be here you go.

Otherwise it can be this. And now when we have those two in here we can put generate fragment title.

And in the content we can do generate response.

There we go. And this should now improve uh our app. So let's see if that is uh true or not. So I'm going to do build a let's actually start a new project just

to make sure everything is clear. So

build a calculator app like this. And let's follow the ingest

like this. And let's follow the ingest to see if we're going to mess something up or not. I mean technically I don't think it will ever be an array of items

that we're going to have to join like this. It will almost always certainly be

this. It will almost always certainly be just this, but uh yeah, I guess we're trying it out. So, we successfully did

this fragment title and we successfully did response generation. So, let's see what it came up with. There we go.

Here's what I built for you. A snazzy

calculator app with a sleek responsive design. And we have a name this time for

design. And we have a name this time for the fragment calculator app. So that's

what I wanted us to achieve, right? If

you don't want this, you don't have to use it. I mean, the app worked just fine

use it. I mean, the app worked just fine before this. So if this for some reason

before this. So if this for some reason messes up your app, you don't have to use it. Of course, I just thought it

use it. Of course, I just thought it would be, you know, fun to add that as well. And you can definitely write this

well. And you can definitely write this in a better way. I mean, starting with the fact that we can just do con title and just make it this.

So let's do output and just make it this like so.

There we go. This is already better. And

then you can do the same thing here. Con

output.

Change this to this.

And then replace all instances here.

There we go.

And you can probably also just use a single function because it's exactly the same. So let's maybe do parse

same. So let's maybe do parse uh agent output. And in here let's go

ahead and see this is a type of uh message or array. So let's try value message

array like that. And then can I just use value con output value first in the array? Yeah, that

works as well. So parse agent output then is the only function you need and you can then put it maybe at the top of the file and we are later going to

move it to libs.

So basically parse agent output and in here it accepts the value which is a message which is an array. The message

is a type of message from agent kit. And

now that we have parse agent output.

Let's go down here and keep the fragment title generator, keep the response generator, keep the two outputs and remove generate response function.

And now what you're just going to do is parse.

How did I call the function? Uh, parse

agent output.

And in here, you're going to parse response output.

And in here, you're going to pass fragment title output. There we go.

And then you can move this function to utils here in the ingest.

So export const parse agent output and import message from inent uh inest kit here. There we go.

here. There we go.

Then you can remove it from here. Find

some place you're using it. And then

there we go. Import from utils like this. And let's just do a sanity check

this. And let's just do a sanity check here. Uh make the calculator use glossy

here. Uh make the calculator use glossy glassy design or something like that.

The only thing I'm trying to do here is again confirm it can kind of understand the context of my previous messages and that it will give me a nice response

with a name for my fragment. So we can follow again inside of the running here to make sure that's what's happening.

There we go. Fragment title generator response generator.

And let's see. Here's what I built for you. A sleek glassy design calculator

you. A sleek glassy design calculator app with a modern twist. Very cool. So

it understood that we just wanted a glassy design on top of our previous app.

Amazing. I'm very very satisfied with this. Uh, and I think that marks the end

this. Uh, and I think that marks the end of this chapter. As always, you know, your app worked just fine up until this point. So, don't let uh this ruin your

point. So, don't let uh this ruin your project if you don't like it or, you know, feel free to research a bit yourself about whether this should be

descending, ascending. Uh, and if this

descending, ascending. Uh, and if this is failing for whatever reason, the fragment title generator and the response generator, you can also remove them. You don't need them. And make sure

them. You don't need them. And make sure to just use a cheaper model here simply because you can even use an entirely new model, you know, like uh Gemini, Grock,

whatever, because we are not calling any tools here. So, just make sure this is

tools here. So, just make sure this is something cheap so it doesn't spend your credits for no reason when it's just generating some text. Uh, amazing. So,

18 agent memory. Let's go ahead and review that. I'm opening a new branch

review that. I'm opening a new branch here.

18 agent memory.

I'm going to stage all of my changes. 18

agent memory. And I'm going to commit and I'm going to publish the branch.

Let's go ahead and open a pull request.

And let's review our changes.

And here we have the summary. We

enhanced message handling by retrieving and formatting previous messages for improved agent interactions. We added

automated generation of concise userfriendly summary messages and short descriptive fragment titles. We updated

the process for saving results to use dynamically generated summaries and titles instead of static content. And we

introduced utility functions and structured prompt templates to standardize agent outputs. And in here we have just one comment and that is that we should probably check if the

value is actually valid. Um so yeah we could add this as well and I just noticed that we return the text fragment as placeholder even though we use this

both for the response and for the fragment title. So, I should probably

fragment title. So, I should probably add a new value, something like fallback value, something like that. We'll see.

Uh, nevertheless, very satisfied with this one. I will research before the

this one. I will research before the next chapter if there's something we can do uh better when it comes to adding message history. But I think this is as

message history. But I think this is as good as we can do right now. Uh, great.

So let's go ahead and change this to main and let's synchronize our changes and we should see our new poll request being merged. Amazing. Uh I believe that

being merged. Amazing. Uh I believe that marks the end of this chapter. Amazing

amazing job and see you in the next one.

In this chapter, we're going to go over some final bug fixes and improvements in our application. This will include

our application. This will include learning how to increase the sandbox expiration, making A2B template private so no one else other than your team can

use it, improving our conversation history from the previous chapter, and overall error handling improvement in our application. So, let's start by

our application. So, let's start by learning how to make our sandboxes last longer. As always, ensure that you're on

longer. As always, ensure that you're on your main branch and make sure you have synchronized your changes. The last

chapter we we merged was 18 agent memory. So let's learn how to increase

memory. So let's learn how to increase sandbox expiration. Right now inside of

sandbox expiration. Right now inside of our application if I visit uh even this one 4 minutes ago, okay, this one works.

But if you visit anything older than 5 minutes ago, it will show this the sandbox wasn't found, which is not ideal if you're doing some presentation or if you're showcasing this to someone. So

here's what how you can increase your timeout. So the sandbox life cycle it

timeout. So the sandbox life cycle it will stay alive for 5 minutes by default but you can change that using the timeout parameter. Now of course

timeout parameter. Now of course depending if you are on free tier or if you're on premium tier you have different limits. On premium tier you

different limits. On premium tier you can keep it alive up for 24 hours but on free tier which we are on we can keep it alive for 1 hour. So for example let's

find something in between. Let's use uh half an hour. I think that's a fair amount. So all you can do is go inside

amount. So all you can do is go inside of your functions inside of the ingest folder and in here when you create it go

ahead and do await sandbox dot set timeout and now you have to enter in milliseconds right and in here you can

see so the maximum time a sandbox can can be kept alive is 24 hours for pro users and 1 hour for hobie users. So, if

you want to, you can set this and then this will be alive for an hour. But keep

in mind, the longer you put this, the more credits are you going to spend. So,

what I recommend is, you know, find a middle ground.

This is half an hour. So, you can put this instead.

Uh, and you also have this util here, I believe, get sandbox. Now, in here,

it's also important to increase it, but you don't have to put half an hour here, but um yeah, let's be consistent. And

let's put half an hour here as well. I

just want to be careful and I don't want you to spend all your credits, but perhaps maybe add this to some kind of constant here. Types.ts DS export const

constant here. Types.ts DS export const sandbox timeout and add a comment 30 minutes in

milliseconds like this. And then you can consistently use it in different places and you can easily change it if you change your mind. Let's go ahead and add

that here. Sandbox timeout. There we go.

that here. Sandbox timeout. There we go.

So now we know how to increase our sandbox timeout. And I will try this

sandbox timeout. And I will try this out. I will do build a uh calculator app

out. I will do build a uh calculator app here and you know at the end of this chapter I'll I'll see if it still exists if it manages to do it without errors.

So let's see what else do we have to do here. We just learned how to increase

here. We just learned how to increase our sandbox expiration. Uh I'm going to leave this for last simply because we are in the middle of generating. Uh so

let's go here. Improving conversation

history. So in the previous chapter we learned that we can add previous messages and we can add it to the state and I've been experimenting a bit and I

think I found kind of a perfect combination.

So what I think we have to do is we have to limit our messages history because the longer the history the more confused the model gets. At least that was my

experience. So, I limited it to five

experience. So, I limited it to five messages. I'm pretty sure it would work

messages. I'm pretty sure it would work great with 10 messages also, but five was somehow the sweet spot where I was able to keep a conversation going and do

some small changes uh constantly. And

then the proper one is actually created at descending, but make sure that you do reverse in here. So, it's actually

ascending. You need ascending here. But

ascending. You need ascending here. But

since we're limiting the take, we need to offset, right? You can also do the offset thing. Actually, it is skipped in

offset thing. Actually, it is skipped in Prisma, but honestly, I'm not really sure if we need to do that. And we can just do it descending and then just

reverse them here. And this, in my opinion, gave me much much better results than anything else. So again, I invite you to experiment. But once I did

this, so I keep this at descending. I

limit this take to five and I reverse the formatted messages. And this made the AI understand much better what I'm building. So for example, let me do make

building. So for example, let me do make it glassy design.

I'm going to try and do this. So we're

going to see. Okay. So we did that. We

improved the conversation history. And

now let's talk about error handling. So,

uh, error handling in our app currently doesn't exist outside of TRRPC procedures, which means that all of our suspense can go wrong. So, here's what

we can do. We can do npm install react error boundary.

And let me show you my version here. So,

package.json

React error boundary 6.0.0.

And then let's go ahead and find a random suspense here for example for loading the project. So how about we add error boundary here

and wrap it in suspense.

This is how you do error handling if you want to do it on a segment level. Nex.js

also comes with its own error boundaries which are written the same as pages. you

would have error.tsx. You can of course add this as well, but I want to show you this. So, uh I'm not sure if this is

this. So, uh I'm not sure if this is error boundary from react error boundary.

Okay, I think it needs to be like this.

There we go. And then in here you would add a fallback error like this. So, first let me just check if this is working. There we go.

Glassy design. glassy calculator you can see it understands the context perfect so let me show you this now uh so first without error boundary so comment out

error boundary I'm working inside of project ID let's go inside of uh projects get one here and in the query I

want you to throw new tRPC error here with code bad request and refresh here And in here you can see

it's loading project. It's loading

project.

Oh uh yes my apologies. No u uh I I'm trying to demonstrate this but I forgot we commented out error boundary and this happens then this is obviously not good.

We don't want this to happen. That's why

we have error boundaries like this. So

go ahead and refresh now. And again it will keep trying to do this for 3 seconds for three attempts. one two

three and then finally it will hit the error and this is what you can then design as your error page right so what's important is that the error

boundary is around suspense now this doesn't make too much sense because I am wrapping the whole page

here right but imagine if course let's just remove this TRPC error from here imagine the case where I'm doing where

is it UI components where I have the project header here right so let me see where do I render project header in the project view so go inside of your

project view and in here we have suspense so the error boundary from react error boundary would make a lot of sense here fallback

project header error like that or in here error boundary again around this suspense

with the fallback messages container error.

So now let's say we go inside of the messages here. Let's go uh inside of

messages here. Let's go uh inside of messages get many and let me just try and do this. So throw new tRPC error here. code bad request. So something

here. code bad request. So something

happened here and let's go ahead and refresh here. You can see that the

refresh here. You can see that the project has loaded and you can see it's trying to load messages. It's trying to load the messages until eventually it fails. And since we added the boundary,

fails. And since we added the boundary, we only see messages container error.

But if you didn't have that here in the project view, if you didn't wrap the error boundary, let's see what would happen then. So let's refresh again. So

happen then. So let's refresh again. So

it's loading messages. It's loading

messages and you already know what will happen. The entire screen will error. So

happen. The entire screen will error. So

just like minimizing the loading state, you can use the error boundary to minimize the error state. So that's what you would do, right? That's why we use the error boundary. So I would

recommend, you know, finding all the suspenses that you use in your project.

And I think these are the only three. So

we just added to all of them, right? And

you can also do the following. You can

also add inside of source app. You can

add a page called error.tsx.

Now this has to be a client component and in here make sure to call it error page. Don't call it error. This is a

page. Don't call it error. This is a reserved keyword. Call it error. And you

reserved keyword. Call it error. And you

can return here global error. You can of course design this however you want. But

this is useful. So this right now doesn't do much. But let's go ahead and do this. Let's go inside of project ID

do this. Let's go inside of project ID and let's say we forgot to wrap this error boundary here.

So now uh and yes uh also go to project view and comment out the error boundary here.

Now the error the global error takes care, right? So that's how the global

care, right? So that's how the global error works. So if I didn't have this,

error works. So if I didn't have this, let me just remove it. Now if I didn't have this, this is what would happen.

Let's say we forgot all the inner error boundaries right?

This happens. So, this is the one thing you don't want to happen. This error

just looks ugly. It looks like something really, really broken, right? That's why

you want to make sure that you have the inner error boundaries. You want to make sure that you have your individual page error boundaries and you should also

have your app error.tsx

the sx simply because it is the last line of defense in case something goes wrong. Perfect. So now let's go back

wrong. Perfect. So now let's go back inside of our messages procedures here.

Let's just remove the throwing of the error here. There we go. Perfect.

error here. There we go. Perfect.

Now what I want to do is I just want to go to the usage dsx here component. And I'm really worried about this format duration for a

simple reason. Dates can often cause

simple reason. Dates can often cause errors and it would be very stupid if our entire app here fails just because

the date is invalid. So here's uh what I think we can do. Let's go ahead and try it. So

I'm going to open a function like this. And let me just close it here.

And I'm going to open curly brackets here. Let me close that here.

here. Let me close that here.

And I'm going to open Actually, here's what I'm going to do.

Instead of trying to do it here, I'm going to just do const reset time use memo like this. Make sure you added use memo

like this. Make sure you added use memo from React. And then inside of here,

from React. And then inside of here, what we're going to do is we're going to open the try and we're going to open catch. And in here, let's add an little

catch. And in here, let's add an little error. And let's do console error. And

error. And let's do console error. And

let's do error formatting duration error like that. And let's simply return soon. So it will just say or maybe

soon. So it will just say or maybe unknown or whatever you think is better user experience, right? And then in here

experience, right? And then in here let's return format duration and then inside interval to duration and then

start new date and let's me just copy it and will be this.

And then we're just missing the format here.

There we go. And in here let's add ms before next. And then we can use this

before next. And then we can use this constant here. So resets in

constant here. So resets in reset time.

Let me just check if it still works.

There we go. Resets in 29 days and 20 hours. Because now if you if this MS

hours. Because now if you if this MS before next happens to be something like not a number.

There we go. Resets in now doesn't work and it doesn't break the page.

That's what I at least wanted it to happen. Okay,

happen. Okay, perfect. So, now that we have this

perfect. So, now that we have this actually, I mean, we can try it out by doing throw new error. Whoops. And now

it should say resets in unknown.

Basically, it cannot block the page. It

can still allow the user to work, which is what we wanted. Um, perfect. So, this

is very good. Let's see what else we have in the list here. So we learned about improving the error handling and now let's talk about making our E2B template private. So if you go inside of

template private. So if you go inside of your E2B dashboard in here you can see your templates and you can see that my Vibe Nex.js test 2 is currently public.

That is because we have published it.

The reason I told you to publish it is because I personally had problems with private uh templates. But what you can do is the following. Open your terminal.

go inside of sandbox templates. Go

inside of Next.js and since inside of here we have a E2B TOML file. We can easily

just do E2B template unpublish. You

don't have to add any other flags. It

will read everything from here including your team ID. And just confirm that you want to unpublish it. And what this does is the following. If you now go back

here and refresh, you will see that my vibe next.js test 2 is now set to private. And that means

that inside of the functions on the ingest here, whenever someone tries to create a sandbox with that, this will

fail unless in their environment their E2B API key belongs to my organization.

So what I suggest you do now is definitely try. So let's try make it

definitely try. So let's try make it red.

And you can see that these sandbox are still going strong. So we def it definitely works what we increased the timeout for. So now what I'm interested

timeout for. So now what I'm interested in will this still work now that I have made my template private and I think that it does since I can fetch the sandbox ID. For me, it happened that the

sandbox ID. For me, it happened that the sandbox ID was not able to be fetched once I changed it to private. So, just

make sure that it still works here.

Let's see. Make it red.

There we go. And you can see how the conversation history is improved. It

made it glassy and red. Exactly what we wanted. So, we definitely improved the

wanted. So, we definitely improved the conversation history here. And for me, it seems to still be working. If for

whatever reason yours stopped working, it shouldn't but you know it happened to me so maybe it will happen to you. You

can easily go inside of here inside of next.js and just run E2B template publish. You don't need any arguments.

publish. You don't need any arguments.

You can just press publish and then you can confirm.

And then from here you can go back inside of here, go inside of your templates and see the status. Make sure

it's public. And you can even do that from here I think by clicking here if you want to. Um, perfect. Amazing. So I

think that's all we wanted to fix. And

there is one thing left to discuss and that is let's just search for to-do.

Instead of messages container, we're using refetch interval as the live message update. And I was thinking about

message update. And I was thinking about what I should do instead of this. But

initially I built a project like this and it worked just fine. So the thing is this is not a multiplayer chat. This is

a single person chat who receives responses from AI and most of the time you only send one message and then wait for the other message to uh come back.

And we are not just doing any kind of polling. We are doing polling using the

polling. We are doing polling using the tan stack query. This means that this refetch interval will ddup it will use cache. It will be an extremely optimized

cache. It will be an extremely optimized interval. And also, I'm not sure if you

interval. And also, I'm not sure if you knew this, but while right now new requests are being made every 5 seconds,

if I change to this page, requests stop being made. So, you don't have to worry

being made. So, you don't have to worry that in the background it's constantly going to fire. It is a very very optimized polling and it actually makes

no problem to use in our type of application here and in fact you can even reduce it to 2 seconds if you want to. This will give you a better

to. This will give you a better experience and it will still be very very optimized. Now in case you're

very optimized. Now in case you're wondering uh okay but does inest offer any uh realtime updates? They absolutely do.

You can go inside the documentation and read about a real time. So in here you can subscribe to a channel and then from the function you can initialize uh you

can publish to that channel. And I

explored this option but the problem was I wasn't able uh to synchronize both my Prisma messages and uh the ingest subscription messages. It is absolutely

subscription messages. It is absolutely possible uh and it can be a good homework for you if you want to give yourself a challenge and do that. For

our use case, polling is more than enough. So I will just remove this from

enough. So I will just remove this from here. And if you want to, you can move

here. And if you want to, you can move this into a constant. So you can change it easily if you're using some polling in multiple places. Uh great. Amazing.

So I believe we are now ready to deploy.

I don't think there's anything else we have to do here. So let's go ahead now and let's merge this. So 19 bug fixes.

I'm going to go ahead and open a new branch.

19 bug fixes.

I will stage all of my changes. 19 bug

fixes.

And I'm going to commit. And I'm going to publish the branch. Uh, and since these changes were very very minimal, I think we can just go ahead and merge

them because they were just some very small bug fixes here, we can do our own review here. So, we added React error

review here. So, we added React error boundary. We added global error page.

boundary. We added global error page.

We added some wrappers here, error boundary.

We added the proper sandbox timeout. We

properly uh reverse the messages. So the

conversation history is improved.

We use the same sandbox timeout in the utils here. We reduced the refetch

utils here. We reduced the refetch interval. We made it safely here to

interval. We made it safely here to fetch the reset interval. And we just added some more error boundaries. That's

it. Nothing else needed here. We can

merge this request immediately. Amazing.

Amazing job. That marks the end of this chapter. As always, go back to your main

chapter. As always, go back to your main branch here and synchronize the changes and then confirm in the source control that you just merged 19 bug fixes.

Amazing, amazing job. And see you in the next chapter where we are going to deploy our app.

In this chapter, we're going to go ahead and deploy our app to Verscell.

This will include the initial deployment. After that, we will obtain

deployment. After that, we will obtain our app URL. Then we have to update our environment variables with that new URL

and we have to redeploy and after that we have to connect inest to our versel project and redeploy once more and after that we are ready to test the app. So

let's start with step one deploy to versel. So make sure that you are on

versel. So make sure that you are on your main branch and make sure the last one was 19 bug fixes. You can

synchronize your changes if you haven't already. And at this point, you can also

already. And at this point, you can also shut down your terminal. No point to have this running while you are deploying.

Now, let's go ahead and make sure that you have a GitHub repository, right? I

don't know if you followed the Git workflow through this tutorial, but if you have, then you have a GitHub repository. So, head to Verscell and

repository. So, head to Verscell and click add new project. And in here, if you are connected with GitHub, you have your project right here. I'm going to

click import. And then I'm going to open

click import. And then I'm going to open environment variables. And in here, I'm

environment variables. And in here, I'm going to go into environment. And I'm

going to copy all of them. And then I'm just going to paste them inside. So

database URL, next public app URL, open AI API key, E2B key, and all of these other ones. And let's click deploy. We

other ones. And let's click deploy. We

are now going to see the result of this.

Perhaps it will succeed from the first try or maybe it will fail. I'm going to pause the screen and show you the result.

And my deployment failed with a bunch of errors and I forgot about the first rule of deploying to Verscell and that is that you should try building locally

first so you don't waste time with failed Verscell deployments. And we also forgot about one more important thing and that is that when we deploy the

versel, we need to add a post install script. So let's go ahead and do a post

script. So let's go ahead and do a post install script and add it to our package.json here. Let me just add it

package.json here. Let me just add it here.

Let me just check where do we add it in the scripts. Okay, so post install

the scripts. Okay, so post install prisma generate like this. So now I'm going to go ahead and do npm run build locally and I'm going to see if I have

any more errors and I'm going to show you how do you trigger a deployment again if your initial one failed.

Basically from here you have buttons to go to project or to inspect deployment.

You can click whichever one you like. Uh

and from here you basically have your project vibe. You can click here and

project vibe. You can click here and there we go. You have deployments. the

first deployment was failed and now the way another deployment will be triggered is simply whenever it notices a new push in the GitHub. So now let's go ahead and

see what we have here. So I seem to keep having these errors seemingly coming from here.

These errors seem to be coming from source generated. So it seems to be

source generated. So it seems to be linting this folder when it should not be linting this folder. So, I'm going to look into how I can prevent the app uh

the build from looking at the generated folder right here and I'll tell you what I find.

Okay, I have managed to find the combination which enables npm run build.

So, just to remind you, the last change we did was we added post install to the scripts. We still need this regardless.

scripts. We still need this regardless.

So there are different ways you can fix the failing lint because the failing linting is what's going on here. One

solution is to go to the next config here and add slint and enable ignore during builds. Now this isn't exactly

during builds. Now this isn't exactly recommended but you can see that once you add this slint will not fire during the build process or at least it won't

fire in production. But you can see that in here it works perfectly fine now.

Well, whereas if you don't have this feature and you try mpm run build, it is going to fail because it will try to lint our source generated uh folder.

There we go. But I don't really recommend this simply because uh that will turn off linting for your entire project during the build and there's no need for that because what we can do

instead is we can go inside of eslint.lint.config.mjs

eslint.lint.config.mjs

config.mjs here and we can open ignores and in here we can target asterisk asterisk forward slashgenerated and then

everything inside of a generated folder.

So basically ignore wherever you can find the generated folder and then everything inside. And I feel like this

everything inside. And I feel like this is a little bit better solution simply because we are still going to lint the rest of our project. But

we are not going to lint the source generated one. So if you try this now,

generated one. So if you try this now, it should work. Let's wait a second. And

there we go. Since the linting has passed, everything else seems to be working just fine. So try for yourself and choose one that works, right? If

this works for you, sure, then use this.

But I will recommend doing this if possible for you in the estate config mjs. Perfect. Once you have these two,

mjs. Perfect. Once you have these two, let's go ahead and let's merge them uh like this. And let's go ahead and add deployment here. We can do this in the

deployment here. We can do this in the main branch. We don't need to branch

main branch. We don't need to branch out. And then just go ahead and

out. And then just go ahead and synchronize the changes. In case you branched out, no problem. Just merge the branch to the main. And once it detects

a new branch to the main uh it is going to uh trigger a redeployment.

There we go. So in here you can see that it automatically detected a new push and it's going to deploy. So let's see if it succeeds this time.

And here we have it. This time the build succeeded. And now we have our app

succeeded. And now we have our app deployed. So you can see it's still

deployed. So you can see it's still running some final uh things here, but down here you can see that we assigned a custom domain and we have the deployment

summary. So what I care about is the

summary. So what I care about is the custom domain. So what I recommend you

custom domain. So what I recommend you do is you click on vibe up here and go inside of overview and in here you will find all of your domains. Do not use the

deployment domain. Use this one, the

deployment domain. Use this one, the shortest one that is the domain of your project. You can go ahead and visit it

project. You can go ahead and visit it if you want to. And what I want you to do is I want you to copy the URL that

you have and I want you to go back to Verscell and then go inside of settings environment variables and in here find

next public app URL and you now have to modify this to be your new app. Just

remove the forward slash like this and click save. And after you do this, you

click save. And after you do this, you can see a prompt here to redeploy. So

you can click it. If you don't get the prompt to redeploy, you can manually go in your deployments and then you can select the last one and you can click redeploy. So let me just check if uh I'm

redeploy. So let me just check if uh I'm not sure if redeployment is now happening or not. So I will just click redeploy myself and just confirm. So

every time you change environment variables, you have to redeploy. So I'm

going to pause, wait for the redeployment, and then we have to connect inest to this versel project.

And here we have the successful uh redeployment here. So now what I suggest

redeployment here. So now what I suggest you do is just try and use your application. If possible, wait out till

application. If possible, wait out till this shows a success message as well.

There we go. and then go to your main URL here. Always use the main URL and

URL here. Always use the main URL and try something simple like signing in. So

I'm going to go ahead and use my account here and I will try to just create a simple project. So I will go ahead and

simple project. So I will go ahead and do uh you can see that all of our projects are saved. So our TRPC is working here. Let's try clicking on one

working here. Let's try clicking on one of these and let's try sending a message. So I would add test here. And

message. So I would add test here. And

what should happen here is the following. You can see the message my

following. You can see the message my message was generated but injust was unable to be fired. That's because we need to connect inest to production. So

this will actually never finish. Let's

go ahead and do that now. So use the link you can see on the screen or the link in the description to visit ingest and go ahead and sign up. I suggest

using GitHub simply so you always have access to your repositories if needed.

And then once you enter here down here at the bottom you can choose to switch organization. I recommend creating a new

organization. I recommend creating a new organization. So I'm going to call this

organization. So I'm going to call this Vibe. And I will click create

Vibe. And I will click create organization.

And I'm not going to invite any members now. And let's go ahead and click on

now. And let's go ahead and click on apps here.

And let's go ahead and just find oh sorry integrations. I think this is what

sorry integrations. I think this is what I'm looking for. And let's click on Versel here to connect. Let's click

connect Verscell to ingest and click on add integration here. And I'm I have multiple accounts. So I'm going to

multiple accounts. So I'm going to select this one where I just deployed the project and I will click continue.

And in here go ahead and find your new project.

So let me just find Vibe. Here it is.

select it and click save configuration right here and click continue to the ingest uh Verscell dashboard. And now

somewhere in here you have all of your Versel projects and you should now find Vibe and you can see it says enabled but in here it says deployment protection is

enabled. Ingest may not be able to

enabled. Ingest may not be able to communicate with your application by default. Let's go ahead and click more

default. Let's go ahead and click more here. And basically what we have to do

here. And basically what we have to do is we have to configure protection bypass. So in here let's go ahead and uh

bypass. So in here let's go ahead and uh I'm never sure how to properly access this. So don't worry I will tell you the

this. So don't worry I will tell you the exact steps in the versel dashboard but let me just see does this take me to Verscel. It does

take me to Versel here. Basically I

think this is where I need to go. Let's

go to versel and let's select our new project here.

Let's go inside of settings and let's go inside of deployment protection. Here it

is. So go inside of your vibe settings deployment protection. And in here

deployment protection. And in here let's go ahead and let's do protection bypass for automation. So I think this is what we need. So let's click add

secret. You don't need to add any value

secret. You don't need to add any value and just click save. And then in here you can copy this bypass secret. And

then let me go ahead and go back and click click on configure here.

And if I remember this correctly, you should now have deployment protection key that you can add here and click save configuration.

There we go. So now inest should be able to access your uh Versell application.

And let's go ahead and do one more thing here. So now I want to go back here to

here. So now I want to go back here to production.

And in here, there we go. You can see that my app was already found vibe development on Versel. So this is a good sign. And it has found one function code

sign. And it has found one function code agent. So I think that this means that

agent. So I think that this means that it's running successfully. I'm just not 100% sure. I keep thinking that I need

100% sure. I keep thinking that I need to redeploy.

Um so h let's see. Let's try it out. If

it doesn't work, it means we have to redeploy. So, go to your uh deployed URL

redeploy. So, go to your uh deployed URL this time and let's go ahead and do build a calculator app.

Let's see if that will work. And let's

go inside of Ingest here. Ingest cloud

inest runs. And let's see if a new run will appear. And the new run is right

will appear. And the new run is right here. That would mean that we

here. That would mean that we successfully deployed our project and we connected inest to deployed instance.

And looks like we didn't need to redeploy at all. Let's just go ahead and wait to see uh if this will actually work, if there are any issues we have to

fix, and then we're going to be able to wrap up this project.

And here I have the result. So, it seems to be working. This is entirely in production. So my URL is

production. So my URL is vibe-bond.cell.app.

vibe-bond.cell.app.

And if you check the cloud here, there we go. It seems to be working. Uh if it

we go. It seems to be working. Uh if it still says running, just do a refresh and it should update the status. Here we

go. Amazing, amazing job. You finished

an amazing project. I had so much fun building this. Uh I I'm sure you did as

building this. Uh I I'm sure you did as well. Make sure to test your app, you

well. Make sure to test your app, you know, make sure everything's working.

Sometimes the easiest fix can actually be for you to redeploy again. And you

can see there was obviously a deployment that was running the entire time. So

perhaps this happened automatically when we connected inest. You can always, you know, click here and click redeploy and that will just redeploy your latest version. And now in your settings

version. And now in your settings environment variables, you should now see inest event keys and signing keys.

So that's the trick. That's what made it work. Amazing, amazing job. I think this

work. Amazing, amazing job. I think this was an absolutely amazing project. Good

job. See you in the next tutorial. And

thank you so so much for watching.

Loading...

Loading video analysis...