{
  "video_id": "xs8mWnbMcmc",
  "title": "Build and Deploy a SaaS AI Website Builder | Next.js 15, React, Inngest, Prisma | Lovable clone",
  "url": "https://www.youtube.com/watch?v=xs8mWnbMcmc",
  "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 simple prompt and hitting submit. What you're about to see next is something you'd normally expect from high-end tools like Lovable, Replit, or 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, 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 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? Let's break it down. What you're looking at here is the Ingest 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 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 Next.js application tailored to the user's request. You can click into any step to see exactly what happened and 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 Postgres 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 Kanban board, I can drag around. And just like our previous project, this one was 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 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 paginated table. And here it is, a sleek-looking admin dashboard with status cards, sidebar, and a paginated 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, it's time to upgrade Billing is powered by clerk That's right, just like their out system, the developer experience is incredibly smooth No webhooks, no complicated code, no confusing stripe setup Everything works out of the box watch how quickly I can upgrade my 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 hundred 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 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 CodeRabbit, 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 tech stack we'll be using. Next.js 15 with React 19 make our framework with support for server-side rendering and server components. TRPC combined with 10-stack query will ensure our app meets full-stack type safety. Prisma ORM with Postgres provided by Neon will be our database solution. Tailwind version 4 for styling along with ShadCN 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-generated code in secure cloud sandboxes. Docker for generating custom sandbox templates. OpenAI, Anthropic, or Grok, depending on the model you choose to power your AI agents. CodeRabbit 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 Next.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 up our Next.js project. If you head to the documentation page of Next.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 terminal and you can run node-v. And while you are 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 which is lower than 18.18, you're going to have to upgrade as well. Keep in mind that if you are on 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 Next.js application. See in 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 15.3.4. Why am I doing this? And do you need to 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, six 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 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 six 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. So this is the latest version at the time of me making this tutorial. So I'm going to go ahead and use this version. I'm going to create a project called Vibe. I'm going to select Yes for TypeScript, Yes for Eslint, Yes for Tailwind, and I'm also going to select Yes for the source directory. Be careful here because I think that the default value might be No. So use the arrow keys to select Yes and press Enter. Same thing for the app router. Make sure you select yes here. Yes for TurboPack, but only for next dev in 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 you have to enter this directory with 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 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 confirm our versions I gonna to go inside of package You can see that I have TurboPack here because I selected TurboPack Yes You can see that I have 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're 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. I have a tsconfig, a post-cssconfig, nextconfig, and an eslitconfig. You might notice that Tailwind config is 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 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 can see that I have some SVGs here. Great. So now let's go ahead and let's install ShatCN UI into our project. First of all, let's mark these as completed. Now let's go ahead and set up ShatCN UI. So by visiting ShatCN UI and going into the introduction, you can see that even though we are going to use it as our component library, ShadCNUI is actually not a component library. Instead, it is how you build a component 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 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 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 init. 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 the Shats in UI into our project here. You can see how it verified the framework Next.js and it 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 TailwindAnimateCSS. I think that's the new package that came from ShatCNUI. And TailwindMerge is new as well, I believe. Great. So those are the packages that ShatCNCLI added. Now let's go ahead and look at our lib file. utils instead 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 merge or add dynamic tailwind classes. And it also modified the globals.css by adding a bunch of variables which we can now use to build our project theme. It also added dark mode rules as 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 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, you can select any of those two options. But if you're using the same versions as me, I'm pretty sure you will have the exact same experience. It should just work straight out of the box. But if you get any errors or any decisions to make, you can select legacy peer depths. If you don't have that choice, perfectly fine. You can just continue. Great. So now we can go inside of source components UI and you can find a 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 about chat 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 run dev, you will see dash dash turbo back 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 using default export, right? So the name doesn't 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 just write hello world. And once I save, you will see the change hello world. If this is your first time using Visual Studio Code, if you have a little circle here, it means the file is unsaved. So just hit save and then it will be updated. And here's what happens if you don't do a default expert. 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 things, 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 classes, I can see the inner CSS it is 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 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 learned 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 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 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 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 Shazian 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 of them and you can clean them up later, but it's just gonna 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 down my app and I'm going to run npx chat cnui 2.7.0 dash dash 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, uncommitted files. 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 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 completely optional It is simply for those who want to learn 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 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 now. So let's go ahead and add a plus 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 my chapter. and then I'm just going to 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 longer have that button publish a branch because now you have access to your 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 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 obtaining a connection URL using a Postgres database provided by Neon. We are then going to set up Prisma, our ORM. 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 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, dot environment. Inside of here, I'm going to create a database underscore 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 description or the link you can see on the screen to let them know you can 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 Next.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.20.3. So if you're using the latest versions, this probably does not matter for you. 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 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 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 I'm just going to run npxprisma init with nothing more. So npxprisma 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 forward slash schema dot Prisma. Prisma would have added database URL, but it already exists in your dot environment. You already have a git ignore file. Don't forget to add dot 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 instead of our .environment so it didn't do anything right usually it would modify your .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 change 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 Postgres and that your URL is the 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, don't forget to add a .environment in the gitignore file. That is very important. But as you can see, my .environment file is grayed out, which actually means it is inside of gitignore. 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 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 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 ID 1, and then I add another user, it will be ID 2. 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? 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 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 author 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. here to see the syntax. I should have told you this before. My apologies. I just remembered. 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 commit this, right? Because right now, our database here is completely empty. Nothing yet exists here. Nothing is pushed here. So let's go ahead and let's do npxprisma migrate dev. I'm not going to do this flag because that's not how we're going to run our commands. So npxprisma migrate dev. And now we're going to be asked to call this migration in a certain way. So I'm going to call this migration init. And just like that, we applied the migration. And two things have happened now. Actually, three things. The first thing is that it synchronized our database from Neon to the schema, right? So now our Neon database has the same schema there. 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. If you go inside of your Prisma, you can now see the migrations here. and inside of here you can see the actual SQL file that happened and the second thing you can see is I not sure where is my generated it right here source generated there we go you can see the Prisma is now available here and the third thing that it did is it synchronized the Neon database So if you go inside of Neon 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 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. In here, it suggests creating a seed 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 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 of source, I think. Let me just see how to 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 this depends on whether you use the source folder or not, or they've changed it. So they use the Prisma.userCreateInput. Now, if you're wondering, where does this come from? How does it know userCreateInput? 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, npxprisma migrate dev which internally runs npxprisma 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 dot uh you can see all these weird things if i i think i can you can 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 about Prisma. Perfect. So I have this user data here, and let me just, if you are 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? Or you can use the link that I will put on the screen for this seed script, if you are and I'm 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 forward slash Seed dot TS. Just ensure that yours is in the correct place. and once you've done that and saved the file in here we have a warning before starting the development server note that if you're using next js version 15.2.0 or 15.2.1 do not use turbo pack right so you can see that turbo pack sometimes has this small little issues but since we are on a newer version we should have no problems here right and now let's go ahead and run npxprisma database seed here like that and that uses the tsxprisma 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 database. Perfect. Another way of seeing this data is by using the Prisma Studio. So NX 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 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 creating Prisma. I like to call it 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 how this works. So basically, why not just export new Prisma client? The reason why is because of Next.js hot reload. 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 by hot reload. I used the window object. I'm not sure if this belongs to the window namespace. 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 try and query something so I'm going to go inside of my source folder instead of app page.psx 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. charisma.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 is actually a React thing, not a Next.js thing. Next.js is simply the environment where server components can be demonstrated. So let's go ahead now and do npm run dev. Let's visit localhost 3000. And in here, you should see a JSON of your users 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. But there is one more thing I want to 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 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 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 now, so npx Prisma migrate dev, I'm going to add these changes now. It will probably ask me to reset database anyway, right? That's why this is a dev command, right? This should also not be used in production. You would usually do npx Prisma migrate. You can learn more about that 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 non-null 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. And this time it worked, right? So this seems to be 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 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 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 migrate reset like this are you sure you want to reset your database all data will be lost. And I will press yes. Oh, yes. I forgot that resetting it also runs the seed script. So let's remove the seed script because it doesn't make sense. 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.\nand 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 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. There's not going to be any posts in our 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 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 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 zero two and then i'm going to call it database. So I'm going to call my branches according to my chapter, zero to database. And you can see that down here, it says zero to 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 zero to database 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 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 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 a source generated prisma to git ignore so that was not added and after that we can go ahead and merge this pull request and click confirm merge. 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 down here and click on the 02 database branch and then you have to select your 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 would be the one that is most, well, 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. You click on synchronize changes here and then OK. Like this. And now you should no longer have any visible buttons here. Right. And if you go inside of your graph, you should see this an initial commit, then 0, 1, and then something different for 0, 2, right? Because we branched out and then we merged that back into our main like that. And now if you go ahead inside of your main here, you can see that six minutes ago, we merged this, right? 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 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're 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 next JS tab, but that is talking about an older Next.js version. So make sure that you are inside of 10 stack React query 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 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 OK. Basically, this is just a sanity check to confirm that you are on your main branch and that you didn't accidentally forget to synchronize your merged branch from the previous 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 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 10 stack 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. And ZOD is a package we already have installed, and we had it installed in the first chapter setup because when we added all ShatCN component, we also added forms, and forms in ShatCN 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 install all TRPC ones, just double check that you actually have the same version of all TRPC packages because that is quite important, right? So 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 11 if you're coming from 10 or 9. So make sure you're using at minimum 11 point something, and then you're good to go. Perfect. Now let's go ahead and let's create a small init file here. So I'm going to copy this. It's a very simple snippet. So even if you don't have access to this documentation page, 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 init.ts. And in here, we are importing init.trpc from trpc server package and cache from React. We are setting up the create trpc 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 colors 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 a routers folder and inside underscore 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 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're 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 nextgs variable folder and then 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.ts and let's go ahead and copy this now in here we're going to get some errors it's specifically about this import alias so we don't use this curly little string. Instead, we use an add sign. So you can just switch it to that and you will have no more errors. As you can see, all of these things already exist. So we have the trpc server package. They are just extending it here, some tree shaking it seems. And the trpc init is the one we just created as well as the router's app. So you can command click on this to visit that same as the init one. And this is a Node.js package, so that's a 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 a route.ts. It very important to have this exact structure Great Once we done that let go ahead and let create the query client So I going to go inside of source the RPC query client Yes, it is .ts. Perfect. So about SuperJSON, let's do this. Let's immediately install it. So SuperJSON, 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.serialize and deserialized data using superjson.deserialize as well. So you can leave it a component like this. No need to modify anything further. Now let's go ahead and let's create our client.tsx. So this will basically be a wrapper, a provider of trpc and 10 stack query, which we are going to wrap our entire app around. If this is your first time ever seeing 10 stack query or trpc, this is a lot of information at once. But if you've ever worked with React query or something like SVR, I promise it's a similar API. It is just 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 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 use client because this has to be a client component. You can see it tries to access the window here. And it's also using some hooks like use state that can only be achieved using a client component. And 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 specifically tailored for Vercel because I don't know where you want this 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. 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 run dev, 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 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 versell 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 can see that this will attempt to load the URL from localhost 3000 slash API slash trpc. So it's important that you didn't do any changes here, slash API, slash TRPC. That's why this structure is important. And then in here, we just have some regular TRPC and TANSTAC query setup, but we have to enable the transformer super JSON 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 super JSON from super JSON. and I'm going to go down here, remove this part and simply uncomment. So we are using the super JSON and you can ignore the error 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 Next.js. Since we are in Next.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 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 command or control click here, and it should lead you to this exact component, which has this little super JSON 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? its 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. And this is where things become interesting. So let's just do that now. I'm gonna go inside of tRPC and I'm gonna 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 see it's much simpler now again we have all of this either installed or already created great so now that we have that let's go ahead and just fix this little super json issue here that i'm having so i'm using super json in client.dsx i'm using it in query client dot ts and i think i should also be using it in init dot ts you can see that i have it commented out 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 date things like that SuperJSON helps parse those things. Great. So we now created server.tsx. And this is actually a very, very 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 more familiar example first. Let's go inside of source app folder and let's go inside of page.tsx. And let's try the following. Let's add trpc using use trpc from naturally client, 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 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 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 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 tRPC works. So instead of having to do localhost 3000 slash API slash create dash AI, right? Which is most of the time a literal string, right? So it's very easy to make 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 TRPC 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 TRPC 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 one more thing inside of this input 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 body, and then we would somehow stringify this with text. Hello, right? This is a stupid example, right? But you know what I mean. 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 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 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 inside of extra information and go into frequently asked questions And here we have it It doesn work I 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 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. Let me, oh, I think that, yeah, you can just create it if you want to. So go inside and create a .VS 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 problems, when I say any, I mean this. You're getting to type any all over your project. It could be due to these missing settings. So this actually fixed my problem once. So that's why I'm sharing it with you. And you can always do command shift P and then reload window. And this kind of restarts the TypeScript 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 your root page. And first thing you should see is this very big error. Why? Because by default in Next.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 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 10 stack react query and in here you would usually create you know your own fetch method which would then call forward slash api 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 following you can just use the trpc one trpc 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 command click 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 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 are 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. 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 experiment with prefetching. 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. 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, let's first remove use client like this and let's break our app. So immediately our app is broken, right? We can't use the RPC here at all, right? So now what we have to do is we have to learn how to use the RPC inside of a server component. And let me just show you, I'm gonna 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. So this is a bad example. My apologies. Previously in the past, you could not see it. if it was a client component. So yeah, ignore this. I tried to do an example, but I used the 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 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 const data and turn this into an asynchronous server component and await caller from the RPC 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 stringify data. So this is how you would fetch from a server component using TRPC, using a caller. So this isn't doing a network request on that 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 only one I've seen that performs this 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? 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 way we are going to use the RPC, and that is using prefetching. So we would do things like this. Const query client would be get query client from the RPC server. We're not going to call the caller here, but we will import the RPC from here. And then in here, we're going to do void query client 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 DanStack React query. we would pass the state to be dehydrate again imported from danstack 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 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 prefetching 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 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 import client from .slash 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 instead of a server component, what we are doing is we are leveraging 10 stack 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 a 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 things like this, this wouldn't work. It doesn't return anything. They've done that on purpose. So you don't actually use this data because prefetching query, all it does is it initiates a call on a server component, but only for the sole purpose of populating a tan stack 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 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 familiarity of the client components and we also don't lose the speed of server 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 confusing. I try to explain it the best I can. I would highly suggest, you know, reading about prefetching and just going through this documentation in the first place. Perhaps Dave explained it a bit better, but this is how I like to 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 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 background jobs and start introducing some background actions we now have our database we have our ORM and we have our data access layer so those are the three things that we need so we can start saving some data in our 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 03DRPCSetup. 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 0.3.trpc.setup.com. And I'm going to commit. And then I'm going to 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 have a complete summary of this pull request as you can see we introduced trpc 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 the TAM stack query. And I also have some potential issues it 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 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 this further on when we update this React context. So it actually understands our code very, very in depth. 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 CodeRabbit account. But that is not all. So now I'm going to go ahead and merge this 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 the RPC setup. And you can see that I got this pop-up 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 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 merge that. And now we branched out for TRPC setup and we merge that back inside. And what I suggest you install is CodeRabbit extension. So CodeRabbit is a completely free VS Code extension. You can go ahead inside of your extensions here, CodeRabbit 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 CodeRabbit. 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 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 as done. Amazing job. We now have the database access, our ORM, and our data access layer. We are finally ready to start doing some AI-related things and 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 a Next.js 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 seen this a million times. But now, imagine you have a more complex task at 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. 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 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 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 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 long running tasks. We will achieve this in our project using ingest. So let's go ahead and create our first function and let's trigger a background job from Next.js. 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 Next.js and select App Router here. And then let's go ahead and install Ingest. Before you do that, just make sure that you\non your main branch and you can click Synchronize Changes just to confirm you didn't have any unsaved 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 ingest. 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 ingest, 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 ingest-cli at latest, the latest is equivalent to 1.8.0, just in case you were interested in my exact version. So, npx ingest-cli at latest or a specific version, and then dev. And when I start this, you can see that it says ingest dev 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 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 npm run dev. And in here, you will start to see something. You will start to see a bunch of 404 pages here. That is because the ingest developer server is trying to find our ingest initialization in our 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 ingest and I will create client.ds 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 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 can replace this with an add sign and a forward slash. There we go. It's basically using this ingest, which we just 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 ingest. So in here, create functions, oops, functions.ts, 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 one 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 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 that. And then let's go inside of the app folder, API ingest route, and inside of here, let's import hello world from that ingest functions file. And now, if you go back here, you should be seeing your app here available, auto-detected. You can see everything is fine. It found it at API ingest framework Next.js, and one function found, Hello World, because we just added it here. So if I rename this to Hello World 2, it immediately renames here as 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 data, but you can go ahead and add email and pass in, I don't know, it can even just be the name, it doesn't matter. and click invoke function. And you will see how it went from queued to running to complete it. So that's the status that just happened. It happened very quickly because there was no function before this. So the queue to running went very fast. And the running only took one second because we only wait for one 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 running. It's running for five seconds, seven seconds, and finally, after 10 seconds, it's finished. Is this getting familiar to what we just discussed? This is a 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 do that. So I'm going to go inside of source, trpc routers underscore app. And in here, I'm going to call this 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.string like this. And then instead of dot query, 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 going to do is await ingest from ingest client dot send. 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. So let's go ahead and pass email to be input.text, 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 actually use this invoke method. I'm going to go inside of source app folder and I'm going to go inside of page.tsx here. And 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 the RPC. So let's mark this as use client, so this becomes a client component. The use client is very important for our demonstration here, so please do it. Now, when you refresh on your localhost 3000, you should just see a test here. So now let's go ahead and let's create a padding for maximum width 7XL, MXAuto. 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 onclick, 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 lasts in comparison to the network 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'm definitely expecting to see something here, but it is not 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 run dev 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 achieve. If I go inside of the RPC invoke again, and inside of the ingest, and go inside of the functions, Let change this to 30 seconds and add a comment here You know imagine this is a download step right here In here we are downloading a video Then in here, imagine this is a transcript step, another 10 seconds. And then finally, you know, imagine this is a summary step. And this finally then takes five seconds. This is what we want to achieve, right? The moment we click on generate summary, we send a network request, we trigger a background job, and we immediately allow the user to close the tab, right? So let's try it again. If you want to, you can also do this. You can go inside of your layout in app folder, and you can add a toaster from components UI sonar 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 Sonor.success. Background job started. And now you will see the following. If your app gets stuck, you can go ahead and just do npm run dev again. I did get this happen a few times and I think I solved it when I removed the TurboPack, but we'll see. Basically, if your app hangs on loading, don't worry, you can just restart it. It will not happen in the actual app. So let's try this now. 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, drpcinvoke. And after a wait, let's return OK, 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're now doing the first step, which would be 30 seconds of downloading a YouTube video. After that, we're going to go ahead on to 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. Or this would actually be the summarization, right? So that is how background jobs work. And that's how you add them in a Next.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 connection. Obviously, in development mode, if you lose the internet connection, your dev server would fail. So yes, in development, technically, 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 Ingest Developer server, you can easily cancel things if you don't want to. You can rerun them. You can go ahead and look at the payload that was added. You can do a bunch of things here. 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 ingest does automatically for you. They actually have a cool example on their landing page here. they have, and yes, they have AgentKit. This is something I didn't want to talk about immediately because I don't want to confuse you, right? So alongside background jobs, we're going to use Ingest to build autonomous agents, right? So AI and background jobs go hand in hand, and Ingest 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 cool examples. So this is the transcription example. You can see that they have a step called transcribe video. So it's very similar to step.slip, but instead it is 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 step finishes, they call an LLM chat GPT 4.0 to create a 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 used the download step as well. So it looks like we don't need a download step, right? So this is what you can use ingest for. The second one is to build AI with automatic retries, 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, which right now seems only for fun, but sleep can be very useful. For example, you can send a welcome email to your user, then wait a week, and then send a follow-up email. So yes, that's how long these tasks can wait. They are background tasks. Perfect, yes. And for development, you don't need any account yet, but later for production, for deployment, we are going to have to create an account with 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 goal of this chapter. I think we achieved this exact thing right here. So we set up ingest. Let me just change the color. So we've set up ingest. We created the first function. We explored the ingest developer server. And we triggered a background job from Next.js. Now let's go ahead and branch out and push this to 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 ingest folder. If you're wondering what that is, I'm just guessing it's cache for the ingest developer server. And now I'm going to go and click on the 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 CodeRabbit extension, if you remember from the previous chapter, 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 background jobs every 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. New pull request. Oh, it looks like it won't push until 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 that. So make sure you click publish branch. And you can see that this is still doing its own thing in the 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 going to have two 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 go you can see fix duplicate step id so it noticed that all of my steps are called wait a moment which is very you know not useful uh when you were reading inside of the ingest 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 ingest function this is of course not something that 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 pool requests, you can have it here in your IDE. And here we have our pool request summary, which is pretty much identical as you can see to the pre-extension for Visual Studio Code. You're going to see in here we have fixed 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 ingest. 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 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 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 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 a 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 CodeRabbit 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 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 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. And let's go ahead now 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 source control on the graph and you can see that I have zero four background jobs now merged right here. And you can see that I'm on the main branch and I should have access to my ingest folder here, which basically means we fixed all of these things. Perfect. I'm also not going to do anything regarding this CodeRabbit 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, 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, which is OpenAI. 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 that I have chosen, GPT 4.1. And it is almost perfect. The absolute best coding model, though, is Claude, specifically Sonnet 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 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 higher paying customers, right? Which are this very, very large companies. So it's not exactly suitable for tutorials. As per Grok 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 won't tell you not to use it. I'm not sure. And as for Gemini or Google, the great thing about Gemini is the amazing 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. I've heard that Grok AI has free tier, so I would rather you use Grok 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 Grok rather than Gemini. The absolute best choice and the choice that I will be using is OpenAI, specifically this model. As I said, there is a chance we might hit the rate limit here, but the reset is around two seconds, which is completely fair. And it will happen rarely, only when we are doing some very, very large tasks. With Anthropic, we get the amazing results. It completely understands Next.js ecosystem. It understands what ChatCN UI is. But once you hit a rate limit and you will hit it very soon, it is almost impossible to get rid of. You will almost be stuck in a rate limit. So in my opinion, choose OpenAI. It is the simple, best solution for this project. If that is possible for you, you will have the best experience using OpenAI. And now I'm going to show you how you can find if any changes have been made regarding this if you are watching this tutorial in the future. So you can use the link in the screen again or the 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, OpenAI, Anthropic, Gemini, and Grok. 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.5 SNET versions, but the rate limits were very easily hit. The OpenAI, 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 works, no problem, and it's amazing. not as good as Anthropic 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 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. 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're 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 ten dollars even less will be enough for you to complete this tutorial many times which will of course depend on how often you create new websites and apps with this project, but I barely spent that amount and I tested pretty heavily. Once you have filled your account, you can go ahead and obtain an API key. If you're using Grok or Gemini, you have a free tier, but as I said, Gemini just doesn't work and Grok, 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. 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 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 was background jobs. So now what I'm going to go is I'm going to go inside of dot environment here and I'm going to create OpenAI here, OpenAI 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 the ingest 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 Anthropic API key. If you're using Gemini, it is Gemini API key. Or if you're using Grok, it is XAI API key. So make sure that you've added one of those here. Perfect. Now that you have done that, let's go ahead and do the following. Go inside of the agent kit by ingest and go inside of installation. And let's go ahead and install ingest 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 want to do the following. Let's go ahead and do npm run dev. 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 input from components UI input. And above this, the RPC methods, I will add value, set value, and a simple use state from 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 a hundred times. And this will simply be, it can stay in Vogue, background job. It doesn't really matter. Great. So now let go ahead and run npx ingest 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 ingest functions here. And let's just remove this one, leave this one for five seconds like this, and change this to, let's just say input. Let's call it 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 value that would be better sorry so let's get instead of invoke change this to value input dot value 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.mutate to pass in the value to be the value from the state. We then modified our trpc router to accept value in the z object and we've accepted we've changed the ingest.send to pass in value in the data object. And of course we modified 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 development server here. So now I'm going to call this test value and I will click invoke background job. And then in here in the running 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 AgentKit installed, let's go ahead and do the following. In the ingest documentation, which is outside of the AgentKit, you can find a very, very simple example by going inside, let me just find ingest functions, step and workflows, AI inference here. And in here where they show you AgentKit for the first time, they show you this very, very simple 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 ingest functions here. And I'm going to add this. Agent, agentic open AI as open AI and create agent from ingest agent kit. and then i'm gonna go ahead and open this function it's already opened right so basically i'm gonna now write inside of here you can leave this 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 a 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 a model GPT-4.0 if you're using OpenAI. And let me just remove the things I don't need for now. Or 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 OpenAI, these are the models that I can use. One of them is GPT-4.0. 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 open ai import because i've copied it from here 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 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 the agents here and you can find this exact thing we just did. We created an agent, we called the summarizer and we gave it a system prompt, and then we give it a model. So we did that 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 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 text 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 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 success okay here and let's rely on the 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 ai and create agent from ingest agent kit new package that we have installed we specified a GPT-40 model. Another hint here, I mean, 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 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, 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 in doing that. So in here, as you can see, it immediately finished. And you can see the step was called a 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 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 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 codeagent.run. And now we're going to say, you are an expert Next.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 next js snippets like button component next js and react 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 longer running task and let's see what 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 component using Next.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 default of the button. So basically, a fully working button. So we are, you can say, halfway there, right? We just made AI create a 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 this chapter we've done what we aimed to do so let me just check\nwe chose our ai provider and we've set up in just agent kit and we even tried a very simple ai step right so basically that's how you're going to write agent kit tools and we are then only going to extend it by introducing tools one of the tool can be terminal 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 agent in a execution loop. 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 coding steps. That's 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 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 this chapter. 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. So now what we have to do is we have to open a new branch and push to GitHub. So let's go ahead and open 05aijobs 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 cash for the ingest 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 free CodeRabbit extension will analyze all of these files. Or if you prefer, you can go to 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 CodeRabbit 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 Next.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 about our trpc value because we are 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 hard-coded strings, and that is exactly something we will 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, 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 sanitization and some other limits on the front end. My apologies, in the background job, I thought that this was the submit function. It is not. We will take care of that as well. And yeah, no need to do anything else here because this will not look like 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 this trigger of the CodeRabbit extension this time, simply because this is a merge which we just reviewed, right? I'm going to open the graph here just for a sanity check to confirm that my last changes were 05ai 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 an online sandbox, cloud sandboxes, which run Next.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 in the next chapter. In this chapter, we are going to explore E2B sandboxes. boxes. This will be the environments where our AI will generate files and create a working Next.js 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 Next.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 to learn more about the sandboxes, how they work, and basically to create a template that we are going to use moving on to create working Next.js 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, in short, E2B allows you to run AI-generated code securely in your application. It's an open source runtime for executing AI-generated code in secure cloud sandboxes. It is made for agentic and AI use cases. And some of their customers are Perplexity, Hugging Face, Manus, and even Grok. So these are very, very big names in AI here. And in 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 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 will probably have less templates than I 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 NextJS thing. You don't have this. So I have this because I tested it. Same thing with this no-name 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 inside of your budget, you should see your credits. So you should have $100 free for your new account. And as you can see, I tested E2B pretty thoroughly. So I really, really tested it all day, every day, and they barely 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. And now let's go ahead inside of the documentation 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 options on how you can run it. And you can shut down your app, right? So basically E2B should become available after you install either via NPM or using brew. So what we have to do next is we have to authenticate, right? So e2b auth login should open up. You can see that I'm already logged in. So in here, I get that message, but you should get a, you probably won't get this message. Instead, you will get redirected to e2b page. And from there, you're going to have to approve the login and then you'll be good to go. So once you've logged in, what I would suggest is try 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 logged in I would get some kind of error here right So just make sure that at least you get a message like this which means okay you logged in but you have no running sandboxes Great So now 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, you probably have three of them, as I said, desktop, code interpreter, and base. But what you need is a Next.js 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 step. So we've created CLI and we've connected to our account. Now we need to create a Docker file template. Now I have provided you with a GitHub repository with two files that we are going to need for this. Not because 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 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 next.js. 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, 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 gonna go ahead and explain what it does. So the first thing we do is we set the environment. In our case, that's gonna be node. After that, we run a terminal command to install curl. 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 underscore page dot sh. Make sure to not misspell this because we need it right here. And you can find the content in the same 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, 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 ShadCN 2.6.3. The reason I'm using these versions is because when I started making this tutorial, those were the newest 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 project, feel free to upgrade this to 7.0 and this to 7.0 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 flags. So why do I add dash dash yes after npx? And why do I add dash dash yes after create 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 Next.js application, we had a bunch of questions. Do you want to use Tailwind? Do you want to use Eslint? Do you want to use this, that? So because of that, I have to just agree to all of those things. So the terminal doesn't hang, 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 dash dash 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 chatcn init and for adding all chatcn components. And then what we do is the following. We move the content of that new folder, Next.js app, where we just added Next.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 because it is easier for AI to understand that the place where it's located, because initially it will be loaded here, we just tell it, wherever you are, this is where you have the Next.js application. You don't have to go to any other directory. Trust me, that is much easier for an AI to understand because otherwise it will 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 this trick. So that is basically our Docker file. Now let's explain the compile page. So the compile page is a little trick that we are going to use to ensure that the Next.js application is running and that the root page is 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. I'm not sure, not too familiar with 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 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 next dev dash dash turbo pack. So the turbo pack is really cool here because it speeds up 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. And basically, the way I set up Docker in my project is by downloading Docker desktop. I use a MacBook, so I downloaded for Apple Silicon. If you use Windows, you choose your architecture here. or if you use Linux, well, you probably know what you're doing then. So after you've done that, make sure that you have Docker installed. So in here, let me just try and open it back here. So if I go inside of, let me just try and find the 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. On Mac and on Linux, it's pretty straightforward, right? If you install it with a DMG, it will add it to the terminal as 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 OrbStack. That's a Docker alternative, 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 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 the Windows in here, you can see pretty similar instructions. I'm not too sure what this means. I don't use Windows. But in here, I think it is important for you to be able to run Docker from the command line as well. but at minimum you should be able 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 gonna go ahead and start my docker application actually the first thing i'm gonna do is i'm gonna try without docker started simply so we can test if uh if the command fails so i 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 this is the following. Go inside of sandbox templates and go inside of Next like this So I going to try and run E2B 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 thing I'm going to try. So in here, you can see how 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 i will have docker desktop available you can see docker desktop is now running so 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 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 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? I'm not sure if you need it inside of here, right? As 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 fact that I have it inside of my terminal. So 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, something transferring, it means the Docker is working. And then you can cancel it using command C or control C. And then we're going to run a proper command with a proper start command. So now in here, Here's what we have to do. This, basically. We have to add e2b template build dash dash name vibe next.js test to dash dash cmd. And you have to add compile underscore page dot sh. So now let's go ahead and run this again. So I'm just going to wait to see 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 upload the entire Next.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. 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, it would be blocked by waiting for the user input. That's why that is important. It was also successful in copying compilepage.sh. So we did this correctly as well. Now I am running chatCN init. 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 succeeding. It checked the registry. It found appglobal.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 RADX packages which we use for our components. 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 content from Next.js app into home user and removing the old folder. Now, 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 dash dash yes 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 is almost certainly because you forgot to add dash dash yes to some of this. Basically, it's hanging on some command and it didn't create the Next.js app. Great. So now what's happening, I think, you know, I'm new to Docker 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 Next.js app, right? Those are two different things. Our Next.js app is just a development instance. we are building the Docker image here. All right, so mine failed. So I will, it looks like it failed because it is missing the compile underscore page.sh command not found. And that's definitely because of this. I should have done 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 slash compile page.sh and let me just show you something. 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.tuml generated. In here, you can see my start command is compile underscore page dot sh. So I have two ways of changing this 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 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.3 and that signals that it is done template is ready pausing sandbox 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 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 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 team, which then again, you know, depends if you're going to publish this or not. What does publishing mean? Well, I'm going to explain in a second. But basically, now you can go find your e2b.toml 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 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 toml file that's basically where the configuration is 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 correctly you should see vibe next.js test dash two 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 of vibe next.js test two. 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 Next.js template. So you can just do e2b template publish dash t 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 is simply because it will be easier to connect to it. At least that was my experience. Later on, we can easily unpublish it. or you can delete it. The reason this is kind of you know you should be wary of this You should not share your template ID with anyone because I can use it and I can use your credits right I can spend your 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 Next.js 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 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 Next.js app is actually working or 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 completely forgot but yeah we have to uh we have to install 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 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 create an agent, let's do const sandbox ID, await step.run. And we can extract the step from here. We removed it in the previous chapter, I believe. So step.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 ahead and simply do await sandbox set timeout. 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.sandboxID. So this will be the step which we are actually going to preserve here and keep throughout this entire project because we will always need the sandbox ID. And now that we have the sandbox ID, 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.run get sandbox URL. in here we would get the sandbox again using await and then in here we need to create a util call get sandbox so let me just go ahead inside of the ingest and create utils.ts an 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 dot connect sandbox id like this and then in here just return sandbox so basically i'm writing a function to make this 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 a way to get sandbox here and passing the sandbox id and then return sandbox dot get host 3000 so uh basically what this is it creates the host under the port 3000. Why 3000? Well, we know why. Because of our sandbox template, right? Inside of here, we know it's running on port 3000. That's the only port it can run on. It's a Next.js app. And this is actually only the host. So let's do const host to be that, and then return, open back text, HTTPS, and then host 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 run dev in one, npx ingest CLI latest dev in the other. It is running. Perfect. Let's go to localhost 3000. Make sure you have your Ingeist development server here. And I'm just going to do create a button component again. Why not? And let's go ahead and see what's going on here. Oh, so there we go. It is failing. So something, you can see the error, invalid API key. So we can cancel this run because I forgot to add the API key. I'm constantly not reading this the way I should so let me just go ahead and add click on API key here and we can find it at the dashboard so let's go back inside of our dashboard here you can see I already have some so I'm going to click create new key here yeah and you can find it here under 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 dot 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 i don't think you have to explicitly add it anywhere because this is what it will be looking for. So 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 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 Next.js application. And we are a completely empty Next.js application hosted in E2B started from our background job. You are halfway there. You can already 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 far, you did it. Amazing, amazing job. So let's go ahead and mark this as complete. And yes, this will stop working after some time, and that's completely normal. So after about five minutes, I think by default, 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 gonna go ahead and i'm going to open my source control here i'm gonna go ahead and create a new branch 06 e2b sandbox let me just confirm that's my chapter name so sandboxes i'm gonna 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 and I'm going to publish my branch. Again, there is a free CodeRabbit 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, you can click review all changes or if you're like me and you like to see the summary and all other things, you can use their pool request function. So now I'm opening my new pool 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 CodeRabbit understands what we just did. So we introduced a sandbox environment for Next.js, allowing users to create and access isolated Next.js 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 sequence diagram demonstrating how that 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 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 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 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 like this. In here, it is suggesting improving the compile page shell script, which could be completely valid.\nI don't know Bash. 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, I'm not going to change it because I know it works. So good enough for me at the moment. Great. 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 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 synchronized 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. 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're 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 AgentKit 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 add some tools. Tools are used to extend the functionality of agents for structured output or for 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 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, ingest, functions. Now in here, we're going to do the following. After you create the coding agent, 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 ingest 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 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?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. Inside of the try, let's get our sandbox here using await get sandbox and pass in the sandbox ID. then let's grab the result of await sandbox.commands.run and pass in the command so we are reusing our get sandbox method from utils i also imported zod so make sure you add this as well 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 by going inside of the E2B documentation and simply learning about the command. Let me see if I can find 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 and define some more 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. 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 are 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.error simply so we see this in the terminal. open backticks and you're going to say command failed render the error and then you can use forward slash n 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 law and then you're going to return the exact same thing. This will basically tell the agent what went wrong with additional information about stdout 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. Our agent now has the ability to use the terminal. It uses Sandbox API for this, and it will keep the results of this 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 Next.js with me for some time, you know that sometimes when we install a package which doesn't support React 19, it fails because we need to add dash dash legacy peer depths. So if that happens here, it will first fail and then the AI will read the message and say, oh, OK, I need to add dash dash legacy peer depths. And Ingest will automatically retry the terminal step with that new information, and then it will succeed. So that's how powerful Ingest background jobs are, and now their amazing Ingest agent kit. So now that we have finished the terminal tool, let's go ahead and create a new tool. This one will be called the create. or update files. The description will be, and let me just 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 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 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 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?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 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 file Updated files file.path is now 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 which we already have we've already seen it create 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. 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 are going to write that to the sandbox file explorer using files.write, which is a similar API like sandbox commands. So that's how we know which file to write where. And then we just keep 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 each 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 in the file explorer we are going to save 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 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 here, let's return error and simply render the error. Perfect. And then 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 the return of this step. This step can either be an object or it can be a 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 finished. Now let's go ahead and let's create another tool. with the name read files. Now in here, let's add a description, read files from the sandbox. Parameters, z.object, files, z.array, and z.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. 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 the 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 it needs to read data. So yeah, let's just fix this path. So if we, in our prompt, instruct 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 hallucinate, right? So we tell it, if you attempt to use a chat CN component, make sure to read inside of the components 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 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 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 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. I started out very simple, like it was almost just like one or two lines, and then I had to add it more and more and more instructions until it understood things very well. And I found this to be kind of a very, very good, 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 Anthropic 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 let's create prompt.ts and let's paste 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 working in a sandbox Next.js 15.3.3 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. You can write files with create or update files. You can execute commands via terminal. I also tell it to use dash dash yes, simply because it's not a human. So basically, 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 rules. Don't install package JSON or log files directly. You can install packages, but don't modify these directly. I tell it the main file is in the app folder page.tsx. I tell it that all Shatsy and components are pre-installed and 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. 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 you think you can modify it a little 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 instructed to return this type of format, task summary, and inside a description of what it 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 is why I'm very strict about it. because this is the only way to terminate the 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 ingest functions.ds and let's go ahead and change our code agent here and let's extract the OpenAI here a little bit. And now what we are going to do is working to change this model to be GPT 4.1. Default parameters here will be temperature 0.1. Now, if you're using something that is not OpenAI, this might not exist. And that's completely 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 Grok or Anthropic, you don't have this probably. So that's completely okay, right? You can even do without this in OpenAI. And the reason I changed the model is because 4.1 is much, much, much better at generating UI than 4.0. and for Anthropic the better model is 3 Sonnet For Grok or Gemini I simply don 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. Prompt at prompt, which we just 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 OpenAI, 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 lifecycle here. So after the array of tools ends, so make sure that you find where the tools end, right? And go to the bottom here. We're going to add a lifecycle. And in here, we're going to get onResponse. 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 using the terminal, then create or update files, and then read the files, and we're 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 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 lifecycle, we're going to 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 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 ingest, where we have get sandbox. And let's export function here, last assistant text message content. It will accept a result, which is a type of agent result from ingest agent get. let's go ahead and do const last assistant text message index which will be result dot output 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 what assistant said last then let's extract the actual message content from that index so result dot output last message index as a type of either next message again from ingest agent kit or undefined so it can even not exist right and then let's return message question mark dot content and we're going to add a ternary below it. If type of message.content is string, we are going to simply render message.content. Otherwise, we will do message.content.map. Get the individual inner content and simply return that inner contents text. And then we are going to join it all in a single string. and then finish the outer ternary by adding or undefined 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, 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 lifecycle on response, 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 dot includes task underscore 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 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? Okay. So that was the extra. Perfect. So basically what we're doing here is we are 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 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 now is the following. Go below this entire coding agent and create a network. The network will be create network from ingest 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 call it 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 limit how many loops the agent can do. So what is 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.state data summary. And if we have the summary, we are going to break this network. Otherwise, we are going to return 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. So the code agent will call itself many times. Until finally, we detect there's a summary. If we detect the summary, we say, great, you 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? 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 exactly what I did wrong. Okay, I think I found it. It's all the way up here. Somehow this 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 and I will do const result that comes from the entire network.run. 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, we can actually do the following. We can show each file that 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 data files. And summary will be result state data summary So right now these are a type of any Depending on the version you use maybe they will even become errors Don worry we will add the types later So if we 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, it is hard to be reliable and deterministic with AI 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, 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 easier for you to have the same result as me. Let's try it out now. So I have my app running here and I'm going to go inside of here and I will create a calculator app. Let's try this and let's invoke a background job. Let's see, maybe it fails immediately. Maybe it works. We're going to see. Let me refresh the runs. Are they working? Are they not working? Not sure. Okay, 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 ingest CLI. I have shut down npm run dev 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 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 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 looks like something went wrong. So what happened here? What happened is that it forgot to use useState. But here's the cool part. For you, maybe this didn't even happen. I don't know, right? It can behave randomly. So how do we fix this thing? Well, we can fix it in two ways. We can fix it by 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 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 interactive element include proper state handling and add use client to the top. Perfect. That's a good example. Only add use client at the top of files that use 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 create a calculator app 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, you have to simply rely on luck sometimes, right? Sometimes the agent will perform very well. Sometimes it will perform very bad. And the better this models get, the better your results will actually be. And as I said, 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 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, you know, you can try and go inside of here and then explicitly tell it you know 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 read through my prompt and see if there's something you don't 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. 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 back-end side. You can now only improve these tools and improve 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 database and kind of return a message back to the user. And if you want to, you can instruct it to use some package, like use drag and drop, use a framer. And then you will see it use the terminal 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 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. This is better than I expected. So let's try telling it to use framework this time. Build a landing 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 that or something 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 sure if that's the newest version of framer. Maybe this won't even work. I don't know. But let's click on get 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 frame or motion. So as I said, it's not 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 OpenAI 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 create this better than me. Your starting point should be this. You're a senior software engineer. And 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 the help of ai and i basically added more things as i saw 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 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 a kanban board use react beautiful drag and drop let's try that maybe we will have some better results with this package so this is the result of the query to build a kanban 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 error error unable to resolve the dependency and it probably told it that it needs to use legacy peer depths you can see this come retry this command with dash dash 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 command failed and we tell it why it failed so that way it knows how to retry and my get sandbox URL was this what seems to be a working Kanban 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. Amazing, right? It even highlights where it's going to land. Very, very cool. Great. So I think that marks the end of this chapter. 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.\nSo 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, we will allow the user to instruct the AI and tell it, hey, it failed. Can you please fix it? Because, you know, I saw Lovable fail. I saw Replit fail. I saw V0 fail. All of these apps fail, right? They are just AI. It's a language model after 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 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 CodeRabbit 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. Then here we have the CodeRabbit 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 stdout, 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 recommends not doing a double ternary instead ending 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 some files which were successfully created, save them, but still throw an error. So quite a good suggestion. But in my experience, if it fails on one file, it will fail entirely because this doesn't mean that it wrote incorrect 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 it to be able to write all files it needs. But very good suggestion here to handle partial success. I will look into 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 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 go ahead and visit our schema file. Before you do that, as always, confirm that you're on your main branch. And if you aren't sure if you have any unsynchronized changes, you can always click this and confirm and just make sure that chapter seven is your last merge 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 be a type of string. It's going to be an ID with the default value of UUID. 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, 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 the message role, 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, 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 field when we update the message model. And now let's go ahead and let's create a fragment model. So the fragment model will also have an id of string and the default value of uuid 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 field's message id which we defined above referencing the id field in the message model and let's add on delete here to be cascade. So if this message gets deleted, the fragment gets deleted as 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 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 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 it's very nice that postgres 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 use case of using json in postgres 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 no errors and again make sure that you are 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 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 npxprisma 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 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 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 and even if this doesn't work you can always just create a new postgres database and then just go inside of dot 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 dash fragment. And there we go. Just like that, we have created a 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 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 go ahead and let create messages in here So inside of here, I will keep everything message related. So for example, one of those things would be all the things that go on the server regarding messages, specifically all our procedures. So now that we are inside of here, we're going to import init trpc 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 we're going to create a create 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.message create or create 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 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 required error. Great. And then let's go ahead and let's chain mutation here. is going to be asynchronous. Let's destructure the input from here like this. And then inside of here, what we are 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 dot create and pass in the data inside. and let's add the content to be input dot value here 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 put that in any type of 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 ingest from the ingest 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 and this is what we should actually do we should keep this as created message or a new message and then simply return created message simply so our api response has some kind of well response for the user 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 ingest you can you can remove zone you can remove ingest and you can remove the base procedure just like this and this is how we are going to add all other module related things inside so later when we add fragments it will be fragments router and we will control or 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.messages.create 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 dot mutate. Just like that. So right now, this should still work exactly the same, right? Let's go ahead and just quickly try it out just to make sure we didn't accidentally break something. So npm run dev in one, npx ingest 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 localhost 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 it looks like message was created. I'm going to go inside of my Ingest developer 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 gotten very good at creating landing pages. Great. So it 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, how about we extract the messages here by using use query from tan stack query. So just make sure you add this import here. Pass in trpc.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 we'll simply create, let's call this getManyBaseProcedure. 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, findMany. And how about we do orderBy. 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 now too so now you can see that i just created this create a landing page with the role 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 it's not that important right now. But it definitely created a red landing page. Great. So now, again, I'm expecting that besides having these steps to get sandbox ID, create or update, and then 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 ingest 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 URL, we have to go ahead and actually save this to the database. So let's do await step.run save result. Asynchronous method like this. And in here, let's return await Prisma.message. Whoops, we have to import Prisma from lib database. So just make sure you add this import. And we're basically going to save the content. So Prisma.message.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 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 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 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 we go. You can see I 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. And we don really have access to the sandbox URL here That 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 you will see the entire fragment 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 have to do. So 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. 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 here instead of functions and let's just change this right let's stop calling it hello world let's go ahead and call this code agent the id will be code agent and let's also change the event to be code agent run 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. OK. 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. If it's still stuck, you can always cut 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. We've basically created the message model, the fragment model, which basically puts us in a position where we can start creating proper UI around this. Because by having the fragment and by having the message, we can create the file explorer. We can create the iframe 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 one more thing. Go inside of your functions.ts in the ingest here. And after you do the result from the network run, define an isErrorConstant. And it will be an error if we don't have resultDataSummary, resultData.state. My apologies. state data summary, or if object.keys result state data files, or an alternative empty array dot 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 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 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 if we don't have the information to create it and the one thing i completely forgot about is the types 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 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 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 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 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, we just have to find all the places to use it. So starting with, 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 code agent function like this. And then go back inside of your source app API ingest 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 ingest agent kit so just make sure that you import the type tool from ingest agent 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'll 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.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. Perfect. So I think that this is it for this chapter then and let me just check how does this look like summary so this looks like it doesn't need anything because the life cycle seems to infer properly from create agent agent state 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 description coding agent like this and of course yeah if you want to you can change the name of 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 know how to fix it but let's just try build a yellow landing page just for sanity checks 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 messages. 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 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 CodeRabbit 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 system, allowing users to create and view messages with associated metadata and 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 message to the database, to the Prisma here. Great. And in here, we have 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 not fixing these comments. 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, right? We are now going to start and build the proper UI. In here, I'm pretty sure this is not needed simply because ingest 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 pagination that something we can look into later but yes it very easy to add pagination with prisma as you can see they have take they have skip and that pretty much all you need here 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 here. Let's go ahead and merge our pull 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 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, we're going to add the project schema. We're going to add message relations 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 AI generations. So let's go ahead and start by adding a new Prisma schema. As always, confirm you are on your main branch. And if you haven't synchronized 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 role. 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 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 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 servers here. And let's first do npxprisma migrate reset, simply so we remove everything from our database because we have invalid data at the moment. And once this is deleted, let's go ahead and do npxprisma migrate 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 project 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 let's go inside of server procedures make sure you are inside of projects here and we're going to change this from messages router to projects router like 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 dot project find many so that's the get many procedure for the router for the create here uh the value will also be uh the message right and actually i'm not even And 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 we're going to do the following. Const created project. Await Prisma project create. And then for the data, 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 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 it now so let me just add it here generate slug from random words slug and then in here in here the name will be generate slug and pass in two words and let's go ahead and open the settings and pass in the format to be kebab like this so that will be it for the name and now we have to immediately create the message so we can do that either 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 dot id and then in here you will have created project as the return and that's it that is our create method for the project's router so in here it would be a good idea 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. You can, of course, modify this later to however you like. Perfect. So you can, of course, also play around with this, 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's 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, they are not exactly working. So let's go inside of the create-based 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 create a new message, we will also assign project ID to the input project ID. So each message will be stored in an individual agent, in an individual 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 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 events 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 the 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 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 ingest.send simply so we see that we are sending project id in all places that we need Great, so in here we are extracting it from 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, 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.\nAnd instead, we can do create project. And this will be tRPC 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 on success. And instead, we can add on error here. And you can do toast.error, error.message. Like that. And now that we have the create project, let's go ahead and let's do create project is pending and create project.mutate. And this will be submit 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 this. Let's give this a class name maximum width 7XL 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 prompt. We can maybe expand this. Let's see. Maximum with, okay, screen. Let's just keep it at seven Excel like this. And when you write test now and click submit, 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 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 modified yeah so it's just an empty next js page but if you look at your prisma studio now and if you actually start it so let's do nvx 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 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 landing page and let's click submit and what should happen now in the prisma studio here this one is that we should have a new project now modern london with one message as you can see Let's just 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. So now when I refresh this again, I should get another message from the assistant with the task summary. And this message also has a 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, 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 app folder. Let's create a new folder called projects. And in here, open project ID. So this is basically a dynamic URL part. It's important how you write this. So curly brackets, 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 do a page export like this. And a div like this. And this will be 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 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 lowercase 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 here, which I'm going to transform into an arrow function simply because I prefer them no other reason we only need the 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 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. Amazing, amazing job. So you just added project schema. In our entire application, we will have one more model in the database, but this is pretty much 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 CodeRabbit summary. We introduced support for projects, allowing users to create and view projects, each with an associated 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 recommended changes. saying here it recommends throwing an error in the background job if it cannot find the project id and this is definitely a 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 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 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 changes, we're going to have to slightly modify the get many procedures of our messages. So before we do that, let's go ahead and ensure that we are on the main 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 Many, 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 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 destructure 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 are 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 need await here You can usually see that if you type an await on something that does not need await 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 over params, you can see that there's a promise of wrapping this. So await 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 void trpc, which you can import from the trpc server, same as get query client. And let's go ahead and actually do void query client dot prefetch query trpc dot messages get many query options and pass in the project ID, which we destructure 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 z.string with a minimum value of one and a message id is required. So should you call this id or should you call this 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. 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 here, you can go ahead and 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 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 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 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 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 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 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. 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 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 trpc, use trpc like this. trpc.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 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 now too just like that just make sure you've marked this as use client and now instead of the page here what you can do is you can change this to be hydration boundary which you can import from tan stack react query you can pass the state here to be dehydrate again from tan stack react query and simply pass in the query client then inside of here render the project view component and passing 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 HTML 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 fallback 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 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 task summary. It created a landing page, blah, blah, blah. Perfect. So this works just fine. And it leverages prefetching 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 ShatCN components. So you can import all of these from components UI 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 our resizable panels. So I'm going to give this div here a class name of height screen. And 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 a minimum height of zero. And now let's go ahead and let's do the following. In between these two resizable panels, add a resizable handle and add width handle like this. and for this resizable panel, give it a default size of 65 and the 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 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 size for the preview. You can, of course, change the default size to whatever you like. But, you know, just make 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 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 dot tx inside of the projects module when i clearly have the messages module right here well it's not the name that decides where you put something in module-based architecture it is its purpose and this specific messages container purpose will only be used inside of 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 mean that it belongs in the message module right But something that reusable like the message API that belongs in the message 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 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 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 always be better to use Suspense Query in a deeper component. Because the deeper component you use it in, 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 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 messages. There we go. And now we can import the messages container here from components messages container. And we can remove the user spans query for messages like this. and pass in the project ID here to be project ID. And now what's important is that you wrap this instead of suspense as well and give this a fallback of loading messages like this. So now the cool thing that's happening, I don't know if you will now see this. And yes, so let me try and demonstrate. it. Yeah, it's kind of hard 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 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 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 a deeper 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 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 thing for loading the project. So yes, for now, we can actually remove this because we will not be loading the project inside of the project view. Let me just move the suspense right 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 the minimum height of 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 message here, and I will render a new 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.id. Let's give it content of message.content. Role of message role. Fragment of message 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 modules, messages, server 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 through like this. And now let's go back instead 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.createdat and let's just do is active fragment for now to be hard-coded to false on fragment click will be an mpro function and type will be message.type now let's go ahead inside of the components and create the message card. Again, we're doing this inside of the projects module because even though these components are called message, they relate more to the project entity than they do to the message entity. And instead of the message card component, we are now going to do the following. First, let's create the props. Content, which is a string role, which is a type of message role you can import from at 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 because it is automatically generated every time you do npxprisma generate or npxprisma 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 role 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 message here and same thing for message type. So basically content role 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 destructure them all here. perfect now inside of this let's go ahead and do the following if role 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 uh typo here so we have this 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 .slash message card. Let me just separate my imports here. No need for use client in this component simply because the project view where it's rendered is already use 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 many and change the order by to be ascending. So the first one should be from the user and the second one should be from the assistant, 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 just above this. Const user message. And let's create an interface 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 4, 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 chatCN UI. You can find it in source components UI card. Now, instead 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 rendered every time the user sends a message. And we should be able to see 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 assistant's output. So in order to do that, we will render the assistant message like this. Assistant message. And the assistant will have 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 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 not sure but maybe these are identical to message card props It doesn have a role So yeah one less prop I not sure if this is the best way to do this but I think it fine Now let's go ahead and actually do const assistant message 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 type. And inside of here we are going to do 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 we got this when we installed chat 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 can accept an infinite number of 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, flexColumn, group, key X2, and paddingBottom of 4. And then in the second argument, I like to do dynamic 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, gap2, pl2, and margin bottom of 2. 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. 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 small and font medium like this. Then copy this span. And in here, you're going to need to install and install date FNS. This will be used to parse dates. And let me show you my package. JSON date FNS 4.1.0. And I'm going to import something from 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 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, 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 flex flex column and gap y of four and inside of here a span with content 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 the 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 developing this. And let me just see. So in here we have flex, item center, gap 2, pl2, margin bottom of 2. Okay, I think this is okay. I'm just, 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 collapse your page a bit. It should work fine. It should normally break words. It shouldn't add any scroll bars except the one from up down, right? That one should appear, but no one on the X axis should not happen. 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. 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 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 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 forward slash logo.svg. Alt will be vibe. Width will be 18. Bite will be 18. And class name will be shrink 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 using 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 to achieve so now what i want to do is i also want to add a little message on the bottom here i mean a little form on the bottom but just before i do that i also want to create a fragment 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 accept three props. The fragment itself is active fragment and on 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 dynamic class name using the CN library. In the first argument, I will add flex items start text start gap to border rounded large background muted with fit padding three hover bg secondary and transition colors and then i'm going to check if is active fragment 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 invalid fragment and here i don't need this there we go The code 2 icon will have a class name of size 4 and margin top of 0.5. I will then open a div with a class name flex, flex column, and flex 1. 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 1. 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. Make sure that you are doing this on a successful response so you have the fragment generated in your database. If you are unsure, if you still can't see it, npxprisma.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 have a fragment, right? So that's what you need to do. You basically need to create 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\nthe 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 clickable, but you don't have to fix that by adding the pointer to 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 get the idea right perfect so now what we can do is we can create the form here at the bottom and that will complete the message container so let's go ahead and go inside of the components and let's create the message dash form dot t s x 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 after the last div here. I'm going to open a new one with a class name relative padding3pt1. 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 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 auto size. 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. Cookform and form, React cookform. So all of this already exists. And they came with Shatzi and UI when we added all components. And the new one is this one, textareaautosize from React textareaautosize. And besides this, let's just see 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 sonar and let's also add some icons so that's going to be arrow up icon and loader to icon from lucid react and from tan stack query we need use mutation use query and use query client from TanStack React query. Then let's add cn from libutils, use trpc from at trpc client, the button component, and form and form field from components UI form. This is another chat cn UI component. And when you installed that, which you did using the dash dash 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 so like this now how you're going to call this value string 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 following now that we have this form schema const form use form pass in z.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 on submit to be form handle submit. And then we have to create a custom submit form. So const on submit here will accept the values, which are basically 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 on submit and pass it here. So now this on submit will only trigger this, which will actually initialize the network call when the 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 for padding top one rounded extra large background sidebar dark BG sidebar and transition all like this. And then if isFocused, which doesn't exist yet, we were going to do shadow extra small. And for show usage, we are going to do around at top none. So now let's go ahead and just quickly fix these things. So for isFocused, it is an easy fix. All we are going to do is add a new useState here with isFocused and set isFocused with the default value of false from useState 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 have imported it. Give this a control of form.control. Give it a name of content. and give it a render of field like this. And inside, use the text area authorize self-closing component. In here, you can immediately spread everything you have from the field above. And then go ahead and give it the following. Give it an onFocus and onBlur to modify the set isFocused state 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 doesn't look so weird so let's go ahead and give this a minimum rows of two and the maximum rows of eight and then a class name, heading top, four, 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 to 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 on submit whoops on submit and pass the event as well so the on submit is this just like that so now uh outside of this which is form filled i believe yes outside of form filled but still inside of the form let's go ahead and do the following let's add a div with a class name flex gap x2 items and 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 you see where that is so it's right here at the bottom So this will now be the following. It will be a keyboard sign. I think this is for keyboard, the short name for keyboard. Render a span inside and render the following sign like this and then enter. And that will turn like this, the command sign and enter. Now let's style it. The class name will be mlAuto, pointerEvents, none, inlineFlex, height5, select, none, itemscenter, gap1, rounded, justrounded, border, backgroundColor, muted, tx1.5, font, mono, text, 10px, fontmedium, medium and text muted foreground 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 the following it will render arrow up icon which we already have imported from Lucid React There we go And now we are going to style it Give it a class name of CN size 8 and rounded pool like this There we go. This is how it's going to 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 trpc here, use trpc. And then we need to add create message from useMutation, trpc, messages, createMutationOptions, like this. And then you can extract the following. You can then extract const is pending to be create message dot 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 createMessage mutation, let's go inside of the onSubmit and let's make it an asynchronous method. And let's do await createMessage.mutateAsync and pass in the value to be data. actually this is values so values dot value and the project id like that perfect and now let's use the is pending and let's use uh the is the 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 to this button and the button will be a little bit different. So this one will be disabled if it's 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 so you don't make a mistake. There we go. And let's also do if is button disabled, background, muted, foreground, and border. like this and then inside of here a ternary if is pending in that case we are rendering the loader to icon which we already have imported with the class name of size or an 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 run dev, npx ingest dev. 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 ingest 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 just add some on success things to happen in the mutation options of the create 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 I'm not sure do we have it? We don't. So 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 going to do is query client.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 we don't have yet, so I will add a to-do, re-invalidate or invalidate 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.error.error.message. 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 we can do here and there we go we have a response now created a full 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 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 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 stop minus 6. Left 0, right 0. Height of 6. Background gradient to bottom. From transparent to. Whoops. to dash background forward slash 70 pointer events none what this will do is it will create an ever so slightly 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 to background and then you can see you can't you can't see the clipping at all. It's like it fades into some kind of fog, right? So it's 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 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 top like that. And then what we are going to do is we are going to change this to be useEffect, 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 send so make sure you're using the find last api 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 so actually i'm not sure if we can do that so let me just do to do and let's do set active fragment right so we're going to do this well maybe in this chapter maybe in the future I will see but let's add messages for now like this and then let's go ahead and add another use effect and in here we will do messages.length and we are going to check if bottom ref dot current question mark scroll into view like this and let me just check i think that for now this is okay if i do a refresh here uh looks like it's not working 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 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 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 access some settings here and see the project name. Great. So we've already made some great progress here. And you can add something 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 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 going to open this. I'm going to 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 is a free CodeRavit extension, which you can use to improve your code 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 CodeRabbit 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 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 prefetching, including invalidation, including refetching, everything. Amazing, amazing job. I'm going to merge this pull request. Once the pull 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 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 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 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 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 want to go inside of my project view, inside of projects, UI views, project 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 these imports here. Once you've added that, let's go ahead and let's modify the messages container component to have a few more props. Let's add active fragment to be 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 the useEffect here, if we detect the last assistant message, call setActiveFragment and set lastSystemMessage, my apologies, lastAssistantMessage.FragmentInside. But only if we have lastAssistantMessageFragment. Well, actually, since it's going to be null, hmm 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 dot id to be identical to message dot fragment question mark ID and set active fragment will on fragment click will call set active fragment and pass the message dot fragment inside like 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 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 and message.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, 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 messages 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 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 rough and let's do if last message is user add message loading state like this. Now let's create message loading dot dsx 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 Schumer 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, set interval And inside of this interval, every two seconds, I'm going to call set current message index previous plus one modulus messages of 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 are going to return a div and a span and inside, render the currently active message like this. Now give this 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 2. 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 2, and padding bottom of 4. Then a div of class name, flex, items center, gap 2, pl2, and margin bottom of 2. Then we're going to render an image component with a source of logo svg up of our project name, width of 18, height of 18 as well, and a class name of shrink 0. 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 pl8.5 flex, flex column, and get y of 4. 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 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 five seconds. So now even without you refreshing, it's going to refetch the messages every five 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 look at your network request, every five seconds there will be a network for refreshing the messages. But don't worry, 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, like this. Now inside of here, let's go ahead and add the following imports. Link, image, use 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 left edit sun moon icon and then let's add use trpc from trpc client button from components ui button and all of these imports from the drop down menu the menu itself content item portal radio group radio item separator sub sub content sub trigger and menu trigger, all of those things. Now let's go ahead and let's create an 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 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 dot components 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, let's go ahead and add a header tag right here. Let's give it a class name of padding2 flex justify between items center and border bottom. And let's call it header. And let me just refresh here. And it looks like it's not showing. Now it is, but it is very small, I believe. So let's go ahead and let me see. Oh, my apologies. No, it is not 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-down menu here. We have all of these components imported. Now inside of here, add a drop-down menu trigger and give it an as child 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 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 and there we go this now becomes a drop down menu it doesn't have the a 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 down 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-down menu separator. So let's do drop-down menu separator here. There we go. And below that, add a drop-down menu sub, and then a drop-down menu sub trigger. Give this a class name of gap2. 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 submenu here. Now let's go ahead and go outside of the trigger and add drop down menu portal. Inside of the portal, add drop-down menu subcontent. Inside of subcontent, add drop-down menu radio group. Give it a value for now 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 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 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, 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 set theme. And I think that is pretty much it. If you try clicking on dark mode, 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 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 ahead. I can see that we have some issue here. Every time I select this fragment, very soon the bottom one starts to select. So I'm pretty sure that something inside of my messages container... Oh, yes, the refetch interval is probably causing this to refetch every time.\nSo 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 is selected, only you can select it. It's okay to be like that now. 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. 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 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 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 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 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're 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 you can turn this into a boolean like this perfect now let's go ahead and let's go inside of components fragment web dot tsx now in here let's create an interface props with fragment from generated prisma now let's go ahead and let's add a couple of more things useStateFromReact, external link icon, and refresh CCW icon. And then we're going to use the button component from components UI button. Now in here, let's go ahead and export function fragmentWeb, 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 iframe. And we're going to add key. Actually, we can't do this yet. My apologies. So for now, just do a class name, height full, with 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 components fragment bed 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 features to make this seem like a little 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, padding2, 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 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. Variant of outline and on click for now, just an empty function. And now you have a refresh 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 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, like so. and keep the variant and keep the size. Let's just add a class name here to be flex1, justify start, text start, and font normal. So now you have a big 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 a 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 underscore blank as the second argument. So it opens in a new tab. There we go. Now let's go ahead and go inside of iframe and let's add, I keep doing the key, but I keep forgetting to implement the key. Let's finally do that. So go to the top here and add fragment key and set fragment key and call useState like this. Then let's add copied and set copied, useState, false. Let's add on the refresh method. set fragment key previous previous plus one const 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 two second timeout. 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 is not 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 can see how 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 a whole new refreshed page. 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 instead of source 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 inside of the UI folder And in here we going to mark this as use client and we going to import everything tooltip related from components UI tooltip You already have this 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 you're going to do is you're going to add the tooltip provider you're going to add the tooltip itself and then you're going to add a tooltip trigger like that as child property and the render children inside and then you're going to add a tooltip 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 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, 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, 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 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? 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 the summary. We introduced our reusable tooltip component for displaying contextual hints. We added a web fragment preview component with controls to refresh, copy, and open the preview in a new tab. We enable the live preview of the project fragments directly within the project view, including interactive controls and tooltips. Perfect. As always, an in-depth walkthrough as well as a sequence diagram here and some actionable comments. So yes, navigator.clipboard.writeText is technically a promise. So you can do .then 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 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 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 of this chapter, so let's go ahead and mark this as complete, and see you in the next one. Amazing, amazing job. In this chapter, we are going to implement the CodeView. 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 ProjectView component so that we can switch between the FragmentWeb component and CodeView 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 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 ShatZN 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 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, 0. Default value will be preview. Value will be tab state. And on value change will get the new value and set tab state to be value as preview or code. Like this. Then inside of these 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 2 border bottom and gap x of 2 and then open tabs list and again encapsulate the fragment inside of the tabs list give it a class name of height 0 padding 0 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 eye icon here, which you can import from Lucid React. And while you're here, also import code 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 mlauto, flex, items, center, and gap x of two. And this div will encapsulate a button from components UI button, which will 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. 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. npm install prism.js. This will be used to highlight code syntax. So let's go ahead and develop this simple component inside of source components. So in here add code view dot tsx And in here you going to need just one more thing so let actually create a folder code view like this and move this inside and you can change this to be index dot tsx and then go inside of your uh vibe assets you can use the link in the description or you can see it on the screen here and find codetheme.css. So it's quite long. That's why we're not typing it. So go ahead and copy this and create it here. So codetheme.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 Prism.js and let me show you Prism.js. Oh, did I install it or not? npm install prism.js. Let me see. prism.js. It is installed, but I think I also need to do npm install dash d at types prism.js. There we go. So prism.js 1.30 and types 1.26.5. And now it works. Below that, import use effect and then import the following things. prison.js 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? Then export const code view and create a simple interface. Props. which accepts the code, which is a string, and the language, which is a string. So assign the props. Let's destructure them. Code and language. And inside of here, return a pre-tag. Give it a class name, padding2, backgroundColorTransparent, borderNone, roundedNone, margin0, and textextrasmall. 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-lang, like this. Usually, if this was a TypeScript class name, you would not do this, because 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, 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 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 you can import code view. So you don't have to go to index. Index can be 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.ts. And let's go ahead and let's prepare the 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 ShatCN, 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 view, which we just created. And then let's go ahead and add resizable with resizable handle, panel, and panel group. And let's also import everything we need from breadcrump. So both of these come from ChatC and UI, so you have them. Breadcrump, item, list, page, separator, and ellipsis. 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 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 define our files here. So I like to do this because I think it visually 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 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 a component which we are going to need in order to even render 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 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 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, 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 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 the 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're 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. 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 passing the files and then return file keys.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-select 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 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 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 if you have active fragment question mark files render the file explorer component and passing the files to be active fragment dot 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. And I've had some trouble making this work. Oh, it looks like it's working just fine. Okay, my version had some problems. I think it 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 easier to develop the code view first simply because we already have the code 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 a new div the class name border bottom bg sidebar px4 py2 flex justify center actually it will be justified between, items will be center, and gap x will be 2. Then let's add to do breadcrumb, file breadcrumb, 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 mlauto, onclick 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 and the hint imported. Great. And now, below this div, we're going to open a new div with a class name, flex1, overflow auto, and render the code view. In here, select the code to be files and then selected file. And the language will be... Whoops. the language will use our function get language from extension selected file oops 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 something, obviously, because it's preventing us from doing this. 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 devalue code, add a class name and 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 project view, in the tab content for the value code, add a minimum height of 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 tree view so we actually see the file we selected 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 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 file. On select will be an empty function. Now let's go ahead and let's prepare these things here. So what I want to do now is the following. 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 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 tree item. So now we need to create the tree item object so let me just 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 start 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 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 those tree 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 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 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 are going to do is a for loop. So for const file path of sorted paths, let's go ahead and split the file path into parts by doing file path dot 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 part's 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 name is equal to null. This will basically indicate that it's a file. This is quite confusing, right? But once you see how the file structure will look, it will make a little bit 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 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 the 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 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 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 destructure 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 subtree using convert node and passing the value and the key so we need to go deeper we need to recursively call this function again until we find a file if array is array subtree children.push. Open an array of key and spread the subtree. else children.push key and subtree. Like so. And then, let's go.\nLet's go ahead outside of this for and return the children like so. And then let's define the result here to be convert node tree and return array is array result. 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 i have added my entire uterus 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 tree items right so if you want to do a double check or you just want to copy file 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 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 free 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 copying this file again pasting it uh whoops copy it again I cannot seem to copy it. Let me try one more time. I think the copy button is still using the old one. Okay, finally, no errors in the utils file. Okay, so now that you have the convert files to three items, we can go back inside of the file explorer. and in here 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 you can either write that function yourself as we just did or you can copy it from the source code or from the assets folder 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 callback and inside of the use callback we're going to 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 the tree view component so i'm going to go inside of components tree view dot tssx 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 view let's assign the props tree view props in here we get data value and 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 tree view. And if you go back to your app here, select a fragment and click code. There we go. I have app folder and page dot TSX. So those are for me. But if I go into one of my older ones, well, it looks like all of these are pretty simple. So it's going to keep using just a simple example. But if you tell it to build something complicated, 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 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 chat cnui 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 with pool 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 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, which will be an optional string, or it will be null. onSelect, which will be an optional function, which accepts the value, which is a type of string and returns a void, and parentPath, which will be a string. So let's go ahead and add three props here, and the structure, the item, selectedValue, onSelect, and parentPath, 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. Otherwise, put the item in the array. Then let's get the current path. This will check if we have a parent path, and it's going to render dynamically using backdix parent path forward slash name. Otherwise, just the name of the file. If we don't have any items, 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, which is active to be is selected. and class name will be data active true background transparent. And on 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 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 if clause. And this means it's a folder. And in here, return sidebar menu item with collapsible. 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 down here. And instead of the sidebar menu item, open collapsible like that. Let me just fix this. 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 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 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.map 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 selected value to be selected value on select to be on OnSelect to be OnSelect Oops ParentPath to be CurrentPath Like that. And that's it for the tree 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 instead of the sidebar menu. And simply do Data.MapItemAndIndex. 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 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. So there we go. Here we have it and we can open and close it. 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 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 code. And yeah, I still am not able to select this. So let's go ahead and look at what we forgot to do. So OK, these breadcrumbs are unused. That's OK. We're going to use them in a moment. But it seems like this handle file select 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 not sure if you can see well you can definitely see the code is changing right And also the file 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 is put it inside of curly brackets like this. Excellent. So now that we have this, the good thing is no need to do anything more in the tree view. 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 file breadcrumb thing first so i'm gonna 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 four segments, we are going to be responsive about it. Now let's do const 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 dot map segment index const is last will be if the index is equal to path segments dot 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 breadcrumb item check let me just feel bread bread crumb 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, 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 ellipsis like so. So render the breadcrumb separator and then a breadcrumb item which renders the breadcrumb ellipsis. 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 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 Or it uses the responsive mode and it will just replace the folders in between with an ellipsis, which is basically three 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 callback. If we have the selected file, write 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. 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 pass in copied. If it's copied, we will use the copy check icon. Otherwise, the copy icon. Like this. And you should no longer have any errors in your code because we are 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 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 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. And you can see that I definitely got 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? If I open this too much, it breaks. So what you can do is you can remove the maximum segments here to be 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. Okay, so you can see it works, but it seems like we have some problem here. Okay, list cannot be a descendant of list. Okay, it's a hydration error. It's not exactly too big of a problem, but 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 to show that for three segments or for four segments right Whatever makes sense for you. Amazing, amazing job. So I think this was a much harder chapter, but I think it was worthwhile. We have an actual 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 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, there's a free CodeRabbit 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 CodeRabbit summary. We introduced a code viewer with syntax highlighting in GitHub Dark Theme. We added a file explorer with tree view, breadcrumb navigation, and a copy to clipboard functionality for code files. We enhanced the project view with tab 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 will 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 prison.js code highlighting. So very, very good. We have a few comments here. This one is a good comment. it's basically telling us to also add an actual type of check. The reason it's telling us this, because 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 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 home layout, the homepage component, which consists of project form and project list. Let's go ahead and let's start our app. Running npm run dev in one terminal and starting in just the dev server in the other. And now let's go ahead and make sure we are on the main branch. And you can click on the synchronized changes just to make sure everything is up to date. Inside of your source 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, I'm loading my previous 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 five seconds. Why after five seconds? Because we refetch every five 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 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 message will use messages, find last, and then simply find the message whose role is assistant. So messages.findlast API is that we are using. And then in here, we're going to open an if clause. And what we're going to do is we're going to check if last assistant message fragment exists. and if last assistant message.id is not identical to last assistant message id ref.current. 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 have to update last assistant message id ref.current 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 our refresh again and there we go you can see how it selects the fragment the last assistant message it can find but if i manually select this one let's wait for five 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 user's selection is if an actual new message arrives. I think that's an okay UX. If you want to, you can 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 having this older fragment as selected only after 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 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, 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 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 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 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 the tutorial if that's something we can fix. There might be some solution, but you know, it's not too big of an issue. Great. So now let's go ahead and let's actually build the home page. So we're going to go and do the 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 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 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 screen, and the maximum height of screen, like so. And in here, let's add a div with a class name, flex1, flex, flex column, px4 and padding bottom of 4 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 layout and it's important that you do a default export here. What I want you to do now is I want you to 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 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 active. So what did we do now? Well, if you go and click back to the dashboard, nothing changes right That because what we just did is we created a layout for all of our home pages Right now this doesn make too much sense because we only have one page the home page 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 create a nice little reusable layout like so. Now in here, let's go ahead and let's do the following. I want to create a self-closing div like so and give it a class name of absolute inset 0 minus z 10 height of full width of full bg background. On dark, use bg radial-gradient like so. And then inside of here, write 393E4A underscore one 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 this is not important this is just for a cool effect you're going to see in a second 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 color of this to not be this one, but instead be d-a-d-d-e-2. And this can still be transparent. And then just add another one, background-size 16 pixels underscore 16 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 home page 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 5XL, maximum width of auto, and width full. I will then add a section with a class name, space Y6, PY of 16 pixels, my apologies, 16VH, 2XL will be PY48. 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 wide, width of 50, height of 50, class name of hidden MD block. 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 five excel font bold and text center and you should already be seeing 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 3xl mx auto and width full and nothing will appear now that's because we have to create a new component called 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 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 can 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 code if it's for one, two, three instances. 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 and I'm going to go ahead instead 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 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 actually we don't have to reset the form and i'll tell you why because on success we are going to uh reinvalidate anyway so let's go ahead and do this after we reset the form the only thing we should actually i'm sorry after we successfully create a project, the only thing we should do is we should call trpc.projects and we should just refetch getMany. That's the only thing that should happen. And then also we should invalidate the 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 here. when you successfully do this let's do router.push and we push to the newly created project so that's going to be forward slash projects data 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 will be calling create project and we don't need the project id here at 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 home page so you can remove this show usage here like so uh what would you like to will can stay the same to 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 inside of our app home page.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 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 because I think it's super simple and works almost every time. And there we 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 want to, you can wait for the result. I know it's very fun to always see the results. 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 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, go outside of this native form elements and create a div with a class name. flex wrap justify center gap to hidden md flex and the 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 constants.ts. And in here, I just created a bunch of project templates for you. 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 inside of home. I will create new constants.ts and I will paste that here. So basically it's something like build a Spotify clone, build an Airbnb clone, build a store page, YouTube clone, file 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, 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, use a nice dark mode compatible gradient here, movie sections, responsive card, and a model for viewing details using mock data and local state use dark mode, right? So it's a very descriptive 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 Sonnett understands your instructions,\nvery, 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 ShadCN installed, right? I have to tell it more in-depth about what's going on. So make sure you have this project 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 slash constants here dot map 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 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 empty. And then let's put template 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, you can select any of these. So let's go ahead now and just properly space these things out. So what I want to do is I want to wrap my form instead of a section with a class name space Y 6, 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 spacing 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 a value 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 what's important you do is you enable all three should dirty 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 have to add the on select to the buttons. So call on select and pass in the template dot prompt. 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 ingest is that if it notices a rate limit it won't retry immediately. It will retry with exponentially longer pauses between each retry which if you're using OpenAI is perfect because OpenAI has reasonable timeouts. So when you hit a limit in OpenAI, they punish you with like two seconds of waiting time. So Ingest 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 OpenAI are quite a good combination. 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. In 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 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 or not. And there we go. So almost the exact same thing as we saw in the initial demo. Amazing. And you can see the code here. Very, very good. So I would suggest that you try a couple of these. And if some are obviously failing, well, you can try and 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 allow the user to select something that will 100% work. You don't want to give them something that might work or might fail. Perfect. And I'm just super interested. Let me just go back here. I want to change this to dark mode. I want to see how this looks like. Looks pretty good. Great. But I actually prefer working in light mode. So let's go ahead now and let's develop the bottom part, which is a 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.tsx where we render the project form. And we have to render the project list outside of this section. So projects list like this. And then let's go inside of our home modules here. So home UI components, 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 10 stack react query. And use the RPC from the RPC client and button from components UI button. Let's export const projects list here. and let's start by defining the rpc then let's define data projects to be use query the rpc 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 8, border, flex, flex column, gap Y, 6, SM, gap Y, 4. 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 projects, 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 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 one sm grid columns three and gap of six and then in here check if projects dot length this should be a question mark so if projects dot 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 projects found and a class name text small and text muted foreground. Otherwise, let's do projects.map, get the individual project here, and then return a button. Give this button a key of project ID, a variant of outline, and the class name font normal height auto justify start pull width text start and 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 they have an h3 element with project.name inside and give the h3 element a class name of froncate and font medium and below it a paragraph using format distance to now, which we imported from date FMS, 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 a landing page and we added the templates, we added the 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 still missing like the nav bar, 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 a 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. I'm just going to wait. Hopefully it will work. If not, you know, it's just a lesson that these AM models are a bit undeterministic. You can't really rely on them too much. But if you spend you know more than I built this app in a span of a month right So I couldn 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. 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 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 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 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, you know, you don't have to use this 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 simply because 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 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. 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 to do that, but let's see what it created. because I think that this is very interesting, actually. Can I rename this? Okay, I can 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 end, but you know, the prompt can always be better. Again, I'm not a prompt 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 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 home page. Let's go ahead and close everything here. And I will go and create a new branch. 14 home page. Like so. I'm going to stage all of my changes and I will create a commit. 14 home page. 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 creation form with template section, validation, and keyboard shortcut 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 predefined project templates for faster project setup. We also fixed the fragment handling to prevent repeated state updates exactly. And we also updated the resizable handle styling for a smoother and more interactive user 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 walkthrough. 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 loading states here in the project list. We could very much do that. We could even leverage our prefetching and suspend. And 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 instead 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 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 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. And in here, you can click copy or you can manually select and copy things. And then go inside of your source app, globals.css, ensure that you're on your main branch and you can synchronize changes if you aren't sure. Make sure that the last merge was 14 and simply replace the entire globals.css file. So in here, 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 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 CSS 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 GitHub assets, you can use it. But let me show you how I even found that theme. I basically used TweakCN.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 ChatCN UI. And I think it is super cool. And the one I selected was Claude, 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 something that I haven't tried before. Let's see. How about I try this clay morphism? 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 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 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 of Claude. So I'm going to select Claude right here, 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 look. And it has nice borders. Everything just looks nice with 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, two years from now. I hope it will because it's an amazing project. But then again, Shadzian can update a lot and they will probably up their day app accordingly to that. So because of that, I'm offering you myglobals.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 using this, myglobals.css, you will notice that now buttons have cursor 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 have this it will not come with tweak cn so it doesn 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 snippet if you want to and then your buttons will have proper cursor pointers. I just think that this looks way better than anything 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 recommend using myglobalist.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 myglobalist.css. Keep in mind that this is for let me show you next version for next 15.3.4 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 or if you're using the exact version as me it will 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 new variant called tertiary. I don't know how to pronounce this to be honest. I never, I always mess this up. But basically it's going to be background primary with 25% opacity. On dark mode it's going to be BG primary again but 30% opacity. Text will be primary. Shadow will be extra small. Hover will be BG primary 20. BG primary 20. And on dark mode, hover will be BG. Let me just check. On dark mode, hover will be BG primary 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, right? And then when you look at it, it will look like this. So kind of a 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. 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 it will change. We'll change this to tertiary, this new variant here, and we change our theme overall. So I'm going to create a new branch, 15 theme. I'm going to add all of the 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 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 should be 15 team. 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 Clerc 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 Clerc, you can actually find Ingest, the company that we are using for our background jobs. And you can confirm that yourself by going in their sign-in 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 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 in 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 Next.js clerk package. But just before you do that, ensure that you are on your 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 clerk next.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 ahead and add clerk and paste these two. I like to wrap them in parentheses, but I think this might depend on the system. I think Windows might have problems with this, 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 parentheses. They don't have to be in parentheses. So all of these could actually be without parentheses, if that's something you prefer. I just feel like the syntax looks better with parentheses. I keep saying parentheses. I mean quotes. Sorry. Okay. Now let's create our middleware file. So that's going to be inside of the source folder, create middleware.ds. Make sure to not 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 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 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 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 run dev here. You don't need to start your ingest right now because we will be doing some other 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 the fastest way to add authentication. So let's click continue to the next JS guide. And the first thing we're going to do is we're going to add these sign 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 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.tsx inside and now let's go ahead and let's import sign in from clerk next js and let's do a page export here with a div which will have a class name of flex, flex column, maximum width of 3xl, mx auto and width full. And then let's add a section which includes a class name space y6 padding top of 16vh and 2xl 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 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 signup page and replace sign in import with sign up import. And then let's go ahead and do the following. So we are going to set this to be public route. So we are going to go inside of our middleware.ts. We are going to import create route matcher right here. and we're going to define a constant is public route using create 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 sign in URL is forward slash sign in And the fallbacks will be an empty forward slash So next public clerk sign in fallback redirect and next clerk sign up fallback redirect URLs Perfect And now I think that already you should be able to see 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 one, two, three, you get immediately redirected back to the sign-in 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, actually, yeah, I think this, we also need forward slash API ingest like this. So make sure you add this, otherwise background jobs will not be able to work. So we need to allow ingest 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 and yes i am in dark 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're 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 just copy the sign in and do it right 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 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 dot environment here and let's just add So next public clerk, sign up URL. And the redirect URLs for sign up fallback and sign in fallback. And it looks like these are duplicates. So yeah, I think you only need one of these and one of these. So yeah, you can remove these two and just move these two. There we go. At least I think that they were duplicates, right? I think they were. and now you should be able to go 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, you should be taking on the same layout as you can see it loads the sign up page. So now you can switch between the two. Perfect. So now what I want to do before we even log in, I want to go back to the 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 link. Let's import image from next image. Let's import all of these from clerknext.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 const navbar here. And let's return a nav element. Give this nav element a class name of padding for background color transparent. fixed top 0 left 0 right 0 z index of 50 transition all duration 200 border bottom and border transparent inside of this nav add a div with a class name of maximum width 5XL MXAuto with 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 the 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 semi-bold, 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 the 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 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 flexgap2 and add signup button and add a normal button inside with a variant of outline and a size of small and render signup inside. You can copy this and then change this to signin 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 sign up and sign in buttons that you can access. Only if you are 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 can also load these apps. So the reason they previously weren't even loading 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 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 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 anything other than this to be a public route. Also, you can create the exact opposite, right? You can call this isPrivateRoute, and then all of these will be private routes, right? And then you will just modify your logic. You will remove the exclamation point, and you will 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 call 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 you have a bunch of public routes then just do the reverse logic, you know? Great. Now let's add the user control component. So I'm going to go ahead and close everything. I'm going to go inside of source components and I will create user control dot V S X. I will mark this as use client and I'm going to import user 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 and every time I have created it in a component 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 imported user control. And there we go. And in here, you now have this 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, 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, 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. 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 your entire API like this. Then you don't have to worry about ingest or TRPC specifically because our API should be protected in a different way\nin the first place. Let's just do a sanity check. What do we have here? We have ingest and we have trpc. So ingest needs to be publicly available simply because ingest will contact this, no one else, right? And if you're wondering how this works, they probably have some kind of 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, 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 recommend allowing all API endpoints to be public routes. So now let's go ahead and let's 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. We already have hooks. Great. So this came with Chatsy and UI. And now create useCurrentTheme.ts. So we already have useTheme from nextThemes. And we already use it in the project header, useTheme. The problem with this is that it has 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 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. 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 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 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 user control right here and we can adapt it as follow. Post current theme, use current theme, like this. And then inside of here, let's also import. I think we need to install a 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 ShadCN 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? you can try moving both the clerk provider and trpc react provider here maybe but i'm not sure how that works right i'm not sure if it matters but 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 to like this inside, then you can create an abstraction around clerk 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 Lightroom mode. So let's go inside of sign in page right 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 clerk themes. Now in here, we can extract the current theme and for this sign in, let's add appearance here base theme will check if current theme is equal to dark and use dark otherwise 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 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 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 theme. Great. So let's see where we are. What did we do? We created a clerk account. We updated our environment. We added clerk provider, signup screens, 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 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 any random project here. You can see that I have my user button right here. And let me just switch to light mode to see everything still works 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 I'm going to remove this comment. and I will return alf to be await alf from clerk next.js server. After that, I mean just below it, I'm going to export type context, which uses awaited return type type of create trpc context. And you can actually find these exact instructions in clerk documentation here. Let me just find trpc. Maybe I can find it. integrate clerk into your next JS plus TRPC app. Let me see if that is 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? 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. For example, they have a specific instruction to now add create context to this TRPC route. But if you look at it, 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 outed, like so. And I will do T.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 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 and then after this if clause return next and extend the context to include out now we have to fix this problem is 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 And you can see that now we have the alf property here, which we added here. Perfect. And it's important that we also cache this in React. So this doesn need to be called every single time And we are simply relying on the user ID from it here So make sure you don do any mistakes here because now we finally going to go ahead down here and do export const protected procedure to be t.procedure.use is authed. 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 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 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 one two three and i can remove them and i can add protected procedure like this and nothing really changes now so i'm just now using protected procedure for each of these forget many or 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, 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, let's, for example, imagine that we can query the messages by user ID. Imagine if I do user ID here and do context.alph, and then I try to do user ID. In 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.userID and then I have to throw new trpc error, blah, blah, blah. But that makes no sense. We just created the protected procedure which does that for us and then passes the 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 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 layer called trpc. And in here, I have my protected procedures. So if this middleware fails, 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 layer and you should protect your routes individually because let's imagine this middleware breaks, which can happen. Next.js had a middleware security issue just a few versions ago. And people who were shocked that that happened, you 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. Don't confuse this middleware with, this is also technically a middleware, right? 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 explain the difference. So whenever you use the middleware, it should only be used to improve user experience, like adding redirects, which is 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 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 hope 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 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 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 protected procedure, I have eight results in three files, the third one being init 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 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 want to. If you have any architectural reason for doing that, of course, you can add individual user ID for the fragment, individual user ID for the 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 user ID to my 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 npxprisma 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 npxprisma migrate dev. And once it connects, let's simply call this user ID or auth. Here we go. I'm going to call this user ID. And there we go. Now I'm going to do npm run dev and I'm going to run npx ingest CLI latest dev. So both things should be running and just double check that it was able to connect to API ingest. because as I said, if you accidentally don't allow your API here, then ingest 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 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.alph user ID. As simple as that. Problem fixed. Now, what we have to do next is we also have to modify the getMany. So whose projects are we loading? Very simple. The currently logged in users projects. So let's add a where here. Like this. User ID matches context.alph user ID. And now we are only loading this currently logged in users project. and same thing for get1. 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 project before. So full security in our application. And now we have to do the same thing but for messages So let go inside of procedures here and let check get many So in here I going to extract the context as well And I 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, 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 const 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 out user id like this i mean maybe we can somehow do it from 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 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 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. Great. So we now have this done right here. And then we can safely do the 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 are 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. 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 modification here. And that modification is that I'm going to load the current user from use user, which you can import from clerknext.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 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 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 actually do const clerk, use clerk from clerk next 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, you can do clerk.openSignIn, like that. 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 sign-in model. So I write test. And there we go. It opens this nice model. I think it looks cool. If you want to, you can also just do, you know, router.push sign in. That also works. There we go. So whichever one you prefer, I just thought I would show you this cool alternative. Great. So I'm pretty sure that we don't have to do anything more besides test the apps. So now I'm going to log in here. And as you can see, it says John's 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 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 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 I can load my messages. I can see my fragments. So all of this is obviously working. Perfect. And let's try 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. We're going to fix these details in the last chapters, right? Obviously, it's not ideal that these 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 I'm going to show you how to add proper 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 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-aware styling. We introduced a navigation bar with authentication controls and user 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 user-based access. Exactly what we did in this chapter. As always, file-by-file walkthrough here and a whole sequence diagram explaining how our new clerk provider and authentication TRPC procedures work. and we did a very good job this time. The only comment is in the migration SQL. 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 OK. 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 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 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 a clerk 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 shocked at how good it is, because we all know that Clerks' developer experience is immaculate. They have completely solved the issue of complicated code. Everything regarding Clerks 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're doing it with Stripe. So you're going to be so impressed by the fact that we won't even need a webhook for this 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 find billing and in here you can select B2C. You can also do B2B, but in this case it will be B2C SaaS. The first thing we have to do is enable billing So we have to go to billing settings This will redirect you to your 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 code base to check if the user is on that plan right so we're going to check if free underscore user is active that means the user is currently on the free tier and in here make sure to check publicly 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 instantly from them. And in return, they're going to have a bit of a cheaper plan. You can, of course, assign the price to whatever you want. And click save. So right now in your subscription plans, whoops, right here, you should have two subscription plans, one free and one pro, which is billed monthly or annually. So in here, let me just go and click on the 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 config 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 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 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. Like this. Let's mark this as useClient. And then let's go ahead and let's import image from nextImage. And let's import pricingTable from clerkNextJS. 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 nothing more. If you now go into your app, just make sure you have it running. And if you go to forward slash pricing, it will redirect you to the login page. So let's just make sure to add that here. So forward slash pricing, like so. Now you should be able to go to the localhost 3000 pricing. There we go. We have a pricing table. So we didn't have to code a 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 3xl, 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 this. Starting with the section, which will have space y6, padding top of 16vh onto 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 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 make sure you're putting the exclamation points 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 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, 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 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 the new feature five monthly credits. And now if you want to do this, is where you will add even more features. For example, public projects, something like 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 modify the other plan, which is the pro 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. Basically, whatever you plan to 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 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, drag and drop this however you like. I just wanted us to add a lot of features here simply because the pricing table looks better if you add more features here. And I absolutely love how this looks. So now that we have this finished, let me just go ahead and do 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 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 amazing. Now let's just go back to the pricing here and let's enable dark mode if we need it. So I'm going to import dark from clerk 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, 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. And if you try and subscribe, you can see that you're redirected to login. So you don't have to immediately try and subscribe, simply because I want to demonstrate the entire upgrade 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 code for that. We automatically added this user to the 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 tryouts so you can see how this looks i think it's just you know amazing 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 going inside of source hooks and let's create\nuseScroll.ts like this. And inside of here, let's go ahead and do import, useState, useEffect from React, exportConst useScroll, and let's add the threshold to be 10. Define the state is scrolled and set is scrolled. useState by default is going to be false. Call useEffect with for now an empty dependency array. Like this. And inside, create a handleScroll arrow function, which we'll call setIsScrolled to be windowScrollY, 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. And all you have to do is return is scrolled. There we go. So now that we have useScroll in our app, we can go back to the navbar inside of home module, UI components navbar. And in here, you can now easily get isScrolled, useIsScrolled, useScroll, my apologies, from hooks, useScroll. And then make this a dynamic class name by wrapping it instead of curly brackets and adding the cnutil. 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 bg background and border border. So now if you scroll ever so slightly, you can see that the navbar starts to stop the being transparent and a 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 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 limiter 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 run dev and then in here I'm gonna go ahead and do the following inside of prisma schema I'm going to create a new model 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 model. Now let's go ahead and let's do npxprisma 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 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 the 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. 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 async function get usage tracker 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, give everyone five points. And for the duration, let's go ahead and let's do 30 days. So 30 times 24 times 60 times 60. Now what I like to do is I like to do const three points. and let's go ahead and give everyone three points so you can replace this like so. Then let's do consideration to be 30 times 24 times 60 times 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 consumeCredit. So export async function, consume credits. And in here, let's go ahead first and let's extract user ID to be await out from clerk NextJS server. Like so. And make sure to execute this. If there is no user ID, we can throw new error here. user not authenticated. That's the first thing. And then let's go ahead and do const usage tracker to be await get usage tracker. Basically this function which we defined above. And then in here const result to be await usage tracker dot consume passing the user ID and then how many points do we want to take from them. So for that, I'm going to define cost 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 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 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 of flexible counts and limits the number of actions by key and protects from DDOs and brute force attacks at any scale. It works with all key, Redis, Prisma, Dynamo, Process Memory, Cluster, PM2, Memcached, Mongo, MySQL, SQLite, and Postgres. Also works in the browser. It offers atomic 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 solve 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? I'm using it as a simple rate limiting for premium credits. I haven't really seen any advice not to do it, but since it solves 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 given the fact that we can easily add it to Prisma, right? So that's why I chose this package. 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 these three functions, a function 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 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. 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 const usage router to be create trpc router status is going to be protected procedure query asynchronous method And then in here let 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 result. As simple as that. So we don't really worry about catching these errors and displaying something, since this is a query right so once you've done this go ahead and add that to your trpc 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 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, actually. 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, modules, projects, UI, components, and let's 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 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, padding 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 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 textextrasmall and textmuted 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.now plus milliseconds before next and then add a new prop here i mean a new param in this interval to duration 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 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 guess, class name mlauto. 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 users from dot slash 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 go inside of this one that i already have and once this loads nothing 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 10 stack 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 npxprisma studio so you can see what i'm talking about we have this new model called usage right now we have the fields key points and expire but nothing exists 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 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 instead 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 a wait and let's call consume credits 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 usage dot consume 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 instead 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 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 TRPC error here with the code bad request and a message something went 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 to 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 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 one thing here immediately. Oh, yes, we forgot the ingest API. We forgot to do that. NPX ingest CLI. Whoops, my bad. And let me just refresh this. And you can see that now when you refresh, 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 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 you 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 landing page again. And this time I will refresh and I will have no free 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 implemented usage. Now, you probably noticed a little bit of a weird thing here And that is that 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 something like ingest.send. You 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 this in the background job, you would also need to pass the user ID in the 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 hooks, right? So that's why I decided to do it in the procedure rather than in the background job. Now what we have to do is this to do right here. So let's remove this. Let's add query client dot invalidate queries, trpc 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 and send the message now it should 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 error.data?code is equal to too many requests let's go ahead and do router we don't have router so let's add it const router use router from next navigation Make sure you add this import. And just do router.push forward slash pricing 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 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 am just going to use stupid queries now just to get that error. There we go. So once I run out of credits, I'm redirected to the pricing page. Perfect. Now there is one more place where we need to do this exact 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 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 product 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 trpc usage status query options and then in here if error data code is too many requests do router.push forward slash 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 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 is we have to get inside of here the status. So has and let's do await out. Const 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 pro access 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 pro access, it's going to be pro points. Otherwise, it's going to be three points like that. And here's a quick tip. If you also want to change the duration, which I wouldn't recommend, you know, there's also, if you look at all of these apps, ChatGPT, Claude, Lovable, Bolt, Replit, 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 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 pro access. 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 points in the middle of an existing database record for rate limit, but you cannot do it for expiration, just 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 in 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, let me just check. I think this is the only place here where I have to do 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 test 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. And let's see if it worked. So right now, I think that, there we go. I have to refresh, right? And then it says 96 free credits remaining. So we 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 the existing points we spent during the 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 is we have to change the text this is no longer free credits this is just credits now and we can also remove all of the upgrade buttons we no longer need them. So let's do that. I'm going to go inside of 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. I have has here, so I'm going to change. Let's go ahead and do const is has pro access has plan Pro as 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 view i'm going to add it here i'm going to import use alf from clerk next js 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 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 here, inside of your plans, select the plan you want, and then check the slot. It's free user. This is the one you want. That's the mistake I made when I 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 be it regarding billing. It was that simple to do. I'm super impressed by this. No webhooks, nothing. I mean, 90% of this chapter wasn't even adding billing. It was adding usage, rate limiting, right? So amazing, amazing 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 schema created the util, we created the usage component, and we updated procedures to trigger credit spend. Yeah, one more thing I wanted to show you. If you want to, you can also implement something like usage procedure. And then in the middleware, you could check for the 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 want to after you finish the project Okay So I 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 CodeRabbit 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 non-pro users. Navbar now dynamically changes style based on the scroll 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, a more in-depth walkthrough here. And we have a sequence diagram explaining exactly what happens, right? So when a user tries to submit 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 available, we return with success and we proceed with the creation. And then we simply return the 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 getUsageStatus method, which simply reads the usage and it returns it. Perfect. See, in here we have some 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. In here, it recommends wrapping this instead of try and catch and fallbacks to soon. So this can happen if the dates are incorrect. And yeah, this might be a good idea because it's kind of weird that the entire page fails just because the date renders incorrectly. So we could actually do this in the next chapter. So we ensure some kind of security here so it doesn't ruin 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 back to my main branch here. And I'm going to click on the Synchronize Changes button and OK. And then I will confirm that we just merged that right here, 17 billing. Perfect. Amazing, amazing job. We have finished 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 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 run dev and ingest 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 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, 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 simply updated the page to be a red themed page. If I add a calculator there, it won't understand what I mean, 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 possible. The reason we really, really need this is not for continuous conversation. The more important reason is, A, I can make mistakes. You already saw that sometimes it forgot to add use 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 previous messages. And let's do await step.run get previous messages. It is an asynchronous arrow function like this. And then let's do const formatted messages and give it a type of message, which you can import from ingest 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 const 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 broken. So formatted messages.push type text role. If message role is assistant, it will be lowercase assistant, otherwise lowercase user like this. And the content is going to be message.content. And let me just see what is the problem here. Formatted messages. Oh, it should be an array of message. My apologies. There we go. And then let's go ahead and let's return formatted messages. There we go. So right now, it is going to have context of the entire conversation. 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 ingest 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. 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 to a couple of places. So let's go ahead and find our network execution 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 older messages, even the responses and the user messages 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 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. it knows that add a calculator in the hero segment is the message after build a landing page. And it is a message after created a polished, full responsive, red themed landing page. So let's see if it was able to do this or not. We should see any second now. And it 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 I just want to make sure that I didn't accidentally maybe do the incorrect order of loading the messages here.\ncreated 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 example, when I tried this privately off camera, it worked for me. So you can see that AI is sometimes a bit unpredictable, but I just want to tune it, you know, So 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 messages, right? But you can already see a slight improvement, right? Because right now, when we asked it to create that landing page, it made it 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. 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 can, 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 understands the same thing maybe or not yeah in here it also the last message here is build the 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 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 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, Let's go ahead and do the following. I want you to visit my 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 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 seems to again made it red. I keep thinking that the way we are loading these previous messages maybe isn't perfect. So you're going to have to tweak that a little bit, or 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 we'll 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 better to understand what's going on but 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 into separate 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. You can even explore, you know, ingest documentation about the state. Maybe in there we can find something. 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 result, I want to go ahead and I want to do 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 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 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 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 to be response prompt. And now you're going to define two outputs. The first output here will be fragment title. Await fragment title generator dot 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 dot 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 const generate fragment title. and I will do if fragment title output dot type is not equal first in the array of course the text return fragment 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? Array.isArray, I think. Fragment title output. Because this can be an array, then let's return Fragment title output first in the array.content.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. And you can just add another safety one fragment. Okay, but this is unreachable, so I don't know why exactly. String is not... Okay, yeah. My bad. and now go ahead and copy this, and call this generateResponse, 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 our app. So let's see if that is 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 follow the ingest to see if we 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 just this. But 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 slick responsive 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 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 would be fun to add that as well. And you can definitely write this in a better way. I mean, starting with the fact that we can just do const 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. Const 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 agent output. and in here let's go ahead and see this is a type of message or array so let's try value message array like that and then can i just use value const 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 generateResponse function. And now what you're just going to do is parse, how did I call the function? ParseAgentOutput. 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 ingest kit here there we go then you can remove it from here find someplace you're using it and then there we go import from utils like this and let's just do a sanity check here 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 instead 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 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 of this chapter as always you know your app worked just fine up until this 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 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 to just use a cheaper model here simply because you can even use an entirely new model you know like gemini grok whatever because we are not calling any 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. Amazing. So 18 agent memory. Let's go ahead and 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 user-friendly 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. So yeah, we could add this as well. And I just noticed that we returned the text fragment as placeholder, even though we use this both for the response and for the fragment title. So I should probably add a new value, something like fallback value, something like that. We'll see. Nevertheless, very satisfied with this one. I will research before the next chapter if there's something we can do better when it comes to adding message history. But I think this is as good as we can do right now. Great. So let's go ahead and change this to main and let's synchronize our changes. And we should see our new pull request being merged. Amazing. 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 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 learning how to make our sandboxes last longer. As always, ensure that you're on your main branch and make sure you have synchronized your changes. The last chapter we merged was 18 agent memory. So let's learn how to increase sandbox expiration. Right now, instead of our application, if I visit even this one four minutes ago, okay, this one works. But if you visit anything older than five 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 how you can increase your timeout. So the sandbox lifecycle, it will stay alive for five minutes by default, but you can change that using the 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 can keep it alive up for 24 hours, but on free tier, which we are on, we can keep it alive for one hour. So for example, let's find something in between. Let's use half an hour. I think that's a fair amount. So all you can do is go inside of your functions instead 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 one hour for hobby 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. 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 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, 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. So now we know how to increase our sandbox timeout and I will try this out. I will build a calculator app here and at the end of the chapter, 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 our sandbox expiration I going to leave this for last simply because we are in the middle of generating So let 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 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 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 since we are limiting the take, we need to offset, right? You can also do the offset thing. Actually, it is skipped in Prisma, but honestly, I'm not really sure if we need to do that, but 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 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 error handling in our app currently doesn't exist outside of TRPC 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. Next.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 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. So first, without error boundary. So comment out error boundary. I'm working inside of project ID. Let's go inside of 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 yes my apologies no uh 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 three 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, of course, let's just remove this the RPC 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. Pullback, 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 inside of messages get many and let me just try and do this so throw new TRPC error here, code bad request. So something happened here. And let's go ahead and 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, 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 it's loading messages, it's loading messages, and you already know what will 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 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 and let's say we forgot to wrap this error boundary here. So now, and yes, also go to project view and comment out the error boundary here. Now the global error takes care, right? So that's how the global 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 simply because it is the last line of defense in case something goes wrong. Perfect. So now let's go back instead of our messages procedures here. Let's just remove the throwing of the 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 errors. And it would be very stupid if our entire app here fails just because the date is invalid. So here's 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. 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. So make sure you added useMemo 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 a little 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 unknown or whatever you think is better user experience right and then in here let's return format duration and then inside interval to duration and then start new date and let 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 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 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. 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 Perfect. So this is very good. Let's see what else we have on 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 your E2B dashboard, in here you can see your templates. And you can see that my Vibe Next.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 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 red and you can see that this sandbox are still going strong so we definitely works what we increased the 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 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 conversation history here. And for me, it seems to still be working. If for whatever reason yours stop 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. 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. 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 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 come back. And we are not just doing any kind of polling. We're doing polling using the tan stack query. This means that this refetch interval will dedupe. It will use cache. It will be an extremely optimized interval. And also, I'm not sure if you knew this, but while right now new requests are being made every five seconds, if I change to this page, requests stop 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 two seconds if you want to. This will give you a better experience and it will still be very, very optimized. Now, in case you're wondering, okay, but does Ingest offer any real-time updates? They absolutely do. You can go and select the documentation and read about the real-time. So in here, you can subscribe to a channel and then from the function, you can initialize, you can publish to that channel. And I explored this option, but the problem was I wasn't able to synchronize both my Prisma messages and the ingest subscription messages. It is absolutely possible, 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 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. 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 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 the React error boundary. We added global error page. We added some wrappers here, error boundary. We added the proper sandbox timeout. We properly reversed the messages. So the conversation history is improved. We use the same sandbox timeout in the utils here. We reduced the refetch 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 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 Vercel. This will include the initial 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 Ingest to our Vercel project and redeploy once more. And after that, we are ready to test the app. So let's start with step one, deploy to Vercel. 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 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 Vercel 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 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 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 Vercel. And that is that you should try building locally first so you don't waste time with failed Vercel deployments. And we also forgot about one more important thing. And that is that when we deploy the Vercel, we need to add a post install 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 here. Let me just check. Where do we add it? In 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. And from here, you basically have your 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 linting this folder when it should not be linting this folder. So I'm going to look into how I can prevent the app, the build, from looking at the generated folder right here. And I'll tell you what I find. Okay, I have managed to found 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. 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...\nexactly recommended, but you can see that once you add this, S-Lint 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 npm run build, it is going to fail because it will try to lint our source generated folder. There we go. But I don't really recommend this simply because 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 slint.config.mjs here and we can open ignores and in here we can target asterisk asterisk forward slash generated 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 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, it should work. Let's wait a second. And then 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 AsletConfigMJS. Perfect. Once you have these two, let's go ahead and let's merge them like this. And let's go ahead and add deployment here. We can do this in the main branch. We don't need to branch 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, it is going to 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 deployed. So you can see it still running some final 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 custom domain So what I recommend you do is you click on the 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 shortest one. That is the domain of your 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 Vercel. 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 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 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 ingest to this Vercel project. And here we have the successful redeployment here. So now what I suggest you do is just try and use your 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 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 do... You can see that all of our projects are saved. So our TRPC is working here. Let's try clicking on one of these. and let's try sending a message. So I would add test here. And what should happen here is the following. We can see the message, my message was generated, but Ingest was unable to be fired. That's because we need to connect Ingest 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 So I going to call this 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 apps here. And let's go ahead and just find, oh, sorry, integrations. I think this is what I'm looking for. And let's click on Vercel here to connect. Let's click connect Vercel to ingest and click on add integration here. And I have 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 Vercel dashboard. And now somewhere in here, you have all of your Vercel 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 communicate with your application by default. Let's go ahead and click more here. And basically what we have to do is we have to configure protection bypass. So in here, let's go ahead and I'm never sure how to properly access this. So don't worry. I will tell you the exact steps in the Vercel dashboard. But let me just see. Does this take me to Vercel? It does take me to Vercel here. basically, I think this is where I need to go. Let's go to Vercel. 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, 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 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 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 Ingest should be able to access your Vercel application And let go ahead and do one more thing 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 Vercel. So this is a good sign. And it has found one function, CodeAgent. So I think that this means that it's running successfully. I'm just not 100% sure. I keep thinking that I need to redeploy. So let's see. Let's try it out. If it doesn't work, it means we have to redeploy. So go to your 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, ingest runs. and let's see if a new run will appear and the new run is right here that would mean that we successfully deployed our project and we connected ingest to deployed instance and looks like we didn't need to redeploy at all let's just go ahead and wait to see 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 vibe-ebon.versell.app and if you check the cloud here there we go it seems to be working 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. I'm sure you did as 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 in Jest. You can always, you know, click here and click redeploy and that will just redeploy your latest version. And now in your settings, environment variables, you should now see ingest event keys and signing keys. So that's the trick. That's what made it 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.",
  "transcript_chars": 485318,
  "transcript_filled_at": "2026-06-06T15:50:45.536643+00:00",
  "transcript_filled_by": "tk-bulk-groq-retry-20260606"
}