{
  "video_id": "pRybm9lXW2c",
  "title": "Fullstack Trello Clone: Next.js 14,  Server Actions, React, Prisma, Stripe, Tailwind, MySQL",
  "url": "https://www.youtube.com/watch?v=pRybm9lXW2c",
  "transcript": "Hey there, my name is Antonio and welcome to the newest video on my channel. In this tutorial, you're going to learn how to build an amazing Trello clone, which we are going to call Taskify. And as you can see, I've already prepared some boards for us. So let's go ahead and check out what's inside my board. As you can see, I've prepared a list to do, in progress, and done. And we can easily move them between each other just by dragging and dropping. And as you can see, we also have a reflective notification in the lower right corner right here. Now let's go ahead and add a new card inside of the to-do list. All we have to do is click on add a card or we can visit the options and click add a card from here. And now let's add a new task, optimization for example. and we can click enter or add a card like this and we have a notification for that as well. Perfect. Now let's see how easy it is to drag and drop that inside of another column. Just like that we can do it as many times as we want. Perfect. Now let's go ahead and click on an individual card to see what's inside. As you can see it says the name, the list where it's in, its description as well as its activity. For now, all activity that it says is that I have created this card. So let's go ahead and rename this card. And once I save this, you can see that I have a new activity. They have updated the card and we have a new name right here. The same activity is triggered if I add a description and click save. Perfect. And now let's see how easy it is to copy a card. As you can see, I can select landing page, for example, and let's add a unique description here and let's click save. And now once I click on the copy action right here, inside of that same list on the bottom, we have a new task, landing page copy. And as you can see, the description has been copied as well. But the only activity we have is that we created that card. Perfect, exactly what we want. And we can just as easily delete a card if we no longer want it. But besides this, we can also go ahead and copy the entire list. So let's do that with the done column right here. I'm going to click copy list. And look at this. It is right here. And the tasks have been copied as well. Beautiful. And we can just as easily delete an entire list. Or we can manually create a completely new list. like for example design and let's click add a list and let's go ahead and move it to its appropriate place. Perfect. So now we know everything that we can do inside of this board. We can also go ahead and rename the entire board as well just by pressing the enter and we have a notification for that as well. Now let's go back inside of our dashboard to see what else we have. As you can see on my left side right here, you probably noticed that I have some workspaces. That is because this tutorial is also going to teach you how to create a business-to-business software as a service. So I have created a couple of workspaces and you're going to learn how to do that as well just by pressing on this plus button. Your users are going to be able to create as many organizations or workspaces as they want. So let's see what we can do in an individual workspace. First of all, you can select an individual workspace right here in the sidebar. For example, I can select the boards inside of this organization. And as you can see, I have none of them here. Or you can go inside of the navbar. And from here, you can select the organization that you want. Perfect. So let's go ahead now and let's click on the activity tab right here. As you can see inside of here we have a similar component that we saw inside of my card model. That is because inside of this page we have a list of entire activity inside of this organization so you as an admin can keep track of everything that's going on inside of your project. Besides activity we also have the settings tab. Inside of here, you can manage new invitations and you can also assign roles like admins or members. You can also kick people and you can also manage profile settings like changing the name or uploading an image. Perfect. And last tab we have here is the billing tab. And once I click here, we have a model which says to upgrade to Taskify Pro. And you are going to learn how to build this entire thing. But let's not click on this right away. First, let's see how to create a new board. As you can see, I have an option here to create a new board and it says one remaining. And let's hover over this question mark to see why. As you can see, it says that three workspaces can have up to five open boards. For unlimited boards, upgrade this workspace. So let's fill up these boards right here. Last one is left. And as you can see, we have a beautiful set of random Unsplash images loaded every time you click create a new board so you surprise your users. Perfect. Let's select this one. And as you can see, we also have the name and a link to the author of that image so we oblige by Unsplash API guidelines. Perfect. And let's create this one to be last board and click create. And you can see how fast that was created. Perfect. Now let's go back inside of our dashboard and now it says zero remaining. So let's try and do that now. I'm going to try and create a new board and as you can see I have an error that I have reached my limit of free boards. Please upgrade to create more. So let's go ahead and finally click on this upgrade button right here and we are redirected to the Stripe checkout page. And as you can see, we have a subscription of $20 per month. So I'm going to go ahead and enter some fake Stripe information here. I'm going to enter a fake name and I'm going to click pay and subscribe right here. And after this has been processed, we're going to get redirected back to our organization page. But you can see that now it says that I have unlimited boards. And you can see that right here it says that I'm on a pro plan. So let's try if that is working. I'm gonna go ahead and pick another beautiful image from Unsplash here and call this board premium board. And let's go ahead and click create. And you might be wondering what does this big create button do in the navbar right here? Well that is useful for when we want to create a new board without leaving this screen. And you might be wondering, well, how do we know inside of which organization is this going to be created? Well, we can easily take a look at that inside of our navbar right here. So this is our selected organization, meaning that that's where the board is going to be created. So let's test that out. When I click create, there we go. I am redirected to the new page. Perfect. And what I want to show you next is that inside of this organization, I have unlimited boards. But if I go ahead and pick another organization, you can see that here, I do not. Here it says free and I have five boards remaining. That is because the subscription is per organization only. So this is going to be a true business to business software as a service. And of course, you're going to be able to manage your subscription in case you want to cancel it at any point for your business. Just right here. Perfect. But that's not all. The entire application is going to be fully responsive on all devices, just like this. And lastly, we're also going to have a beautiful landing page. so you can showcase to your users exactly what they're going to get out of your application. And one last thing that I'm very excited about, and that is that this entire application is going to be using Next14 and the new stable server actions. We have a lot to do, so without further ado, let's get started. So let's go ahead and let's set up our project. Here on the left side, I've prepared my Visual Studio Code, which is my editor of choice for this tutorial. And here on the right side, I've prepared my browser. In my case, it's Google Chrome. So let's go ahead and let's open up our terminal. You can, of course, use any terminal you want, but I'm going to use the one inside of Visual Studio Code. In order to access that terminal, all you have to do is go into the lower left corner and click on these two icons right here. And then just go ahead and select terminal like this. Now we're going to run a command which is going to initialize our next 14 project. Let's go ahead and write npx create dash next dash app at latest. And now let's go ahead and give our project a name. In my case, it's going to be Trello-Tutorial like this. And let me just explain what this at latest means. So at latest specifies that we're using the highest or the most up-to-date version of this package right here. So once you've written this, feel free to just press enter. And now let's answer some questions about the setup of our project. For the TypeScript option, we're going to select yes because we're going to be using TypeScript. For the S-Lint option, we're going to select yes as well. We're also going to be working with Tailwind, so select yes for that. Now for the source directory, make sure you select no. Mine is pre-selected. And if you're wondering how to change between the options, you can use the arrow keys on your keyboard. So just make sure you select the no option for source directory. Now, very important for the app router, make sure you select yes, because app router allows us to work with server components and use the new stable server actions, which is going to be something we will be exploring in this tutorial So make sure you select yes for this option Now whether you would like to customize the import alias is completely up to you of course but I would recommend that you follow the exact options that I have so you have the closest setup to mine. So make sure you select no for this option and that's gonna leave this add sign as the default import alias. If you don't know what an import alias is, don't worry. once we start coding, it's going to be pretty clear. Perfect. And now just go ahead, relax, and wait for all of this to install. After the installation has been complete, you're going to see this success message right here. What we have to do next is open up that project inside of our editor. So just for now, I'm going to close my terminal, and I'm going to click on the open right here. And then I'm going to select Trello-Tutorial because that is the name of my project. And just go ahead and click open. And if you get this big prompt, feel free to press yes. Great. And now once you open your project, you should have this kind of project structure. You should most certainly have the app folder. And inside you should have the globals, layout, and page. You should have the node modules, which just means that this has been installed for you. And you should have the public folder. And of course, some configuration files for Tailwind, like this, for TypeScript, for S-Lint, and some gitignore files right here. Before we start this project, I just want to install one more package inside. And let's go inside our terminal again. And I'm going to go ahead and please excuse me, I have to upgrade my terminal. So I'm just going to pause for a second. You probably don't have this, so ignore that. Great, so excuse me about that. And now let's go ahead and write npx shadcn-ui at latest init. Like this. Perfect. And now let's see the options. So would you like to use TypeScript, recommended? it, make sure you select yes, because we selected yes for the initial command which we run to initialize the project. So make sure those two match by just pressing yes. And now it's asking us which style we would like to use. You can of course pick your own style, but again, I highly recommend that you follow exactly what I do and select the default style. And now about the color, I'm going to go ahead and select neutral. This is the least important part. You can select any color you want here. But again, if you want your project to look exactly like mine, just follow along. Perfect. And now it's asking us where is our global.css file. As you can see here, it's placeholder to app slash globals dot css. So we can quickly check that by opening up our sidebar. and let's confirm that we have our app folder globals.css. Looks like that is completely okay. Perfect. So we can just press enter. It's asking us whether we want to use CSS variables. Select yes for that. And now here's the bit of a tricky part. It's asking us where is our tailwind.config, in my case, .js located, right? And it has this option right here. But let's take a look at our setup here. As you can see, we don't have tailwind.config.js. We have tailwind.config.ts. So nothing dangerous will happen if you just press enter on the .js default placeholder, which this provides you with. But you are going to have two unnecessary files. So what you can do in this option, if you have .js here, the same way I do, is you can press the tab on your keyboard and then you can go ahead and edit this to be .ts like this and just press enter. Again, nothing dangerous or scary is gonna happen if you leave it at .js. Your project is still going to work but you're gonna have one unnecessary file. Great. And now we have the question for the import alias for our components. If you left the default alias default as I did in the initial options, you can just press enter here. If you did any modification there, you're going to have to modify this as well. So this is why I recommended that you follow the exact options which I selected. So just feel free to press enter here. And the same thing is for the utils, you can just press enter. And last question is referring to whether we are using the app router. As I've mentioned, app router allows us to use React server components. So yes, we are going to be using them. So just go ahead and press enter for the yes option. And now that's it. All you have to do is confirm this. So you can press enter or just type Y on your keyboard. And let's go ahead now and let's see what we have. So I'm going to go ahead and close everything just to see what new items we have inside. So as you can see, we now have the components folder, which is currently empty, we also have the new lib folder inside. And inside of this lib folder, we have our utils file. And as you can see here, we have some new packages, clsx and tailwind merge. And this function cn, which if you watched my previous tutorial, you already know this is going to come in handy and we're going to use this function a lot. I'm just going to briefly explain what it does, but it is going to make more sense once we start coding. So the cn function is enabling us to safely and properly combine tailwind classes, specifically dynamic tailwind classes. For example, we're going to have a specific class for error state, specific class for success state. And the best way to do that is using these two packages. So the shat cn command And nicely combined those two for us in this very nice util right here. Perfect. You can go ahead and explore a bit more about all the files you have here. You can see that our Tailwind config has now been filled with a bunch of variables here. So all of that came from the ShatCN UI. And if you're wondering what ShatCN UI is, my apologies for not explaining. Because I use ShatCN in all of my tutorials. So sometimes I assume that people know. But still, I think it's important if this is your first time watching. ShedCNUI is a component library which is very compatible with Next14, which we are going to use in this tutorial. Great. So now we are finally ready to go inside of our terminal and run the command npm run dev like this. And as you can see, I have Next.js 14.0.1 running on a localhost 3000. So let's go ahead and refresh my browser here where I have prepared that URL. And in a couple of seconds, you're going to see a landing page similar to this. Perfect. So let's go ahead and quickly clean up this page. So we are ready to do some more work. Let's go inside of the app folder and let's go inside of page.tsx right here. So everything that you see on this page right here is actually written inside of app folder page.tsx. So you can just go ahead and remove everything inside of this return function. So everything inside of this brackets, right? I'm going to go ahead and start with main. I'm gonna go all the way down and I'm holding the shift. I click here and I click remove. Perfect. And now I can just write a div saying hello Trello like this. And let's remove this unused import and clean up this white space at the top. And now if you take a look at my browser here, you can see that I have a text hello Trello. Don't worry if yours is smaller. I just zoomed in so you can see. So now let's go ahead and test out whether we set up our Shatsian correctly, which means that we should have our Tailwind working. So I'm going to go ahead and write class name. I'm just going to zoom in even more so you can see. And inside of this class name, I'm going to write text-sky-500. And that should change the color as it did in my browser into a nice bluish color. Perfect. And if you're wondering how do I have this little color box right here well that is a very cool extension called Tailwind CSS IntelliSense so you can go inside of extensions if you're using Visual Studio code and write Tailwind inside and the very first option is going to be Tailwind CSS IntelliSense so just go ahead and install that refresh your Visual Studio code and then you should be seeing this. And you're also going to get another cool feature, which is if you're not that familiar with Tailwind, very useful. For example, if you have no idea what this class does and you have that extension installed, you can easily hover over a class and you're going to see everything it does. Let's take a look at something different. For example, let's use flex-1 and when I hover, you see exactly what CSS that is. Perfect. So now that we have this working, we are ready to start figuring out how routing works inside of Next 13. Great, great job. So now let's learn how routing works inside of a Next project. So let's go inside of the app folder right here. And what I want you to do is create a new folder. Inside of this folder, give it a name example, like this. And inside of example, create a new file page.tsx, like this. Let's go ahead and let's write const page. Let's return a simple div, which is just going to say example page like this. And don't forget to do export default page at the end. Otherwise, routing is not going to work. Perfect. So let me just clear some things up. This constant page does not have to be named page. It can be named anything you want. Just make sure that you export default the same constant. So it doesn't matter. But in the tutorial you probably going to see me combine the folder name and the well the fact that it is a page So in my tutorial you going to see stuff like this My constants for routes are going to be example page. This is the convention that I'm going to use, which if I accidentally open this file, I immediately know, okay, this is a page. This is a route. This is not some random component. I know exactly what this component does. So I'm just quickly explaining the structure here. Perfect. So now you might be wondering, all right, well, how do I access this route? Well, let's just confirm our folder structure here. So inside of our app folder, we have the example folder, and then we have page.vsx. In the first part of the video, I explained that everything you saw on the main page is inside of this root page.tsx, which is just inside of the app folder. So now that we moved, I'm sorry, created a new page.tsx inside of this example folder, that means that we can visit it by going on localhost 3000 slash example. And there we go. We now have our example page right here. Perfect. And now let's go ahead and let's create another folder. And let's now name it users. So let's imagine this is some kind of dashboard, right? Somewhere where we are going to display our users. And more importantly, how do we create a dynamic route? Meaning that so far you've learned how to create routes which are hard-coded, like slash example. But what if we want to route by a specific user ID? For example, your user clicks on some other user's profile. How do we create that URL? What is the structure? Well, there is a very convenient feature inside of Next, which allows you to do just that. So inside of your user's folder, create another folder. and inside of it give it a name starting inside of square brackets so next recognizes that this is going to be a dynamic part of the url it's not going to be hard-coded like slash example or slash users it's going to be dynamic right and inside give it the name of the variable that you expect. The name doesn't actually matter, but you are going to have to be aware of what is the name because that's how you're going to access it inside of your code. So let's clear this up. Let's see exactly how to do that. Inside of users, I'm going to write square brackets ID like this, and then I'm going to create a new file page.tsx, and let's go ahead and write const ID page. You already know that is my convention. And I'm going to write a div ID page. And don't forget to, of course, do the export default at the end of ID page. Perfect. So let's check what that route is going to be. So that's going to be slash users, and then slash whatever we want, right? Because IDs can be a lot of things. So let's try that out. Inside of my local host, I'm going to write slash users, and then I'm going to write slash 123. And look at this, it's working, even though we have not defined this 123 anywhere inside of our code. So that's what this convention of using square brackets in our folder name allows us to do. So now what's important for you to understand is how to fetch this ID inside of this page.tsx because that can be very useful, right? You're probably gonna want to then go to the database and request the user with that ID, which we have in our URL. What's important for you to understand is that by default, every page inside of the app folder is a server component, meaning that from here you have some special conventions and well probably some different stuff that you're not used to if you're coming from the single page application react world but don't worry i'm going to try my best to explain that here so how do we access that id first thing we have to do is confirm what is the name of the variable in this case it is id we know that by looking at this folder. And then go inside of page.esx right here and go inside of the props here. And let's go ahead and extract the params. Sorry, just the params like this. And let's give it a type. So let's give a type to this props to be params, which is an object, which is going to hold an ID, which is a string like this, like that. And now let's change this to be ID params dot ID. And there we go. Now we have a text which says ID one, two, three. And if I change my URL to four, five, six, for example, the ID changes as well. So if you're wondering where did this params come from, right? Usually, if you're working in a single page application, you're used to passing props to your components. Well, we are going to do a lot of that in this tutorial as well, but server components have some special options like accessing the params and in the future is also going to be search params. You're going to see all of that here, but I just think this is an important concept for you to understand. Perfect. Now that we have this, I want to cover another type of folders here. So you've just learned that pretty much every folder that you create inside of next 13 app, sorry, next 14 app folder is going to become a route. But what if you just want to create a folder, an organizational folder, for example, which is excluded from the URL? Well, you can do that. Let's go ahead and let's create a new folder and let's write normal brackets and let's write for example I don't know let's write test like this and go ahead and try a new file page.tsx inside of that. So I'm gonna go ahead and write const test page. I'm gonna go ahead and I'm going to return a div which says test page and I'm gonna make sure that I do export default test page. So usually we should be able to access slash test, right? Let's see if that is true. So if I go to slash test, I have a 404. If I try and put parentheses, which is not possible in the URL, you can see that they still get a 404. So this folder is completely excluded from the URL. And this is going to be very useful for us. But it's not that these folders should only be used for excluding things out of the routing system when creating folders. They are also very useful for when you want to create reusable layouts for many different routes. For example, let's go ahead and create a new folder inside of here, which is going to be called, I don't know, let's say something like that. And let's go ahead and create a new file inside page.tsx like this. And let's do const something page. And let's return a div something page. And export default something page. We can now access this route. But what is the URL? Well, very simply, it is just slash something like this. Now we have the something page. As you can see, our URL does not have the test folder inside of it. So that's very useful for us. Also, let's remove this page.tsx, which we have inside of the test folder like this. Let's just remove it. Perfect. So make sure you just have the parentheses test folder and then something inside. And now let's go ahead and create another folder called other like this. It doesn't really matter. We're just playing around now and create a new file page.psx inside. And let's do const other page, return a div other page. and let's export default other page like this. So now if you go to slash other, there we go, we have the other page, perfect. But why am I doing this inside of this test folder? Well, of course, I wanted to demonstrate how you can organize things, right? Because sometimes if you have a lot of routes, it's very useful to organize it inside of a folder, but you don't want that to become part of the routing system, right? Well, let me show you one cool thing that you can also do inside. There is a reserved file inside of the next framework, just like page.tsx, but it is called layout.tsx. And let's go ahead and create that file inside of this test folder. So create a new file, layout.tsx, like this. And as you can see, we have an error here, which says that the default export is not a React component. Don't get scared by this error. That is because just like page.tsx, layout.tsx requires a default export, but we have not written anything yet. So let's do that now. I'm gonna write const test layout. So why am I calling it test layout? Well, because it's inside of the test folder, right? So I'm using the same convention as with pages. And let's go ahead and let's return a div inside like this. And let's write layout. And make sure you do export default test layout. Perfect. And what happened now? As you can see, in my URL, I am still on slash other. But the only thing that's rendering is this layout text, which we've written here. And the same thing happens if I try to go to the something folder or route which we created here right So I going to change this to slash something And again it only says layout Well that because we have not finished configuring our layout.vsx. Go back inside and from the props in every layout file you create, you can always destructure children. So let's go ahead and destructure the children and let's immediately give it a type. Children is a type of react.react node. Also in next you don't have to import react right except if you are importing use effect, use state, use memo and stuff like that or some very specific types but for stuff like this you don't have to import react. It's globally available here you can see the namespace. Perfect and instead of writing layout we're going to render children like this. And let's take a look at the browser now. I'm on slash something and it's rendering something page. I go to slash other and it's rendering the other page correctly. Great. So what exactly did we achieve by creating this layout file? Well, let me show you. You can now go ahead and give this div a class name of bg-rose-500, for example. And as you can see, the other page now became red. If I go to slash something, that page is red as well. But if I go ahead and try one of these routes, like example or users, let's see what happens with them. So if we go to slash example, this one is not red. So that's what layouts are so useful for, because they can create, well, layouts for specific routes. And when combined with this organizational folders, which initially we just thought are being used for, you know, ignoring the app router are actually much more useful than it seems. And this is going to come very handy because in our project, we're going to have a landing or a marketing page where we want to use one specific layout with a specific navbar with a footer where we're going to have the privacy policy and everything. And then when we get to the actual application, we're going to want to have a dashboard layout with a sidebar, with a different nightbar. So that's why these organizational routes are so important to understand because we're going to be working a lot with those routes. and in here in this folders like other and something you can continue doing this like we did with users like dynamic ids and more perfect so just one thing that i've noticed right now which we have to fix before we move on to other stuff is that i want to give this div so i'm inside of layout.tsx inside of the test folder and i want to give it a height of full but when i do that, nothing really changes, right? It's still not, obviously, this has a name of h-full, which means 100% height. But for some reason, it's not doing that. So let's fix that by visiting our globals.css file. So go inside of the app folder, globals.css, and just below this tailwind direct dives. Also, don't worry about these warnings. So they're not errors, they are warnings, because this rule at Tailwind is simply not recognized in my editor. There are ways to fix that, but they're not going to be a problem throughout this tutorial. Don't worry. So go ahead and add the following HTML, body, colon, root, and just add height 100% like this. And once you save that file, you can see how now our layout.tsxhfull and bgrows500 has taken on a completely different effect. Perfect. And here's maybe a more realistic example of what we would do here. So I'm going to remove this, right? And instead, I'm going to write a div here. This is a navbar like this. And there we go. This is what these routes are so useful for. I'm just going to add a little HR element here. So it's separates this two. There we go. As you can see, this is how we are going to use organizational routes. Inside of our layout file, this is where we're going to put our navbar. If we have a sidebar, that's exactly where we're going to put our sidebar, right? And then we're going to have some other routes like example, which are not going to have that because we don't need that. So this way, we're going to be able to separate, organize, and reuse our layout components in a nice way. and all of that inside of this big folder structure here. So that's what I wanted you to understand in this part of the tutorial, some basics of routing. Of course, if this still seems complicated or unclear, don't worry because we just did this, but we're gonna delete all of these files and we are slowly gonna repeat exactly what I just did, but with a real world example because we're gonna be building a real world project. So it's, I hope, going to be more clear than if it's not already. And you can, of course, always visit the official Next14 documentation. Just make sure that you are reading the app router version. And there, they're going to even further explain how all of these things work. Perfect. So now we can actually go ahead and remove the test folder, example, and the users folder. Just move them all to trash. we are not going to need them. And you can go back to localhost 3000, where we have Hello Trello in a nice blue color. And one more type of route that I wanna show you is the API route. So in the older versions of Next.js, when there was a pages router, we had to write an API folder. Nowadays, we don't have to do that. In my tutorial, I'm still gonna use the API folder just because I think it's a nice structure to hold my API routes inside of the API folder rather than them being cluttered with my other client routes. But just for this very simple example, let's not do all of that. Let's just simply create a new folder and let's also call it users, for example. And inside of this file, let's go ahead and let's create a route.ts. So this is the same type of convention as with page.tsx or layout.tsx. But since this is an API route, no need for the TSX. It's just TS, right? So let's save this right here. And now let's write a very simple route handler inside. So I'm going to write export asynchronous. Sorry, it can be just export function, get like this. and let's go ahead and let's just write return next response and make sure you import next response from next slash server dot json and I'm gonna write hello Trello like this a very simple object let me just remove this pop-up so that you can see there we go like this perfect and if I try and go inside of my browser to slash users now, you can see that I have a little extension which prettifies my JSON output, but you should see the same thing. Basically an object, maybe not with this pretty colors, right? Again, this is an extension. I'm not sure what's the name. So that's how we create API routes inside of Next.js framework. I just wanted to show you that one last thing. And this is called a route handler. So inside of this single route.ts file, you're going to be able to create functions like post, functions like patch, right? So that's why we don't do default exports in this one, because they can have a lot of them in one. If you try and write export default, it's not going to work. As you can see, the page isn't working. But when I remove, it's working. Perfect. So now we know how to create API routes, how to create organizational routes, how to create layouts, how to create dynamic ID routes, how to access those in server components. And I think we have warmed up enough for us to start implementing our authentication. Great, great job. Just make sure you delete those files and folders we created. So you should just have a clean slate, favicon, globals, layout, and page. Perfect. All right, so now that we've practiced routing, let's go ahead and let's utilize that to create our marketing page. In order to do that, first thing I want to do is create this, sorry, is delete this page.tsx, which is currently rendering a simple text, Hello Trello, which is visible right here on the localhost 3000. So make sure that you are on localhost 3000 and not on any other route. And go ahead and remove page.tsx from your application. And now you're going to get a 404 page. And now we're going to bring it back, but inside of an organizational folder so that we can create a custom layout for our marketing page, which is also going to be our root page. So go ahead and create a new folder inside of the app folder, open up the parentheses and write marketing. And inside, create a new file, page.tsx. And let's go ahead and write const marketing page. Let's return a div marketing page. And don't forget to export default marketing page. And when you save, there we go. We are back at the beginning. This is acting as our root page.tsx. But this time, it's in a nice folder. This means that we, well, first of all, it's better organized right now. We know exactly what this page.tsx is without changing its name because, well, we cannot change its name. It needs to be page.tsx to be accepted into the routing system. But besides that, if you remember from our practice, we can now create a reusable layout, which is going to be reflecting everything inside of this marketing folder. And we're also going to be able to create a custom components folder inside of this marketing folder, which is only going to be available to use inside of this page. Sorry, it's going to be available to use everywhere, of course, but we are kind of, you know, with our structure indicating that it should be used only inside of the marketing page. So just a bit of a practice with structuring your\nproject. Great. So let's go ahead and create the layout page inside of this marketing file. And of course, we have an error. So let's go ahead and let me expand my code here. And let's write const marketing layout. Let's immediately destructure the children. And let's give it a type of children to be react.reactNode. And let's go ahead and simply return a div and render the children inside. And X for default, marketing layout. And there we go. Nothing much has changed. But now let's go ahead and give this div a class name of H, pool. So it takes the full screen height and bgslate 100. So it's a bit darker. And now let's create a main element and wrap our children inside of that. And let's give the main element a class name of pt40 padding bottom of 20 and bgslate 100 as well. And if you're interested in exact pixels, you can hover over these classes to see exactly what they are doing to the code. So why am I moving this from the top and why am I adding the space on the bottom? Well, I'm going to add a comment for now because here we're going to have a navbar and at the bottom we're going to have a footer. You don't have to write this. We're going to come back and do it later. Perfect. So now I want to go back inside of the marketing page and go ahead and well, style it to look like something. Let's go ahead and give this div a class name of flex items center, whoops, justify center and flex call. If you're ever unsure about what classes do, you can always hover to see exactly what CSS they are implying. Right now, what I'm doing is centering this text into the middle like this. And flex call indicates that all the items that I create are not going to be next to each other, but one below another. If it was just flex, then that would mean that it's in a flex row position. But flex call indicates that I wanted to go one below another. Great. And let's go ahead and create another div here. With a class name of flex items center, justify center again, and flex call. So exactly the same thing that we did above. And in here, I want to open a new div, which is going to hold our metal from Lucid React. If you're wondering where do we have this Lucid React installed? Well, that came installed when we run our Shat CMUI and when we chose the default style for our app. If you chose the New York style, you're not going to have Lucid React installed. So if you're getting an error here, you can just go ahead and run npm install Lucid React. No biggie. Perfect. And now we can render this medal right here. And if you save, you're going to get this little medal here in the browser. Now let's go ahead and give this a class name of height six with six and margin rate of two. Perfect. And below that, let's go ahead and write number one task management. Like this. Perfect. And now let's go inside of this empty div and let's give it some proper classes. So class name is going to be margin bottom of 4, plex, items center, border, shadow small, padding 4. So we add some space to it. DG amber 100, text amber 100. Sorry, text amber 700. so it's a bit darker like this, rounded full and uppercase everything like this. And let me just expand this and there we go. Now you can see how we have a nice little badge here in the middle of the screen. Perfect. Now let's go outside of this div which is our badge, right? And let's open up an H1 element, which is going to say Taskify helps team move like this. And let's style this. So it looks like an actual heading. So we're going to give it a text of 3XL. But on medium devices, we're going to give it a text of 6XL. So it's bigger. And now let's write text center like this. so it aligns properly. Let's write text neutral 800. So it's just a tiny bit lighter like that and margin bottom of six. Perfect. Below that, open up a div and write work forward and a dot at the end. And in here, we're gonna write a class name to be text three Excel, the same it was above. And on medium devices, it's gonna be text six Excel So same as above everything. We're also going to go ahead and give it a BG gradient to write like that. From, and now we're going to write a color. I'm not sure how to pronounce this. So Fushia, I'm not sure. All right. So Fushia 600, I'm just going to call it purple. From purple 600 to pink 600, like that. So we have a nice little gradient. Text is going to be white. PX is going to be four. Padding overall is going to be two. Rounded is going to be medium. Padding bottom is going to be four. And W is going to be fit like this. So you can go ahead and expand this. And there we go. You can see how this looks now. And if you think that the font looks weird, don't worry. We're going to change the font later. For now, I just want to create a little structure here. Perfect. So now we have that. Now let's go outside of this div, which is wrapping our task management, our taskify helps team move, and this little gradient box. So not outside of this div, but outside of this one right here. And let's open a new div. And inside, we're going to write collaborate, manage projects, and reach new productivity peaks, like that. And now we can go ahead and just write from high rises to the home office, the theme, sorry, the way your team works is unique. and let's go ahead and add accomplish it all with taskify so basically it doesn't matter just a little promotional text right on our website and give this div a class name of text small on medium text is going to be extra large text is going to be neutral 400 margin top is going to be for max width is going to be extra small but on medium devices max width is going to be to excel let's center the text using text center and mx auto to push it from both sides like this and if you expand you can see how this looks on desktop mode like that and you can see how it looks on mobile mode like this. Perfect. So now that we have this, all that I want to do is import a component called button from ShadCN. So for that, let's head inside of our terminal. I'm going to open a new one here. So here I have my project running and I clicked a little plus icon here. So I have a new instance. And inside, I'm going to write npx ShadCN-UI at latest add button like this. And just wait for this to install the button. And there we go. We can close this terminal. Let me zoom back in. And now we have a new component inside of our components folder. Inside of UI folder, we have button.tsx. So this is what's different with ChatCN in regards to other libraries. Other libraries give you the same type of components, but usually you're not able to see the exact code that is inside of them. But with ChatCN UI, you're able to visit the code and you can modify it however you want. We're not gonna do too much modifying. I'm quite satisfied with what ChatCN offers by default. But if you're interested, you can always change the focus visible. You can change the opacity when it's disabled. You can change the variance of your button, the sizes of your button, whatever you want. But for now, I recommend that you don't play around with it too much until it's time to do so. So now let's use that button by going outside of this div and opening up that button and import it from add slash components UI button like this. Let me show you how that looks. So right here at the top, I've imported button from add slash components UI button and this till add sign is what's called an alias. So what does an alias do? Well, it saves us from doing this, right? Usually we had to go outside manually, right? Using this dot dot slash notation, but with an alias, we can easily do this and it's the same thing. So that's what I was talking about when I was telling you about the option to customize the default import alias and I told you to leave it exactly as it is and by default, that's the at sign. Perfect. So as you can see, I like to separate my imports. So I'm always going to keep my global imports at the top. By global, I mean those things that I installed using NPM. After that, I'm going to keep this aliased imports. And then even lower, I'm going to keep my local relative imports if I ever need to use them. For now, we don't have any. Perfect. And inside of this button, let's go ahead and let's import a link component from next slash link. So make sure you import that. And since it is from npm, I'm going to add it here to the top. And let's go ahead and let's write get Taskify for free. And it's missing an href. So let's for now just write sign dash up like this. Perfect. And now let's style this button by giving a class name of margin top 6. Let give it a size of large And let write as child so it properly works with this link component inside Perfect So here it is our initial landing page If you click here of course you going to get a 404 So now it's time for us to add custom fonts so this looks even better. So what I want you to do now is go inside of my GitHub. You can find the link in the description. And I want you to go inside of the public folder. And in here, you can find a file called font. So go ahead and click on this file and find a way to download that file. Once you've downloaded that file, go ahead and open up the public folder, which you have right here, and drag and drop that downloaded file inside. If you're wondering what font this is, this is the CalSense font or CalOpenSense font. I'm not sure what's the name. And it is from a project called cal.com. It's a competitor to Calendly, if I'm correct. And they've created this amazing font for us to use. You can Google CalFont GitHub to see more information about that. So make sure you have this font inside of your public folder. If you don't, it doesn't matter. So this is just a font, right? This is just styling. No important functionality is being done here. But I want to show you how to import local fonts using Next.js. So in order to do that, we first have to import something called local font from next slash font slash local like this. And then go ahead and write const heading font to be local font like that. and go ahead and give it a source of dot dot slash dot dot slash public fonts, font dot W-O-F-F-2, like that. And now we have our font. Perfect. So now we have to add that font inside of one of these divs right here. So let me just go back to my local host here. And let's see if I did any mistake here. And I did, yes. So inside of my public folder, I just dragged and dropped the font. But actually, when I create a new folder inside called fonts, and then drop that inside, so it's even better organized. And there we go. Now we have no error because our relative import here is correct. Perfect. So now what I want to do is I want to import our util called cn, which we researched at the beginning of this tutorial where we installed chatcn. So with this cn, we can now combine the existing class names, which we have, and then append this initial additional class name, which is going to come from this constant right here, thanks to this local font instance from next font. Perfect. So let's go ahead and let's go inside of this second div right here. And let's just add curly brackets around this class name like this. So when we save, nothing should change. And then what I want to do is at the beginning of this curly class, add the CN library and wrap this entire thing inside the parentheses like this. And again, nothing is going to change. So here is how CN works. In the first parameter of CN, we're going to pass our default classes. And then I'm going to add a comma and I'm going to write the dynamic class, like heading font.classname. And when I save, you can see that we have a beautiful font right here. See how nice this looks? Perfect. So now we know how to import local fonts. And now let me show you how to import fonts from Google Fonts, because you can do that as well. So go ahead and import the specific font you want. In my case, that's going to be poppins. So import poppins from next slash font slash Google. Like this. Perfect. Now we have the poppins font and let's initialize it in a similar way. So I'm going to go ahead and write const text font to be poppins. We have to give it subsets, which is just going to be an option Latin. And we also want to give it weight. And let's go ahead and open our array here and let's write 100, let's write 200. So basically we're going to fill up all the options that give us. 400, then it's 500, 600, 700, 800 and lastly is going to be 900. Perfect and now that we have this we're going to append this font to the rest of our text. So let's go ahead all the way to the bottom here where we have this collaborate manage projects text and do the same thing. So first add the curly brackets around this class name like this, nothing changes. Then add cn and wrap the entire thing inside the brackets. Great. Let's collapse this so the default thing is in the first line and then add a comma and write text font dot class name like this. And when you save, you can see that we have a nicer font for our description as well. Perfect. And we can leave the button as it is. So now it's time for us to create our navbar component, our footer component, and also one reusable component called the logo because we're gonna use it in the navbar, in the footer, and we're also gonna use it in the dashboard of our Taskify project itself. So let's go ahead and create our first reusable component called logo. So in order to do that, first go back inside of my public folder here and find logo.svg right here. This is a logo from the logoipsum.com website where you can find a bunch of free logos. Just make sure not to actually use them for your business. You can use them for your demo projects or some education purposes. So go ahead and download this file and go ahead and drag and drop that straight into the public folder similar to what we did with the font. Just like this. So paste it here and make sure to rename it into logo.svg in case you have this little number as I do. So just make sure to that it's called logo.svg. Perfect. So I'm going to close everything and I'm going to go back here and let's go inside of the components folder. So I'm going to keep the UI folder for all the Shatsian components, which we're going to import. But for our own custom reusable components, I'm going to write that inside of the components folder directly. So right click here and create logo.tsx. Make sure it's outside of the UI folder like that. And in here, let's go ahead and write export const logo like this. And let's go ahead and return a link component from next link. Let's give it an href of just an empty slash. Let's go ahead and give it a div and let's add an image from next slash image and in here the source is going to be slash logo.svg which we just added into our public folder alt is going to be logo height is going to be 30 and width is going to be 30 as well great and now let's go ahead below that and let's add a paragraph here which is gonna have which is just gonna say taskify like this perfect and let's go ahead and give this paragraph a class name of text large text neutral 700 and padding bottom of one great and now let's give this div which is wrapping our image a class name and that one is going to be hover opacity 75 transition items center gap x to hidden by default but on medium devices it's going to be visible so from medium and up meaning on desktop devices we're going to see this logo but on mobile devices it's going to be invisible Great. And now let's go ahead and let's import cn from s slash lib utils. And let's go ahead and let's also import local font from next font local. So we're going to reuse that font which we imported previously. Perfect. And let's go ahead and do that. Adding const, heading font to be local font. Let's write a source to be data slash public slash font voff2. Great. And now let's go ahead and let's wrap this class name inside of curly brackets, right? Let's add our cnutil and let's wrap this entire thing inside of parentheses. Let's add a comma and then we write having font dot class name. And that's it. Our reusable logo component is finished. Perfect. So now let's create our navigation bar. So I'm going to close everything and I'm going to go inside of the app folder and inside of marketing. And in here, we're going to create our components folder. But I'm going to show you a little trick. So there is a third type of folders inside of Next.js, which is a second type of folder which can be used to be outside of the routing system. So we learned that we can use the parenthesis around the folder to ensure that it's not inside of the URL. But as you can see, even then, we can still render some pages and routing inside of it. So there is an actual type of folder which can be used to completely exclude it from the router. And that is called an underscore folder. I mean, it probably has a different name. I'm calling it an underscore folder because it begins with an underscore. So let's add underscore components like this. So even if we added page.tsx inside, it would not be visible anywhere. You will not be able to access it unless you import it into a page. So that's exactly what we're going to do. Inside of the components folder, create a new file navbar.tsx like this. And let go ahead and let write const navbar And let return a div saying navbar like this And make sure you add export const navbar So no export default when we write components. We're going to use export default only in layouts and pages, but not in individual reusable components or just components like this. That's when we're going to use just an export const. Great. And now let's immediately add this inside of our layout.tsx right here. So instead of this comment here, we're going to add that navbar component from .slash underscore components slash navbar. And you can see how I imported this at the top. Perfect. And now you can see a little text here, which says navbar. Perfect. Let's go inside of the navbar and let's finish it up. So I'm going to give this div a class name. of fixed top zero so it's always going to be at the top w is going to be full height is going to be a fixed 14 like that px is going to be four border bottom like this shadow small bg white flex and items center great so you can now definitely see that here at the top and we have a little text here. We have our border and we have some slight shadow. Perfect. Now let's go ahead and open a new div inside with a class name md max w screen to excel mx auto flex items center w full and justify between. So we are doing another flex box but this time we are doing it in a row right? And what does this MD Max W Screen to Excel do? Well, it ensures that on desktop devices, there is a limit to how far we can expand the screen, right? So for example, let's go ahead and write something here. I'm going to write test here. And you can see how now when I expand to a certain point, it stops resizing. Are you noticing that in the navbar, right? So that's what this class name max w screen to excel does. You can make it even shorter. For example, max screen large. And let's see, you can see that now it stops here, it stops resizing at a certain point. But for my case, I think to excel is just fine. So it's enough room for a small for a big desktop monitor. But if it gets larger than that, it stops resizing. Great. So we now have that perfect. And now we can render our logo component and make sure you import that from add slash components logo and destructure it like this. All right, and it seems like we have a little error here. So let's go inside of our logo component. So that's located inside of components UI, sorry, inside of components for logo here and we are missing the fonts folder here. There we go. And now if you expand, there we go. We have a nice little logo here and we can click on it, but right now it's not doing anything, but it's actually redirecting to the page that we are already on. Great. Let's head back inside of our navbar component here. And now below this logo, let's add a new div here with a class name of space x4 so the items between are spaced evenly on the x-axis let's add md block let's add md w auto let's add flex items center justify between and w pull and then let's add our buttons So import button from add slash components UI button and write login. And below that, add a new button, which is going to say get Taskify for free. Like that. Perfect. So you can see on mobile devices, they're going to be each on its own corner. But on a large screen, they're both going to be in this corner right here. Now let's add some styles to this button. So the first one, size is going to be small, variant is going to be outline, and as child, like that. And if we are using as child, we also have to add a link from next slash link. So make sure you add that import. All right. And let's give this link an href of slash sign dash in, like that. Perfect. and then we're gonna reuse that link already here in this other button like that and this one is gonna lead to sign dash up like that and let's go ahead and give this button a size of small and also as child prop perfect so let's see how this looks now there we go we have our logo here we have our two buttons here. When we click on them, they lead to 404 because we don't have authentication set up yet, but we're going to do that in just a moment. And now, a last thing we have to do inside of our marketing page is the footer. So let's go ahead and do that. So we can actually copy the existing navbar component. So I'm going to go ahead and do that. So go ahead and copy the navbar component and paste it inside of the underscore components folder and rename it to footer like this. And let's go ahead and let's slightly modify it. So instead of top zero, first, let's actually rename it. So footer like this. And then instead of top zero, it's going to be bottom zero like that. We don't need to have the height of 14 like this. We also don't need a border bottom. Instead, we need the border top. We don't need any shadow, so we can remove that. We can also remove the BG white like that, and flex item sender are not needed either, and instead, let's write BG slate 100, like that. Perfect. We can leave this exactly as it is. The only thing we're going to change here is these two buttons. So you can remove the as child, and you can remove the variant and you can remove the link as well. So do the same thing here. Remove the as child and remove the link here. And change this one to say privacy policy and change the other one to say terms of service like that. And let's give both of them a variant of ghost like that. Perfect. We can remove this import now. And now that we have our footer ready, remember, if you get stuck at any point in a specific component, you can always head directly into my GitHub from where you downloaded my fonts and the images. And you can just take a look at the component and see exactly what's going on. You can copy it if you're stuck or you can use it as a guide. And now let's go back inside of our layout here and remove this comment which says footer and let's actually import the footer component from dot slash underscore components footer. Great, and let's go ahead and try this out now. There we go. You can see we have a footer here, but something seems a little bit off. I have a feeling like our footer is too small, right? It seems too cluttered. So let's actually go ahead and give it a fixed height. You know, I removed the height because I thought we're not going to need it. Actually, instead of height, we can just do this. Instead of PX, just give it a P4 like this inside of the footer component. So we had a PX here and just change it to be P4. And let's see that now. There we go. That looks much cleaner now. Perfect. So we have finished our landing or marketing page. So that is now our root page. Perfect. We are now ready to implement our authentication. And by doing that, we're going to implement our middleware, which is then going to take a look if we are logged in or not. And if we try to visit this marketing page while we are logged in, it's going to immediately redirect us to the dashboard and pre-select our organization. Great. Amazing, amazing job. Now that we have our landing page finished, it is time for us to implement authentication. But just before we go on about doing that, I want to go ahead and edit my favicon and the title of my tab, right? I want this to look like a proper application. So first thing that I'm going to do is I'm going to create a new folder called config, which is going to be outside of all of these other folders. So just click on a random file like gitignore and then click on a new folder like this and create the config. And inside, create a file site.ds, just like that. And then export const site config. Let's give the name of our app to be Taskify. So from here, you can also change this if you're planning on running this as your own software as a service later. And let's give it a description to be collaborate, manage projects, and reach new productivity peaks. Basically just some marketing stuff. Great. And now that we have this, let's go ahead and let's visit one file which we have not looked that yet, which is this global layout, right? So we created our own layouts in the example and here, but we haven't really explored this root layout. So this root layout needs to exist. And as you can see, it has a name root layout, and it renders the HTML, the body and our children. So this is important, you can change, you can remove this file. But what we can of course modify. So let's go ahead and modify this metadata a bit. I'm going to change this from a string to an object. So it gives me more options. Default, this is going to use site config, which you can import from add slash config slash site as I did here So it going to be site config dot name like that And let also give it a template And template is going to be well open a backticks like this and write a percentage S then write a pipe, and then render the site config dot name. So if you're wondering what this is, so as it says, this is a template. So this is the title. This is our default title if we are on the page, like on the marketing page. If you take a look at my browser tab, you can see that it says Taskify now, right? But later when we open up a specific board or a specific organization, we're going to change it to be something like my organization type Taskify, like that. So that's how it's going to look like. So instead of ask manually doing that every time, we can just use this template syntax right here. So this is going to be replaced with whatever we let the title be when we create some additional layouts. Great. And now let's change the description to be site config dot description like that. And last thing we have to do is add our logo because right now by default, you can see that it has this Vercel logo here. So first thing that we have to do is go ahead inside of our app folder and remove the favicon.ico like that and then go back inside of the layout file and add icons open up an array open an object and write a url to be slash logo.svg and hwrap to be slash logo.svg and save that and take a look at the browser again And there we go. We have a matching logo in our tab. Perfect. So now we are ready to head on creating our authentication. So in order to set up our authentication, we're going to use Clerk. So head to clerk.com or use the link in the description. And go ahead and find in the sign up or sign in button and enter the dashboard. and as you can see I have a bunch of projects here from my previous tutorials from my side projects and stuff like that and just go ahead and find a button which says add an application or perhaps that's the default screen for you so let me just zoom out so you can see this is something what it's going to look like so let me zoom in even further there we go so let's give our application a name so this is going to be Trello-Tutorial you can of course change this to Taskify or whatever is the name of your product. And you can see exactly how that's going to look in the components. So here it says Trello tutorial. And then you can go ahead and play around with whatever you want to add to for your users to sign in. So there's a bunch of options, as you can see, all the way to MetaMask. So Web3 login, if that's what you want. So I'm going to turn off the email address. And instead, I'm just going to use Google like that. And you can, of course, enable something else. Now let's go ahead and click create application like that. And now let's go ahead and set up our project. So as you can see, first thing we have to do is copy and paste these environment keys, you should have these two keys right here. So go ahead and just click copy. And let's go ahead and do something before we add this to our environment file. And that is go inside of dot git ignore. So this is very important, make sure you go inside of dot git ignore. and just below this .environment.local add .environment itself like that. So this .environment.local is of course, well, for that file but I also want to do it for this file. Now you might be wondering, well, why am I not using .environment.local? Well, that's because we're later going to add Prisma and Prisma uses the .environment file. Now, yes, you can change the location of the environment keys where Prisma looks for them But honestly, it's just a teeny bit unnecessarily complicated for this tutorial. We can just use the environment file and teach you how to add that to your gitignore. Now, why is it important that this is inside of your gitignore? Well, that way it will not be committed to GitHub, meaning that it will not exist anywhere except locally on your computer, making it much more secure. And it is even more dangerous if you make your GitHub repository public. That way, there's a lot of bots and scrapers which can look for public environment keys and steal those keys. And that itself might not sound dangerous because in this tutorial, we're all going to use free tiers, right? We're not going to pay for anything. But imagine if you actually use this for your business and have some environment keys which are on paid services. Well, they can create a lot of damage then. So that's why it's important that you add .environment inside your gitignore file and make sure you save that file. And then go ahead and create the .environment file. And inside, paste those two keys, which we just copied. So you can get them from here. Perfect. And make sure you have selected the Next.js option in the quick start and click on this purple button, Continue in Docs. Perfect. And if you watched my previous tutorials, you're probably going to notice that Clark has updated their documentation. It looks pretty nice. So in here, make sure that you select the app router option like this. Actually, you don't have to. I thought that this was a switch between the two. Sorry. This is the example quick start repos if you want to look at that. Looks like we don't have to click on that. Okay. So first thing we have to do is npm install this package. So let's go ahead inside of our terminal. And you can actually shut down the app for now. It doesn't matter if you're running it here. So npm install clerk next js. Perfect. And while that's installing, Let's see what the next steps are. So we have to set the environment keys and we already did that. And next thing we have to do is add the clerk provider inside of our layout. So let's go ahead and do that. But I'm not going to do this inside of my main layout. I'm actually going to do this only in the layout that I expect to be protected without. So let me show you what I'm thinking of. So this has installed. Great. Let's leave this as it is. Make sure it's installed. It's important. Let me close everything here. Instead of the app folder, I'm going to create a new folder called platform. Also, instead of brackets, inside of brackets here. And then inside of here, I'm going to create a new layout.tsx. And this one is going to be a platform layout. Remember, we can extract the children immediately. and let's also give it a type react react node and let's return clerk provider from add clerk next js so this package which we just installed so just make sure that is installed perfect and inside we can just render the children so in my previous tutorials I always added the clerk provider inside of my root layout right here but that way you are gonna to make your website, I'm not sure what's the proper way, you're either going to lose on static rendering or something like that, I'm not sure, but they're working on improving that, perhaps they've even fixed that, I'm not sure, but you know, let's try out this new method of doing it, it's no biggie, and you know, it's a normal practice, so we are wrapping the layout where our actual platform, where the user needs to be logged in, is going to be, because in marketing, we're not going to be accessing any logged in states or something like that. We're going to let the middleware take care of that. So create a platform folder and create layout.dsx and make sure you add export default platform layout. Perfect. So now that we have that, let's see what are our next steps here. So we have to create a middleware. So let's go ahead and copy this. So this is step four, require authentication to access your app. And let's go ahead and you have to create it in the root of your application. So create a new file, middleware.ts, like that, and just paste this code inside. Great. Let's see what is next. Now let me zoom this in for you so you can see. All right, so we just added the middleware. and before we add this button what I want to do is I want to go ahead and create custom sign-in and sign-up pages usually this was all in this page but they decided to move it perhaps they feel that's a better way to do it so if you're confused you know if you're watching my previous videos you probably expect this to be here but it's not now it's in this next steps right here so create custom sign-up and sign-in pages so click right here and let's see what we have to create. So we have to create a signup page and then we have to create this catch all folder with also the signup inside and then a page.tsx and then this code. All right, not too complicated. Let's copy this right here. And I'm going to do that in a very specific way. So usually, again, I did this in the root of my application or the out folder or something like that. But this time I'm going to do it a bit differently. So make sure you save the middleware file, go inside of the app folder, go inside of the platform, and in here, create another organizational folder called clerk. So this is where we're going to hold everything regarding clerk. That's going to be our authentication and our organization settings, our organization creating, selection of organization and stuff like that. So go ahead and create the sign in folder like this. And inside of it, go ahead and create a new folder, which is going to have this syntax. So double square brackets, dot dot dot assign dash in and inside create a new file page dot dsx and paste this inside and as you can see this uses the sign up so i made a mistake first i should have created sign up but very easy to fix i mean it doesn't really matter but just import sign in and render sign in just like that and this very same way we're going to do the other page so go ahead and create a new folder sign dash up and go ahead and create a new folder again double square brackets sign dash up just make sure this is exactly like this and page.tsx perfect and then you can just paste this up and replace this too with sign up perfect now that we have that we have to add some other environment variables. So let me just go back here. Okay, we did this, we did this, and now we have to update our environment variables so the middleware knows where to redirect us. So let's go ahead and copy these files, or you don't have to copy them, you can just look exactly what I'm going to paste now.\nSo go back inside of your environment file and just after the clerk secret key, add the public clerk sign in, sign up, after sign in and after sign up like this. Perfect. So as you can see, we have defined that my sign in URL is slash sign in. My sign up URL is slash sign up. How do we know that these are the values? Well, that is because that's how we named our folders. As you can see, I have the platform folder. I have the clerk folder, and then I have sign in and sign up. So if I want it to be register and login, I have to change this folder to login. I have to change this one to be dot dot dot login. And then I have to change it right here to be login as well. And it's still going to work if you need that for any reason. And remember, this is not part of the URL. This is not part of the URL as well. meaning that this route is just localhost 3000 slash sign in. So this is why I mentioned that it's important to understand the concept of organizational routes. Perfect. So now that we have that, I believe that we are ready to try this out. Let's go ahead and do that. So I'm going to go ahead inside of my terminal and I'm going to run npm run dev. Make sure you have the environment files. Make sure you have the middleware and all of those pages. and let's see if we did this correctly. So I'm gonna refresh this page and nothing should change on this page. And as you can see, all right, so something changed. I'm gonna explain in a second. All right, so let's immediately go back inside of our middleware and allow the landing page to be visited by non-logged-in users. Because right now the middleware detected that we are not logged in and it redirected me to localhost 3000 slash assign in. And you can see this little component, which we are going to style just a bit. So let's go ahead inside of the middleware. Let's find that. There we go. And inside of this middleware, go inside of this auth middleware, inside of the object, add public routes and just add slash to be the public route. And that's it. Just save that file. And let's go ahead back inside of localhost 3000. And there we go. As you can see, now we are logged out and we can still visit our landing page. Exactly what we wanted. But now when we click on Get Taskify Free, it leads us to slash sign up. So when I click here, there we go. I can now log in. When I click on log in, same thing, but this one leads to slash sign in and this one leads to sign up. Perfect. So now we have to just style this screen a little bit so it's not like this in the corner, but instead I want it centered. So it's very easy to style that thanks to the organizational folders and the fact that we know how to use the layouts. So all we have to do is find the app folder, we have to find the platform, and then we have to find clerk. And if you have any idea, you can already start doing this on your own. So how would you center every route inside of clerk? What did we say about layout and organizational components? Sorry, yes, organizational folders. So if you have any idea how to do that, I advise you to pause the video and try and do it yourself. If not, you can just follow along. No problem. So what we're going to do is we're going to create a layout inside of the clerk folder like this. And then we're going to write const clerk layout. We're immediately going to destructure the children. And we're going to return a div, which renders those children. And now let's just go ahead and give those children a type. So children are a type of React, React node. All right. And let's give this div a class name of H full, plex, items center, and justify center. Perfect. So if you got anywhere near to the, you probably didn't guess the exact class names, right? If this is your first time guessing this, but if you managed to figure out that you need to create a layout and render the children, you were on the right path. Good job. Perfect. If not, no biggies, you're going to get it. And now let's just export default clerk layout. Great. And as you can see, now we have a much nicer centered screen. So whenever I click, it's like this. Perfect. So one more thing that I want to show you is another component called user button. So I'm going to go ahead and create a new page inside of my platform right here, which is just going to be called protected like that. And inside, create a new file page.dsx. So we're going to delete this later. I just want to demonstrate how it looks when we are logged in versus when we are not. So let's go ahead and write const protected page. Return a div protected page. Perfect. And let's export default protected page. Like that. Great. So I'm going to go ahead and log in with Google. And after you log in, you're redirected back to this page. Later in the tutorial, this is not going to work exactly like that. If we are logged in, we're only going to be able to access the dashboard, right? We are not going to be seeing this landing page all the time. What I want you to try now, make sure you're logged in, is go to localhost3000 slash protected. So this route which we just created. And as you can see right here, it says protected page like this. Perfect. And now I want to show you a couple of cool stuff that you can do. So by default, every page is a server component. That means that we can try and access some of the information about our logged in user using the clerk server utils. So let's write const user to be current user from clerk next JS like this. Import current user from clerk next JS. And that returns a promise as you can see. So it means that we need to wrap this inside of an asynchronous function and evade this result. And then if I hover over the user, it says that it is a type of user. So let's write user and let's then do user dot. And you can try any of these email addresses, first name. I'm going to check the first name. So question mark dot first name. And there we go. You can see that it says user is Antonio because I'm logged in. Perfect. You can also try another hook called out. So it's just out from clerk next JS. And from here, you can extract user ID. So this is very useful. You're going to see it later. So user ID is just user ID like this. And there we go. You can see the ID rendered out right here. Perfect. This is going to be very useful for us when we want to protect our API calls and our server actions to ensure that the user is logged in. We're not going to check for this one, but we're going to use this to quickly check, okay, we're logged in. Perfect. So now you know how to do this in server components. But what if this was a client component? Let's try that out. So I'm going to remove all of these things. I'm going to remove this. I'm going to remove these hooks. And let's just leave this like this for now. And let's mark this as use client. So now this is a client components. Basically, it's the component you're used to when working with React. And now we can no longer use those utils which I just tried. Instead, we have, as you can see, I have this auto-completion from GitHub Copilot to give me useAuth. So let's try that. So const useAuth from clerk next.js. You can see how it has a prefix use, meaning that it is a hook. When something is a hook, you almost immediately know that you need to use that inside of use client component. You cannot use that inside of a server component. And let's go ahead and destructure user ID from use out. And now let's get our user from use user like this. And let's get our user and let's just hover over to see. There we go. Use user return. And let's just check. Maybe we have to destructure the user? Yes, we have to destructure the user. Great. And as you can see, this can stay exactly the same. And as you can see, it is still working exactly what we expect. Perfect. And now that we have that, we can go ahead and remove this. We can remove these two hooks and we can remove everything inside. And instead, let's render a clerk, sorry, user button from clerk next JS from the same package, right? And let's go ahead and just close this. All right. And now, as you can see, we have a user button here. And from here, we can manage our account or we can log out. We can do a lot of things. So let's click manage account. And from here, you can see you can change your password, you can change connected accounts, you can go ahead inside of your profile and change your image if you want to, basically a bunch of different things. And you can, of course, also sign out. So let's click sign out here. And right now you can see that I'm redirected to this weird page. Check out the URL, right? It's not localhost. So just go back to localhost. And what I want you to do is go ahead. Oops, let's go back inside of that protected page. I want you to add a little prop here to the user button. after sign out URL lead us back to the landing page. So after we sign out, we wanna go back to the landing page. So let's try this again. Make sure you are logged out. Landing page is accessible. If I try to go on slash protected, I'm redirected to login exactly what we wanted. Now I'm gonna go ahead and login. And now I am allowed to go to slash protected. As you can see, I'm right here. Perfect. And let's try one more time. When I click sign out, there we go. I am back on the landing page. So I just wanted to show you how that works. Perfect. And now let's go ahead and remove this protected folder. We are not going to need it anymore. I just wanted to demonstrate how to, that our authentication is actually working. Perfect. Great, great job so far. And now we're going to work on creating our organizations, inviting users, admins, all of that stuff. Great, great job. Great. So now that we have authentication set up, we are ready to start exploring how organizations work. And thankfully, Clerk does all of that for us. So let's go ahead, head back inside of Clerk So right now you should have this kind of screen You can see I have my user here And let go ahead and click on organizations right here Let me just zoom in so you can see So right here on the organizations and inside, you can see that organizations have not been enabled for this application. So go ahead and click enable organizations. And that's going to redirect you to organization settings here and just enable them like this. Great, perfect. So make sure organizations are enabled. And there we go. Now we have organizations here. Great. So you can now close clerk, no need to do anything further. And what I want to create now is a protected route, which we're going to use to select an organization or to create a new one. So let's go inside of the app folder. Let's go inside of the platform, inside of clerk right here, and create a new folder called select-org. like that. And inside, create another folder with double square brackets and the catch all route inside, select dash org again, and then add a page.tsx. So make sure it's inside of this clerk folder. So this select org route is also going to share the same layout where we centered our children. So we don't have to worry about that. Perfect. And inside of here, all I want to do is import organization list from clerknext.js and export default function create organization page like that and just return organization list. Perfect. And now let's go ahead and log in. I'm just going to go ahead and do that. And now that I'm logged in, I'm going to go and manually go to slash select dash org like this. And there we go. As you can see from here, I can either select my personal account or I can click create an organization. From here, I can upload a specific image, but I don't have to. And I can go ahead and give this, you know, a name, for example, Foo Incorporated. And I can click create organization. And from here, I can add email addresses to invite some other members and I can immediately assign their roles to be members or admins. For now, I'm going to skip that. Perfect. And there we go. Now we have my organization right here and I can click here and now I have selected my organization, but nothing seems to be happening here. And that's because we need to add some additional options to this component right here. So the first option is that we remove this personal account. So this is going to be primarily a business-to-business software. So let's go ahead and write hide personal inside of this component. And now when you refresh a select org, as you can see, we no longer have that personal option. Great. And now what I want to do is add after select organization URL. Let's go ahead and redirect to, let me just extract this, sorry, expand this. So we want to redirect to organization slash ID like this. So this is going to be recognized inside of this prop that this is a slug, right? This is a dynamic part of a URL. And below that, we're going to copy and paste this and change this to after create organization URL. So this one is after select and this one is after create. Perfect. So let's go ahead and try that out. I'm just going to hit refresh here. Make sure you do that as well. Let's click here and we should be redirected to a 404 page. But check a look at the URL. It says organization slash and the organization ID in the URL. Perfect. So that is exactly what we needed. And now we're just going to go ahead and quickly create those pages. So we have to go back inside of our platform organizational route. So let me close everything here. Go inside of the app folder platform right here. And let's go ahead and create another organizational folder besides clerk, which is going to be our dashboard like this. And inside, go ahead and create a new folder called organization like that. And then another folder, which is going to be organization ID. Great. And then we can create a page.dsx inside. And let's write const organization ID page. Return a div saying organization page like this. And export the default organization ID page. Perfect. And as you can see, I no longer have that error now because my URL matches. Slash organization and then the organization ID. So if you're having any problems, go ahead and confirm that you have a folder called organization and a folder called organization ID. It doesn't matter if you put it inside of this organizational route, because remember, this is omitted from the URL. So this dashboard is not inside of the URL. Platform is not inside of the URL as well, right? So none of this make the URL. But only here we actually start with the URL, because this has no parentheses around it. And this, if you remember from the first part of this tutorial, is a dynamic part of the URL. And if it's still not working and this is correct, then go ahead again into the platform, but this time into clerk and then go inside of select org right here in this page and confirm that you didn't misspell something here. Perhaps there's an S here instead of Z or something like that. Great. And now you're actually going to see how useful these organizational routes are once again, because as you can see here in my clerk folder, I share a specific layout where I just center all of these items. But here in dashboard, that's not happening. So that's why we created a specific organizational route, but at this time, just for the dashboard. So for now, what I want to do is I actually wanna show you how to fetch some organization information. So you can use the auth, which we already played around with from clerknext.js. And from here, besides the user ID, you can also extract the org ID like this. So let's go ahead and write organization and org ID like this. Perfect. As you can see, now we have the organization and the org ID is right here. And you can see that it matches what is in the URL. Perfect. And now let's go ahead and let me show you another component which we have. So I'm gonna remove this for now. and let's add the organization switcher component. Import that from clerknext.js and let me just close it. So it's a self-closing tag. And you can see how that looks. So similar to like that user button component. As you can see right here, I can switch. I can manage my organization from here. I can check out my users, a bunch of stuff like that. Perfect. And in here, I can still switch to personal account, But also from here, you can add that prop, hide personal. And after you refresh, it's not going to be here. And from here, you can also trigger a creation of organization if it is faster for your users. So very, very convenient component. Perfect. So what I want to do now is I actually just want to bring this back to say organization page. We don't need this hooks. We don't need this like that. and just save it like this, because now we're going to create a specific layout only for this dashboard component. Sorry, for this dashboard organizational route. And we're going to start and create a little navbar and add all of those components there. So let's go ahead inside of the dashboard organizational folder and create a new file layout.tsx. So let me just collapse this organization folder just to show you that this layout file is inside of dashboard, not inside of organization. That's important because we're going to have some more routes here, which are going to be inside of the dashboard organizational folder. So go inside of this layout and add const dashboard layout. And you already know that we can immediately extract children from this. Sorry, let's give them a type. So children are a type of react.reactNode. and let's go ahead and return a div, and let's render the children, and we also have the export default dashboard layout, and let's just not misspell that, all right, and there we go, no more errors, perfect, and now what I want to do is I want to give this div a class name of height full and then I want to render a navbar component from here but once I save this I'm going to get an error because the navbar component does not exist. So inside of this dashboard right here go ahead and create a new folder underscore components and inside of here create a new file navbar.tsx like that perfect let's export const navbar and let's return a div saying navbar like this and then we can immediately go back inside of our layout and we can fix this undefined error so make sure you don't import the navbar from the marketing page right that's the different one we're gonna import this closest to us dot slash underscore components navbar. And once you save, we have a nice little text navbar here. Perfect. All right. And now let's go ahead and let's go inside of this navbar and let's go ahead and slightly style it, right? So I'm going to give this div a class name of fixed z50 top zero w dash full height of 14 like that. Border bottom, shadow small, bg is going to be white and flex and items center like that. Perfect. And now let's go ahead and open a new div here with the class name of flex items dash center and gap x4. And just above this div, I'm going to add a little comment mobile sidebar. So this is just for me. You didn't have to write this, but just for me to remember that we need to handle responsivity at some point but we going to do that later First we have to implement the desktop sidebar right And inside of here go ahead and open a div with a class name hidden MDFlex. So this is only going to be visible on desktop, not on mobile. And that's going to be our logo component, which we created and store in S slash components logo like this. Perfect. So now if I expand from here, there we go. you can see our nice little logo here. Perfect. And let's also go ahead and give this div which holds our navbar a px4. And let's also maybe change this from div to nav, right, to be more semantically correct. There we go. All right. And now outside of this div right here which holds our logo, just go ahead and import a button from components UI button as I did right here. And let's just go ahead and write create like this. Perfect. And let's give this one a size of small and let's give it a class name of rounded small, hidden, md block, h auto, py 1.5 and px 2. So let me just go ahead and add a little space here so you can see exactly py is 1.5 and px is 2, like this. And there we go, you can see our little button here now. Perfect. And I just want to go ahead and add another button here, which is going to have a different size, sorry, a different look. So class name is going to be rounded, small, block, and md hidden. and inside of here, render a plus from Lucid React, like that. So this is only going to be visible on mobile devices, and give this plus a class name of H-4 and W-4, like that, and since this is from NPM, I'm going to move that import to the top. Perfect. So this is how it looks on mobile. We just have a little plus button, but on desktop, we have a big create button, and now that I'm looking at it, it kind of feels like we could have done this in a better way. Instead of making this hidden and MD block on the button, we can actually do it. Oh yeah, but then we have to wrap this into a span. I don't know. Let's leave it like this for now. I think it's fine. Yeah, perhaps we could have improved reusing the same component, but I don't think it's a big deal. You can play around with it if you want. Great. All right, so now that we have that, what I want to add outside of this div, so just here in this empty space before we close the is a new div with a class name of ml-auto flex items-center and gap x2. And inside of here, we're going to render our organizational switcher, sorry, organization switcher from clerk next js so make sure you import organization switcher from clerk next js like that and let's go ahead and give it some props so first hide personal we already know that after create organization url we're gonna go to slash organization id after leave organization url We're going to go to slash select dash org like that. After what's left after select organization URL, that's also going to go to slash organization slash ID like that. And now I'm just going to modify the appearance a bit. So I'm going to write appearance elements. I'm going to go inside of the root box and I'm going to add the display to be flex. oops so display is going to be flex justify content is going to be center and align items is going to be center as well perfect great so we just improved uh this little image here usually without this styling it just looks a tiny bit off in comparison to all the other all the other elements which we are going to have. Great. And just below this organization switcher, we're also going to add the user button from clerk slash next JS. So import this user button from clerk slash next JS right here and go ahead and give it an after a sign out URL to be a slash and appearance is gonna be elements, avatar box. And inside of here, give it a height of 30 and a width of 30 as well. Perfect. So now we have all of those elements inside of our navbar here. Perfect. But we're not exactly finished now because I can still go to localhost 3000 and as you can see, I can visit my landing page, but I don't want that to happen, right? In fact, I always want to be on one specific organization, right? Even if I haven't selected any organization, in that case, I want to get redirected to that slash select org route which we created, right? So let's go ahead and modify our middleware to do exactly that. So we're going to improve our middleware a bit. Usually we pretty much never attach the middleware in my tutorials, but this is a really cool feature that the clerk has, these organizations. So we get to play around with it a bit. So let's go ahead and write after out. This is a function which will give us an out and a request. And let's go ahead and open it. And first thing that I want to check if we are authenticated and we're going to do that using out that user ID. So if we have the user ID, it means we are authenticated. And then we're going to check if we are on the public route. So if out is public route, so this means if we are logged in and if we are visiting the landing page, in that case, I want to redirect to either my organization ID or to select org. So let's write the initial path, which we want to redirect the user is going to be select org, right? If they are logged in and they attempt to visit the landing page, we're just going to push them back to select an organization. But if we have the user organization ID, in that case, the path is going to become, make sure you write this inside of back text, slash organization slash individual out dot organization ID, right? So we are going to move them to there. And now we just have to create a function to do that. So const org selection is going to be new URL. We get the current path, which we have, and we combine it with request.url. And then we do return next response, which we can import from next slash server. So just make sure you add this little import. So return next response dot redirect org selection. Like that. Perfect. And now outside of this big if clause, which we've just written, we have to handle some other regular cases which we might have. So if we are not authenticated, and if we are not on a public route, we have to explicitly tell what to do. And that's going to be very simple. where it's going to return redirect to sign in from clerk slash next js so just make sure you import redirect to sign in from clerk next js and we're simply going to pass in the url from where they came from so once they log in like obviously this user which is not logged in try to access a private route. Perhaps it was bookmarked or perhaps they had a link, right? So if they attempt to do that and we tell them that they need to log in first, it would be nice of us to give them a return back URL. So once they log in, we're just going to give them the current URL they just tried to access. And last one we have to check is if we are logged in. So if ALF has the user ID and if we don't have auth org ID and if request.nexturl.pathname is not slash select dash org. Let me see if I can expand my screen just a bit more so you can see it in one line. Basically, let me show you. So this is the if clause. If we have the user ID and we don't have a specified organization, meaning we never selected any organization. And if we are not on slash select organization, basically, if the user is trying to do anything, even if they're logged in, but they don't have an organization, we have to force them to create an organization or select one first, except if they are on that page where we do the organization selection. So just make sure you don't have any typos here. Otherwise, you might create some infinite loops here. So const org selection is new URL slash select dash org and request dot URL and return next response dot redirect org selection like that. And that is it. That is our middleware. So make sure you save this. If you're having any issues, you can always copied directly from my GitHub. And now let's try this out. So I'm going to go ahead and attempt to go to localhost 3000. And as you can see, I'm immediately redirected back to my organization. Perfect. But what about if I create a completely new account? What happens then? So let me just go ahead and log out. All right, so I just logged out. And let's just confirm that I cannot go anywhere. So I'm going to try the organization. There we go. That is not working. And now let me just go back to localhost 3000. So I'm allowed to go on the landing page. And now I'm going to log in, but with a completely different account that has never selected any organization. Let's see what happens then There we go As you can see I redirected directly to slash select dash org right and let try and clean let attempt to go to landing page now As you can see we cannot So if the user is logged in but we have not created an organization we need to make the user create their own, right? So create the organization, skip, and there we go. We are back inside of here. Perfect. Amazing, amazing job. Just one tiny thing that I want to create is I want to create a special variant for our button here. So it's kind of a bluish color rather than this one. For that, we have to visit our button component in chat CN, sorry, in the UI folder. So components UI, this is the chat CN button, right? So make sure you are here. And then inside of this variance, we have default, destructive, outline, secondary, ghost, and link. And we're going to add our own. So we're going to add primary and that's going to be bg-sky-700 like this, text-primary-foreground, hover bg-sky-700 slash 90 like this. So we just added this one line and then we can go inside of our app folder, inside of the platform, dashboard, components, navbar right here and we can give both of these buttons a new variant which is going to be as you can see now we even have the auto complete for it primary and if I save there we go you can see how now it has a nice bluish color and currently it doesn't do anything but later it's going to open a little form in a type of popover. Perfect so you have started working on organizations your middleware is working and you have completely completed the ALF. And now it's time for us to start working on the sidebar here and actually rendering some information about the user's current organization. Great, great job. All right, so now that we have our navbar for this organization page right here, it's time for us to create a layout for our organization route, which is going to push the content below the navbar and also render a little sidebar here so we can choose between each organization that we have. So first thing that I want to do is go back inside of the app folder, platform, inside of our dashboard, and here in the organization folder, create a new file, layout.tsx. So just make sure that it's not inside of organization ID. We're going to have a separate layout for that as well. And as you can see, we have this error because we're not exporting anything from layout. So let's write const organization layout from here. We already know that we can extract the children. So let's do that and let's assign a proper type to them. And then we can just go ahead and render main, render children inside and export default organization layout. Great. As you can see now, nothing should change, but the error should go away. Now let's give this main element a class name of padding top 20. And as you can see now, this content has been pushed below our navbar here. You probably forgot that we even had that. It is inside of this organization ID page right here. We have a text organization page. but inside of this layout where we rendered the navbar, that has been hidden. So now when we created this layout inside of the organization folder, we brought it back here. Besides this, let's also give it a medium device where it's going to be PT24, so even further down. And PX is going to be 4 and max width is going to be 6XL. So if you forgot what that is, well, that's the limit to how far something will go in resizing, right? So let's go ahead and give it a 2XL. So if we are on a large monitor, then max resizing is going to be WscreenXL and MXAuto to make sure that it doesn't overflow. There we go. You can see how now I cannot resize past a certain point. Great. And now let's go ahead and wrap this children inside of a div. And let's give this div a class name of plex and gap x7. And then in here, we're going to open a new div with a class name of w-64 shrink-0 hidden and md block. So this is going to be a wrapper where we are going to render the sidebar. So for now, I'm going to leave a comment for that. So this is defining how wide that sidebar is going to be. And shrink zero is saying that when we collapse our screen, so not on mobile, right? On mobile, as you can see, it's hidden. It becomes visible on medium devices. So this shrink zero is only actually going to relate to this screens, right? So you can see how this organization page content is shrinking, right? When I collapse my screen. What we want to ensure with shrink zero is that this sidebar content never changes. It's fixed with which we decided right here. Great. And now before we start and build this entire sidebar thing, I want you to do the following. So I want you to go ahead and create a new organization. Let's call this, I don't know, another organization. Let's click create and we're not gonna invite anyone. And I want you to take a look at something. So right now, if I go ahead and switch from the organization here, you can see that my URL changes as well, right? So if I choose another, you can see that I have the new URL right here. The organization has changed. But what if I copy the URL, then change the organization and then paste it here? What happens? I should technically be on this organization because that's the URL, right? But I am not. So we're going to create a little component, which is going to actively take a look at our URL and then programmatically change the active organization. So let's go ahead and create that. And we're going to do that by creating a new layout inside of the organization ID folder. So leave this like this for now. And then inside of organization ID, create a layout.tsx. And let's go ahead and name this const organization ID layout. You already know that we can extract the children. Let's give them a type of react, react node. And in here, let's just, we can actually return a fragment, which is going to render the children. and export default organization ID layout. Just save that and you should have no errors and nothing should change. And now what we're gonna do is we're gonna create a component called organization control, but we're gonna spell it as org control. So it's shorter. Great. And now let's go ahead and create this. So inside of organization ID here, create a new folder underscore components. and inside create a new file org-control.tsx. Let's mark this as use client. Let's go ahead and import use effect from React. Let's go ahead and import use params from next slash navigation and let's go ahead and import use organization list from clerk next.js and then export const org control. it's gonna have nothing in its props and in here we're gonna return null because this is purely gonna be used to do some things in the use effect so first let's get the params from use params then let's go ahead and let's the structure set active from use organization list and you already guessed it we can use this to change the organization programmatically so what we're going to do is we're going to open up the use effect call. Whoops. In the dependency array, we're going to pass in the set active and params dot organization ID. So it's only going to change, it's only going to be called once this changes. And let's go ahead and write if we don't have set active, we can just break the function. And then set active. And let's say organization is going to be params.organization.organizationID as string. There we go. And now let's go back inside of this layout where we attempted to use that, but we don't have the import. And let's just add this import. So a few things that I want you to confirm. Make sure inside of the org control component, you did not misspell params.organizationId. As you can see, we don't have any types here. So we just spelled it out, right? We hope that we spelled it out correctly. We never defined which are the types of this params. So make sure you didn't misspell this and make sure that it matches this folder variable right here. Otherwise, you're gonna have some issues. So now let's test out that thing that I was talking about. So I'm gonna choose this test organization. I'm gonna copy the URL. I'm going to switch to another. And as you can see now, another is selected. But if I change my URL manually and press enter, there we go. It switched to test. You can see for a second it was another. And then our org control component activated and it switched to test. Perfect. So that's what I wanted us to do before we go on to creating the sidebar. Because the sidebar is also going to just push to the URL. So instead of us calling the set active every single time we do route push, we can create a component like this, which is actively taking a look at the parameters inside of our URL. And if it changes, it will change the organization for us. Great job. So first, I want us to head into the terminal and install a couple of packages, which we're going to need to create this sidebar component. Let's go ahead inside of the terminal. I'm just going to shut down the app and I'm going to write mpm install use hooks dash tf.\nThis is going to be a compilation of useful hooks for us to use, which are type safe. And then we're going to go ahead and call npx chatcn-ui at latest add skeleton. So we're going to make sure that we have the skeleton component. And we're also going to add in the accordion. So accordion like this. Make sure you have this too installed. Perfect. And then you can go ahead and run npm run dev like that. Perfect. Now let's head back inside of our organization layout. So right here where I wrote the comment sidebar. And in here now I'm going to render the actual sidebar. So if I save, I'm going to get an error here. Just make sure you refresh your local host every time you shut down the app and rerun it again. So there we go. Now you can see that I have an error. So let's go ahead inside of the dashboard components right here where we have the navbar. So why here? Why not create components inside of this folder if that's where we use it? Well, because we're gonna reuse it in the navbar as well in a different component called mobile sidebar. You can see that I have a comment here for mobile sidebar, but we're gonna get onto that later. So for now, find the dashboard folder, find the underscore components where you have the navbar and in here create sidebar TSX. Let's go ahead and mark this as use client and let's export const sidebar and just return a div saying sidebar. Great. And now we can head back into this organization layout where we have an error for importing sidebar and we can safely now add it not from Lucid React but from dot dot slash underscore components sidebar. Great. If you save, you should now no longer have an error and when you expand, you should have a little text which says sidebar here, which is only visible on desktop devices. So if you collapse like this, it's not going to be visible. All right, so now let's go ahead and let's start developing this. So first, I want to create an interface sidebar props, which is going to hold the storage key. And I'm going to explain why we're going to need the storage key once we actually use the accordion component. So let's go ahead and extract that storage key. So just assign the props, sidebar props here. And let's extract the storage key. All right, so storage key is going to be used inside of our accordion. That's because the accordion can be expanded or collapsed. And it can get pretty annoying the fact that accordion collapses itself after a re-render, right? So if we change a specific active organization, all the sidebar items are going to get collapsed. So that's why we're going to have this storage key and we're going to use local storage to keep track of what was opened and what wasn't. So let's go ahead and add all the necessary imports we're going to need to create this component. So we're going to need some link component from next slash link. We're going to need plus icon from Lucid React. We're going to need use local storage from use hooks TS, which we just installed. and we're gonna need use organization and use organization list from clerk next JS. And then let's go ahead and let's import the button from add slash components UI button. Let's go ahead and let's import the separator. If we have it, we don't have separator. So I forgot about that. Let's head back inside of our terminal. I'm just gonna open a new one here and write npx chatcn-ui at latest add separator. Whoops, let me just delete all of this and write it myself, separator, like that. So just make sure you have this component installed. Great, make sure you have your project running. And now let's try again, import separator. Make sure you don't import it from Radix. Make sure you always import from add slash components. Whenever Radix is offered in your imports, you did something wrong, right? So make sure you never do that. Let's go ahead and let's import skeleton from components UI skeleton and let's import our accordion. Again, you can see I have imports from radix and I have imports from add slash components. So always make sure you're using the components, not radix, because you're not gonna easily notice what's wrong, right? There are not gonna be any errors in your code. you're just going to notice that your component is unstyled and it doesn't work as it should. And 99% of the case is because you imported something from Radix. Great, perfect. So now we have this and let's go ahead now and let's add all the hooks we're going to need and all the other stuff. So first, let's create a state which is going to be connected to our local storage, which is going to keep track of which accordion is expanded and which one is not. So const expanded, set expanded, and this useLocalStorage works very similar to useState. So you can see that we have the expanded, setExpanded, and now we're going to add useLocalStorage here like that. And let's pass in the storage key, and let's pass in the default object, which is an empty object. And now let's go ahead and just give a default value to the storage key in case we don't pass it to the sidebar. So in this case, I'm going to use the letter T to indicate that this is our Trello project. T sidebar state. So why am I even using this as a prop? Why not just hard code that here? Well, as I said, we're going to reuse the sidebar in our drawer for mobile devices. And it can get pretty inconsistent if you use the same storage key for both. So we're going to have something like the mobile sidebar state once we create our mobile sidebar. So for now, just pass in this. And now what I want to do is give this local storage a defined type of what to expect inside. So that's going to be an object. So record with string and any like that. Perfect. So now that we have that, let's go ahead and let's get the current active organization. So const organization is going to be an alias. We're going to alias it to active organization. So if you don't know, when working with objects, when you destructure things, by default, if I just did, whoops, so if I just did this, then we access that organization, right? If I want to, I can render the organization like this, right? But you can also do this, like active organization or whatever you want. You can rename it to whatever you want. And then you would use active organization in your components. So that's what the alias does. And let's get the active organization from use organization. And while we are here, let's also extract the is loaded prop. So is loaded. And we're going to remap that to is loaded organization, but org for short. And now below this constant, let's also add another hook from the organization list. So const, and we're going to go ahead and extract user memberships is loaded, which we are going to remap to is loaded organization list. And we're going to get that from use organization list. Perfect. And let me just de-indent this. All right. And now inside of this organization list, I'm going to write user memberships infinite true. So if you're wondering, how do I know that we have to write this infinite true? Very simply, this is from the clerk next JS documentation, right? So this isn't something anyone would expect you to know, right? You simply have to take a look at their organization hooks documentation. And from there, you can see exactly why we need it and what it does. Basically, it's kind of a pagination thing. All right, so now that we have this, let's create a constant called default accordion value. So const default accordion value is going to return an array of string, and we're going to call object.keys over the current expanded constant. So this one right here. So object keys expanded, and we're going to call reduce. we're going to get the accumulator, which is going to be a type of array of strings. And we're also going to have a key, which is going to be a string itself. And then we're going to open an object. And if expanded, if the current key is expanded, in that case, we're going to push that to the array. So key like that. And let's go ahead and return the accumulator. And let's give it a default value of an empty array like that. Perfect. Now that we have the default accordion value, let's go ahead and let's add a function which is going to actually add something to the expanded list when we click on it. So const on expand is going to take in the ID of the organization, which is a string. And all we're going to do is set expanded, get the current value, open up an immediate object, spread the current value, and then just add that ID. And we're going to make it the opposite of what it currently is. So expanded ID. So if it was true, it's going to be false. If it was false, it's going to be true. Great. And now let's go ahead and let's create our loading state. So if we have not loaded organization, or if we have not loaded organization list, or if user memberships dot is loading, sorry, this is not, this one we write without this exclamation point like that. So if either of those is loading, in that case, let's go ahead and let's return. For now, we can just write a skeleton. It doesn't matter. We imported it. Later, we're going to actually style it to look something, to look like something. Great. So let's just go ahead and refresh to see if we are seeing anything great. You can see how for a second, nothing is rendered because we have a little skeleton. But I mean, it's not really visible yet, but don't worry, we're going to go ahead and style it later. All I want to ensure is that for a second our sidebar is loading. Great. And now let go ahead and let actually style this div here So first off I going to wrap this entire thing inside of a fragment And then I want to go inside of the div and give it a class name of font medium, text extra small, flex items center and margin bottom of one. Then I'm going to write a span here, which is going to say workspaces. and the class name pl-4. And then let's add our button component, which we imported. And inside, add a link, which we imported as well. Let's give this link an href of slash select dash org. And inside, we're going to render our plus icon from Lucid React. All of the things basically, which we imported here. Great. All right. And let's give this plus icon a class name of H-4 and W-4. And then let's give this button an as child prop, a type of button, a size of icon, a variant of ghost, and a class name of ML auto. So it is pushed to the right. Great. And now let's go ahead and see if we can already take a look at it. we can. There we go. So now we have our workspaces text. And when I click here, I'm redirected to create a new organization or select one. Perfect. So now let's go ahead and actually render our organizations here. So go outside of this div, which renders this workspace and a button. So right at the end of our fragment. And in here, go ahead and add the accordion component, which we already imported right here. Again, make sure you don't actually accidentally use the radix one. And let's go ahead and give it a type of multiple. So multiple accordions can be active at the same time. Let's give it the default value of default accordion value. So yes, I kind of forgot to explain what we're actually doing here. So the values, the way we keep values in here in this expanded is not exactly compatible to the way default values are expected inside of the accordion component. So that's why we create this little constant here, default accordion value to reduce over that expanded object, which we have, you can see it's an object, right? And this object is going to look like something like, let me just write here, something like, I don't know, basically my organization ID, true or false. So this is what that object looks like, right? So basically, what we're doing here is we're iterating over that entire object and we're creating an array of active IDs so we are turning them from that object let me write a comment here so we are turning it from this object which was one two three true into an array which just going going to hold one two three so that's what this default accordion value does and the reason again why we need that is because that is what this accordion component expects in the default value. You can see when I hover, it expects a string of an array of strings or undefined completely. Great. And let's give it a class name of space Y2. Perfect. And then inside, let's go ahead and let's do user memberships.data.map and let's immediately destructure the individual organization from here and let's go ahead and for now let's add a paragraph organization.id like that and give it a key of organization.id and let's see if we can now take a look there we go you can see because we have two organizations, we are rendering two organizations ID here. Perfect. So now we're going to create a component called nav item, which is actually going to render that organization in a nicer way with a little image and some additional actions. So that component is going to be called nav item. So let's go ahead and return this paragraph. So we remove that paragraph and instead render the nav item component, which we don't have yet, but we're going to have in a moment. Let's give it a key of organization.id. Let's give it an isActive prop to be if active organization.id is equal to current organization in this array id. Let's go ahead and give it isExpanded prop to be if we have this organization id inside of the expanded object, so organization.id. and let's also pass in the entire organization, organization and let's add on expand to be on expand. So we should have all of those props here. Great and now let's go inside of this underscore components folder where we have navbar and the sidebar itself and create a nav-item.tsx. Perfect. So let's mark this as use client as well. And let's go ahead and export const nav item and return a div nav item. And then we can go back inside of the sidebar and we can import the nav item from dot slash nav item. And I'm going to separate this like that. Perfect. Let's head back inside of the nav item and let's go ahead and give it some props. So interface nav item props is going to have an is expanded, which is a Boolean, is active, which is a Boolean as well. It's going to have organization, which for now, let's say any. We're going to create a type for it in a moment. Let's also add on expand, which is a function which accepts an ID, which is a string and returns a void. And now let's go ahead and create a type organization. What's important for us is that it has a ID, a slug, an image URL, and a name, which is a string. And now let's use this organization instead of this any type. And let's also export type organization so we can use it elsewhere. Great. So now let's go ahead and assign those props. So nav item props. and let's extract is expanded, is active, and organization, and on expand. Great. And now head back inside of the sidebar where we render the nav item. And as you can see, we have a little error here. So let's just fix that by adding as organization and import organization from dot slash nav item. Let me show you that right here. So from nav item, I'm importing the component itself and the organization type, which we just made exportable like this. There we go. And now you should no longer have any errors here. Perfect. So let's go ahead and let's style this. So instead of a div, this is actually gonna be an accordion item from add slash components UI accordion. So let me show you, make sure you have this imported. Again, not from Radix. Give it a value of organization.id. give it a class name of border none and then let's go ahead and add accordion trigger from components accordion let me just collapse this one's great so accordion trigger and inside let's go ahead and give this unknown click which is an arrow function which goes on expand and passes in the organization ID. Let's also go ahead and give it a class name. And this is going to be dynamic. So make sure you open curly brackets and import the CN library. So I imported CN library right here. I'm just going to move it to the top. All right. So this CN library is going to have some default classes, which are going to be flex, items center, gap x2, padding of 1.5, text-neutral-700, rounded, MD, hover, bg-neutral-500 slash 10, transition, text-start, no-underline, so those classes, and we're also going to add an additional hover, no-underline as well. Great. And then go ahead and add a comma and now we're going to write some dynamic classes. So if this current accordion which represents an organization is active and if it's not expanded in that case we're going to render an indicator for the user to know that it is active. So bg sky 500 slash 10 and text sky 700 great and then inside of this accordion trigger let's go ahead and let's create a div let's give it a class name of flex items center and gap x2 let's add a new with a class name of W7 and H7 and relative. And inside, we're going to render an image component from next slash image. So let me show you where I imported next slash image and I'm just moving it to the top here. So let's give this image a fill property. Let's give it a source of organization dot image URL. Let's give it an alt of organization and let's give it a class name of rounded small and object cover. And I think that already we should be seeing something. And as you can see, I have a little error here. So this happens when you add an image to the next image component, but you don't modify the config as you can see in the error right here. So what you have to do is find what it says for the host name. It's probably going to be exactly the same for you as it is for me. But, you know, just in case something changes in the future, find this image clerk.com. And now let's head inside of next config to allow next.js to use images from that source. So let's go ahead inside of next.config.js here. And let write images remote patterns And let open an object So protocol is going to be HTTPS and host name is going to be image like that Great And now what I recommend you do is just restart your application. So I'm going to do that npm run dev and then I'm just going to refresh my local host and while that is refreshing I'm going to go back inside of my nav item component where I just rendered the image and let's see there we go you can see how nice this looks now we have a little image and you can see how because this organization is our selected organization it has a bluish color right until we expand it then it doesn't have a bluish color so that's defined right here in this dynamic is active and it's not expanded. And if you're wondering why does it lose a color? Why did I make it like that? Well, it's going to make sense in a second when you see the actual content, which is going to be inside of what's expanded. Perfect. So let's just try and switch to another organization. And there we go. You can see now the top one is bluish. Perfect. Exactly what we want. But besides rendering the image, we're also going to render the actual organization name. So outside the div, which is wrapping an image, add a span, which is going to render the organization name, and give this a class name, which is going to be font-medium, and text small. Great, let's see that. There we go. You can see how nice this looks now. Perfect. So what I want to do now is create the actual accordion content. But in order to do that, I first want to create an actual routes array for that. So let's go ahead and first let's import all the icons which we're going to need from Lucid React for that. So go ahead and import from Lucid React the following icons. Activity, we're going to need the credit card we're going to need layout and settings so those are the icons which we are going to use for our content and now let's go inside of the nav item before we return this and in here let's go ahead and let's write const routes to be an array of objects and each object is going to have a label. First one is going to be boards with an icon of layout. And you're going to have a class name of h4, h-4, w-4 and mr of two. And it's going to have an href and open back text for this one. And also let's add a comma here so we don't have that error. And the href is going to go to slash organization slash specific organization, whoops, ID. So basically the default page. And then let's go ahead and just copy these three items here. The second objects label is going to be activity. And the icon we're going to use is activity. And it's going to go to organization, organization ID slash activity. Great. And let's go ahead and copy and paste this. The third one is going to be settings is going to use the icon settings. And it's going to go to slash settings. So this part pretty much always stays the same. Let's copy this again. And this one is going to say billing. The icon we're going to use is credit card. And it's going to go to slash billing. Great. And while we are here, let's also go ahead and import our router and our path name here. So const router from use router from next navigation. So let me just show you that quickly. There we go. Use router from next slash navigation. Make sure you add that. Also, just a quick tip. Sometimes if you use like this auto imports, it can happen that it imports router from next slash router. And you can see that there's no error here, right? But you can no longer use this if you're using the app router. You have to use next slash navigation. So whenever you're using use router, if you have some weird errors and nothing is read in the code, just make sure that your useRouter is coming from next slash navigation. And besides useRouter, let's immediately import usePathName. And now let's go ahead and add pathName. UsePathName. Great. And now let's go ahead and let's add our const onClick to accept an href, which is a string, and router.push that very same href. Great. And now we can finally go inside of, outside of accordion trigger and render the accordion content. Again, from add slash components UI accordion. So I added it right here. Make sure you have it as well. And let's go ahead and give this accordion content a class name of padding top one, text dash neutral dash 700. like that. And then let's go ahead and render routes.map, get the individual route, and go ahead and render a button component from add slash components UI button. Let me just show you how I imported that right here. All right, and let's go ahead and give this button a key of route.label, or maybe href would be better. A size is going to be smaller, onclick is going to be an arrow function which calls the onclick and passes in the route href and class name is going to be dynamic so the default ones are going to be w full on normal justify start pl 10 and margin bottom of one and then we're going to have if path name is equal to route.href then we're going to make it bluish. So bg sky 500 slash 10 and text sky 700. Great. And let's give it a variant of ghost. And now finally inside of this button, we can just render route dot icon. So route dot icon, don't accidentally do router, right? I wrote that a couple of times. So route dot label. Great and let's test this out now There we go And as you can see when I expand Now it makes sense right This is why when I expand this loses the color Because this one is active right Trust me if both of them are blue it just looks weird So this looks much better Perfect And here's the cool thing When I refresh you can see that because we use the use local storage This accordion stays open so the user doesn't have to always manually expand that especially if they're gonna have a bunch of workspaces and perhaps organized exactly the ones they want to see open. And if you try and click on this you're gonna get a 404 which is good because it's working. So just check in the URL and you can see we go to slash settings here. Check the billing. You can see we go to slash billing and if you click on the boards itself nothing's going to change but if you click on the boards inside of another organization you can see that it switches to that organization. So take a look at my nav bar. Now it says test here and this is blue color. But when I click on boards here, now this is blue color and this has changed to that workspace. Great, so you finished this little sidebar here. So now what I want to create is a way to open that sidebar if we are on mobile because right now there's no way, well, first of all, there's no space to render that sidebar. Technically, we could like render it on the top and then display the content on the bottom. But I really don't like that solution. So what we're going to do is we're going to head back inside of our app folder, platform, dashboard components here in the navbar. You can see I left a little to do here to create a mobile sidebar. So if you don't have this comment to help you find that, first of all, go inside of this navbar component and find this logo component. and outside of these two divs. So just the first line inside of your nav element. That's where we're going to render the mobile sidebar, which we're going to create now. First thing I want to create is a hook, which is going to control the state of that mobile sidebar. So whether it's open, whether it's closed, and also we're going to use it so that when we click on a specific board in the sidebar, the whole drawer or the sidebar, whatever you want to call it, closes. Because in my previous tutorials, some people told me that when I create this mobile sidebars, it's not exactly the best user experience because it kind of stays open after you change a route. So that's why this time I made an attempt to improve that. Of course, there are many ways you can do that. But, you know, in the specific way we're doing it, I kind of feel like we have the most options by using a state to control that. There are, of course, like, what's the name, proper components to do that from ShadCN, but it simply kind of feels like too much work in spaghetti code to do it. I don't know. You can, of course, try it yourself. So this is what we're going to do. We're going to go inside of the terminal. I'm just going to close it and I'm going to write npm install to start. So make sure you have that installed. and we're also going to need a sheet component. So let's go ahead and let's add npx chat cn ui latest add sheet like this. Make sure you have that package and this and then you can npm run dev again. Great. So let's go ahead and let's create a new folder called hooks. and inside of this folder create a new file use-mobile-sidebar.ts like that and let's import create from to stand. So the package which we just installed. Let's create a type for this. So mobile sidebar store is going to have a property is open which is a boolean. It's going to have a function on open, which is a very simple function. And the same goes for on close. And then export const use mobile sidebar is going to be create. And let's give it a type of mobile sidebar store. Let extract the set and then immediately return an object like that So make sure you wrap it inside of additional parentheses right If you never seen this syntax let me just briefly explain If you do this this opens a function So you would have to do return and then an object, right? But if you want to skip that, you can just open parentheses and then an object. And this tells JavaScript, you know, okay, so this is immediately returning an object. And let's add is open to be false. Let's add on open to be a function, which calls the set, which we extracted from the props here. And it's going to set is open to be true. And we need on close. And then our errors are going to go away. And we need is open to be false here. Great. So we have that. And now let's go back inside of our navbar components, app platform dashboard components navbar. And in here, I'm removing this comment again. So the first item inside of your nav element, that's where it should be. Let's add the mobile sidebar. And if we save, of course, we're going to get a little error as soon as my local host refreshes. So let's go inside of the underscore components. So where all of our new items are and let's add mobile sidebar TSX. Let's mark it as use client and export const mobile sidebar. And let's return a div saying mobile sidebar. And now we can go back inside of the navbar and we can now safely import mobile sidebar. Great. So let's go back inside of the mobile sidebar now. And first thing I want to do is I want to add that hook. So first we're going to get extract on open, which is going to be use a mobile sidebar. We're going to get the state and we directly going to add state is open, sorry, on open. We're going to copy this two more times. The second one is going to be on close. And the last one is going to be is open. There we go. So we have the three states here. Perfect. And now let's go ahead and let's add the path name. Use path name from next slash navigation. And let's also add is mounted and set is mounted to be controlled from use state. So this is going to help us to prevent hydration errors, especially when working with susten states and components like models or sheet components. So let me just align my imports a bit. So these are the global ones. Great. All right. So now let's finish doing this is mounted trick here. So I'm going to call use effect. And I'm going to set is mounted to be true. And then I'm going to write if it's not mounted, just return no. So why am I doing this? So in Next.js, even if you mark something as use client, that doesn't exactly mean that it's not going to be server side rendered, right? There is one, I mean, you can correct me if I'm wrong, but this is how I understood it and why these hydration errors even happen when using use client components. So as far as I understand, each component, even if it is in use client, is going to be server side rendered, at least the first iteration of it. Right. So what happens when you're using these models and sheets and this type of components is that on server side, it has a specific state like closed. Right. And then on the client side, it's suddenly opened and that creates a hydration error. right and it just doesn't look nice and it's very confusing to you why is it happening and you know how to fix it so how does using this use state is mounted and this use effect actually help well one thing that you can do to guarantee that a specific component is only going to be rendered on the client so never on the server not even server side rendered is by using use effect because use effect will not run in that server side rendering iteration. So basically, what we're telling this component is, if this is mounted has not changed to true, meaning that if we have never reached this initial use effect, we don't render anything, because obviously, this is still running on the server, right? But then when it reaches this use effect, it changes it to true. And then we skip this and just render our component. So I hope this kind of cleared it up. You can also Google a very good blog post called the perils of hydration. And inside of there, you can see like an in-depth explanation of what I just said with probably, you know, much better way of explaining than I just did. Great. So I hope that kind of cleared it up. All right. And now let's go ahead and let's add a new use effect here, which is going to be called every time a path name changes. And let's also add on close here so we can call it properly. And very simply, we're just going to call on close. Basically, what this means is that whenever our URL changes, mobile sidebar will close. So we're going to use that, that when we click on a specific item in a sidebar, the router is going to push, the URL is going to change and our sidebar is going to be closed. So with this way, we don't have to manually call on close every single time. Great. So now that we have all of those things, let's go ahead and change this div into a fragment. Let's remove this text and let's render a button component from components UI button. And inside, I want to render the menu icon from Lucid React. So make sure you have that imported. Let's give this menu a class name of H4NW4. Give this button an on click to be on open. Let's give this class name a block and MD hidden. So this is only visible on mobile devices, right? When it reaches a medium screen, it's immediately hidden because on medium screens, we have that big sidebar here on the side. So no need for this additional one. and let's go ahead and give it a variant of ghost and the size small great and then below this button let's open up a sheet component from components UI right here so again make sure you don't accidentally import it from Radix and while we are here let's also add the content great so let's go ahead now and let's render the content and inside all I want to render is the sidebar from dot slash sidebar so we're going to reuse that entire component and let's go ahead and pass some props and some styles before we try it out so the sidebar needs to have a different storage key so t sidebar mobile state like that just so it differs from the desktop state. And let's give this sheet content a side of left and the class name of padding2 and padding top of 10. And this sheet is going to have an open of open, sorry, is open. And on open change is going to be on close. Just like that. So let's try it out. So make sure you are on mobile mode, click here and there we go. You can see how when I click here, it changes the organization and it closes. Beautiful, beautiful job. Great. So just one quick thing I want to do here. These two buttons seem just a bit too close to one another. So I'm going to go ahead and give this to this one margin right to there we go that that looks just a tiny bit better perfect so you finished the entire thing you can see you can do anything on mobile as you can do it on desktop as well great great job so next thing we're going to do is we're going to create the settings page for each of our organization so before we go ahead and create the settings page there's one thing that we haven't quite finished. And that's the loading of this sidebar. You can see how now we just have a blank space. I completely forgot that we didn't finish creating our skeleton here. So I want you to go inside of the app folder, platform, dashboard components. And in here, we have our sidebar component. And if I remember correctly, here we have this loading state, where we just added a lonely skeleton here. So when I'm making skeleton, what I try to do is I try to replicate the actual content which is appearing here. So what I'm going to do actually is I'm going to attempt to recreate this entire structure but inside of the skeleton. So I'm going to go ahead and create this first part which is actually just rendering the workspace and a plus icon, right? So this workspaces and a button. We know how that looks, right? So it has this space here and then this thing on the side. So let's go ahead and do that. I'm going to go ahead here and I'm going to give this div a class name of flex item center gap x4. Actually, we don't need gap x4. In fact, we can just move them away from each other as far as possible using justify between. And let's add a margin bottom of two. And then let's give this skeleton a class name of height 10 and width of 50%. So that's representing our text saying workspaces. And then below that, I'm going to add another skeleton with the class name of age 10 and with a width of 10 representing the button. So let's see if that improved this. When I refresh, there we go. I think this looks nice. You can see how we have a little loading box for the text and a little loading box in the same size of this button. Perfect. So now I want to create the same skeletons for this one. And I want to show you a neat trick that you can do by going inside of the component which you need and then using it to render a skeleton. So I want you to go inside of the nav item component like this. And then let's go all the way to the bottom and let's add a skeleton for it. So nav item dot skeleton is a function skeleton nav.\nitem. And inside, let's go ahead and let's return a similar structure. So a div with a class name of flex item center and gap x2. And inside, let's open up another div. Let me just close this one. All right. And give it a class name of w10 h10 relative and shrink zero. So this is representing our image component and let's add a skeleton inside with a class name of h full which is going to be a maximum of 10 right and let's go ahead and add w full and absolute and let's also import the skeleton because it seems like we don't have it in this component so I just added a skeleton right here above the expert organization perfect so we have the skeleton now and just below outside of this div, which is representing our image, let's add a pure skeleton here with the class name of h10 and w pull like this. And the reason we're doing this inside of the component is because we can now go inside of the sidebar. And now what we can do is just open up a new div, which is going to render an array, well, not an array, but a couple of those skeletons from nav item. So this way we don't have to style it inside of here. Instead, let me show you what we can do. So let's do space Y2. So they're going to have space below one another. And then we're going to use nav item, which we already have imported, but we're going to write dot skeleton like this. And let's copy and paste this three times. And let's take a look at that now. When I refresh, there we go. You can see how that looks. It's representing this state, right? We are putting three of them because I don't know, I imagine users are going to have more organizations, some kind of trying to get closer to what it would actually look like. Perfect. I think this looks much better now. Great. And now we are ready to create this settings page. So creating the settings page is actually going to be quite easy because we have finished component for that. We're actually going to use this component, which opens up when we click on manage organizations right here. So we're going to reuse this component, but clerk offers us either to use that inside of a model or inside of a inline component. So what we have to do is fix this 404 first, as we can see in the URL that goes to slash settings. So let's go inside of the organization, organization ID, and in here, create a new folder settings, which is going to represent that route. And then let's create the page.tsx inside const, I can actually call it settings page, and return a div saying settings like this, and export default settings page. And when I save, we should no longer be having a 404 error. Instead, you can see that this is selected now because the path name matches. So let me just show you where that works, just in case you're having any errors. It's inside of the nav item here. You can see that we check if the path name is equal to route.href. And as you can see, the href, let's find my routes right here. The href is slash organization, organization ID, and then activity right here. So make sure you don't have any typos here, like organization with an S or something like that. Make sure it has no typos. and then it should be working. Perfect. So let's head back inside of this settings page. And all we have to do is we have to import organization profile from clerk next JS. And then let's render the organization profile like this. Let's give the wrapper div a width of full. And you can already see that rendering here. And let's go ahead and add an appearance to it. So elements root box is going to have a box shadow of none and a width of 100. And still inside of the elements, my apologies, we're going to have a card. And in here, we're going to add a border to be 1px solid e5, e5, e5. and we're gonna have a box shadow again of none and a width of 100%. Great, and let's take a look at that now. There we go. You can see how we now have settings. And when I click here, you can see that it works on a different organization. So if I go back to this one, it's another. If I go to test, it's test. Perfect, so now that is working as well. great great job and if you're interested in what this appearance does you can go ahead and comment it out like this and then you're going to see that it looks just a little bit different it has this shadow which kind of looks like it should have been a model but thankfully we have access to the appearance prop so we can add the necessary attributes to make it look more inline and more according to our chat CN components. Great, great job. Great. So now that we have our settings page finished, what I want to do is I want to set up our database. And the reason I want to do that already is because the next thing I want to teach you is how to use the new server actions. We're going to start from the most primitive way of using server actions, and then we're slowly going to abstract them into our own custom hook and our own custom util, which is going to make those server actions completely type safe and also validated using Zod. So the first thing I want to do is head inside of your terminal. So let's go ahead and open that and you can feel free to shut down the app if you are running it in the same terminal. And let's go ahead and npm install d Prisma like that. And let's wait a second for this to finish. And after it's finished, let's go ahead and run npx Prisma init. And that's going to generate a couple of files inside of our project. So let's take a look at what those files are. As you can see now, we have a Prisma folder and we have a schema.prisma file inside. And inside of our .environment file, you can see that after our environment keys for clerk, we now have this big comment that this was inserted by Prisma in it. And we have a mock database URL. So this is not going to work. This is just a dummy placeholder. But we're going to replace that with our actual database URL. So let's go ahead and do that now. So for my solution, I'm going to use PlanetScale. PlanetScale offers a completely free unlimited database, but it does require a credit card to prevent abuse of that free tier by creating more accounts. So if you don't have a credit card for any reason or just don't want to use it, you don't have to use PlanetScale. So the only important thing is that you use some kind of SQL. If you want to, you can set up your own local Docker MySQL instance, or you can Google PlanetScale free alternative. The reason I'm using PlanetScale is because it's by far the fastest way to create a production ready MySQL database with load balancers and of course, amazing Prisma support for migrations. So I'm going to show you how to do it with PlanetScale. Again, if you don't have a credit card or don't want to use it, you don't have to. You can just Google how to set up MySQL locally because the only thing we need from PlanetScale is this database URL. That's the only thing we need. Okay, so head to planetscale.com or whatever your solution is and just go ahead and sign in. After you sign in, go ahead and find the button which says new database. Make sure you don't have any other databases active. So this is my paid database. That's why I have it. But you always have one free database. So go ahead and create a new database and give it a name. In my case, it's going to be Trello tutorial. And as you can see, you have the option hobby, which is free forever. But as I said, it does require a credit card. Again, if you don't want to do that, you can just find a local solution for MySQL. those are always free. So make sure you select the free option and just go ahead and click create database and confirm one more time here that total monthly cost is free and click create a database. And while that is creating, let's go ahead and let's select our development instance. So as you can see, we have a lot of options here, but the one we are using is Prisma. So go ahead and select Prisma like this. And here we have to create a password for our database, which is in the same way going to create our database URL. So go ahead and click create password, the name does not matter. If you want to, you can copy the username and password and store it somewhere safe. If you accidentally lose it, you can always create a new password, but you cannot see it after it's been generated, right? So make sure you copy it. Again, if you forget it, it's not a big problem. You can always generate a new one for the same database, but you cannot see it immediately after you copy it. Great, so now it says that we have to configure our Prisma application. We already did this and we run this command line and go ahead and make sure this optimized is selected for the lowest latency between our application and the actual database. And there we go. here we have the database URL. So this is what we need. So let's go ahead and copy that. And let's replace this database URL with that solution. And there we go. I have my database URL right here. Again, if you're doing this in a different solution, all that matters is that you find the equivalent MySQL database URL. And the other thing that matters that you do is update the Prisma client. So let's go ahead. We can skip over this. We don't need that. What we actually need is this. Sorry, I scrolled too far. We have to modify our schema Prisma. So let take a look at our schema Prisma to see the differences So I going to go inside of Prisma folder schema And as you can see we have to change the provider to be MySQL We have to change the URL to be the database URL. Actually, that is the same. And we have to change the relation mode to Prisma. So what I'm going to do is just copy this entire thing and replace it with this. So again, if you're not using PlanetScale, you can do this manually, just by looking at what I did. Provider is MySQL. URL is the environment variable database URL. Relation mode is Prisma and provider is Prisma Client JS. Perfect. And that's it. You don't have to look at PlanetScale again. You can close it completely. So again, just ensure that in your dot environment where you have all the clerk environment keys, you now have your database URL. And in Scream App Prisma, you modified it to look like this. and now what I want to do is I want to create our first model here. So go ahead and write model board. Let's go ahead and give it an ID which is a string, a type ID and the default value of UU ID and now let's go ahead and simply give it a title which is a string and once you add a new model inside of your schema Prisma you have to add inside of your terminal here and you have to run the following command, npxprisma generate. So this is the first command, which will locally create types and well functions for this new model, which we added. What does it mean locally? It means that it's added inside of your node modules. So with the next package we're going to install, which is this Prisma client. And then when we use our Prisma client like this const Prisma, new Prisma client, And then when we write Prisma.board, you're going to see no errors because it's added locally. So that's the first thing you have to do whenever you add a new model or modify a model inside of your database. And the second thing you have to run is npx Prisma DB push. What this is going to do is push it in my case to planet scale. So the planet scale is in sync with what we have. And whenever you do these changes, npxprisma db push and npxprisma generate, make sure that you also restart your application. So if you haven't already, shut down your application, and then just npm run dev again. But we're not going to do that just yet, because the first thing I want to do is do npm install at prisma slash client. So this is going to allow us to use Prisma in our application and now we can npm run dev. Great. So now that we have this saved, let's go ahead and create a little library, which we are going to use to access our database. So inside of the lib folder, create a new file db.ts. Go ahead and import the Prisma client from at Prisma slash client and write export const db to be global this dot prisma or new prisma client and you can ignore these errors for now we're going to resolve them in a moment and now let's write an if clause if process dot environment dot node environment is not production so if we are either in development or local or something like that in that case global this dot prisma will be our database constant And now let's go ahead and fix these errors. Or you can see our global this module doesn't have Prisma registered. We can do that quite easily by adding a declare global var Prisma to be a type of Prisma client or undefined like that. And now let's briefly explain why we are doing this. So in production, this is what's going to happen. You can imagine that we didn't write this. So this is what would happen. We wouldn't even technically need this. This is the most primitive way to export our database, which uses the new Prisma client. So why do we do all of this for our development mode or anything that's not production? Well, that's because Next.js uses hot reload. And during that hot reload, new Prisma client will be initialized multiple times. And that's gonna create a warning inside of your project. So what we do is we do the following. So when we define the database variable, we check if we have it stored in globalthis.prisma. If we don't, meaning this is the first time we just started the app, in that case, we just initialize new Prisma client. And then since we are in development because of the check in this if clause, we assign that database constant to globalthis.prisma. And then when hot reload activates, because we changed some other file, all it does, it looks like at this global this.prisma and since we already have it here, it knows that it doesn't have to initialize it again. And if you're wondering why doesn't hot reload affect global this.prisma, well, that is because global is excluded from hot reload. So that's a brief explanation of why we are doing that. So what I'm gonna do now is create the most primitive server action. And I'm going to do that inside of app folder, platform, dashboard, organization, organization ID, page.tsx. So right here where we have the text organization page, which is currently rendered right here. And we can access that component by clicking on the boards in an individual workspace. So just make sure you refresh your application. So everything is up to date because we just did a lot of turning on and turning off. Make sure you see the organization page text. And we're going to do the following. We're going to replace this text right here with a form. So I'm going to write the native HTML form element. I'm going to add a native HTML input here. I'm going to give it an ID of title. I'm going to give it a name of title. And I'm also going to make sure it's required like this. And let's go ahead and see if we can see that right here. okay it's right here but let me just go ahead and give it a little placeholder so we can see what we are typing so enter a board title and let me just give it a class name border input or maybe just border black it doesn't really matter and border there we go and a little padding one. Great. So just something that we can see clearer. Perfect. So we have this little input here. And one thing that I want to just ensure here is that organization ID page is a server component. So you can either simply know that by having, you know, experience in the app router, as I said, a couple of times, every page that you create is automatically a server component, except if it is rendered inside of a client component, which you already learned, we defined by adding use client at the top. So now this becomes a client component and let's see the difference now. So I'm gonna add a console log. I am rendered in the browser. Sorry, I am logged in the browser. That would be a better idea. So make sure you have use client and add this console log right here. refresh your page and open up your console just a second let me assign my console right here there we go and you can see my logs here I'm logged in the browser you can see how it's visible right here but what happens if I remove use client let's try that so I'm going to go ahead and expand my screen and refresh the entire thing and now as you can see I no longer have those logs So where is that log? Well, that log is visible inside of our terminal right here. As you can see, there it is. I'm logged in the browser. Well, the message stayed the same, but you see what's the difference? This is obviously rendered on the server, meaning that the logs from this component are only visible in our terminal because our terminal is rendering, is running the server. Great. So we ensured that this is a server component. Make sure you don't have use client at the top. So if you watched any of my previous tutorials, you know that with server components, we can do cool stuff, but like directly fetching the database, right? But one solution that we didn't have until now, at least not in the stable version, is how to mutate data from server components. That was one mystery that we had. So what I want to do now is I want to add an asynchronous function create, which accepts form data, which is a type of form data, which you don't have to import from anywhere. We just have it. And inside, we're going to add use server like this. And then let's console log. For now, I am triggered. and let's add this async function create inside of form action like this and let's go ahead and open our terminal so we can see the logs here. So I'm going to refresh my page now and I'm going to write test and I'm going to press enter and once I press enter you can see that I have a message here I am triggered meaning that this function which we created is successfully triggered inside of a server component. If this doesn't look, if this looks like completely ordinary to you, you have to understand that in server components, usually you were not able to pass any functions or any hooks, anything like that. So that's why if you come from my previous tutorials, this is a new thing for you. But if you came from purely a single page application world, this probably looks like a normal function to you. But there is a difference because this is entirely run on the server. This is a server component. And because of this use server declarative, we can now access our database and use this form data to, well, create a record. So let me show you exactly how we can do that. Right here, I'm going to write const title to be formdata.get title. And then what I'm going to do is I'm going to call our database and I'm going to import it from s slash lib DB. So it's that function which we created recently. And then I'm going to write db.board.create. And I'm going to pass in the data and I'm going to pass in the title. And as you can see, now we have a little type error here. So let just mark this form data as string There we go And before we run this let go ahead inside of our terminal here I going to open a new one and I going to write npxprisma Prisma Studio So let me just expand my screen so you can see it in one line MPX Prisma Studio and press enter. And that's going to be running the Prisma Studio, which is basically a UI for our database. So you can see it noticed that we have defined the model board in our Prisma schema, but we have zero records of that board. So let's see if this function now it's working. So I'm going to try and add a test here and press enter. Let's refresh here. And it looks like it's not here. So let's go ahead and check out what's going on. All right. So my apologies, I forgot that we have to put a wait here. So make sure you put a wait inside of your before your DB board create like that. And let's try this again. So I'm going to refresh this. I'm going to refresh my Prisma Studio. no records here. I'm going to write test. I'm going to press enter. And now when I refresh here, there we go. You can see I have a new record inside of my database. So as you can see, we just used a React component to do a create method or what would usually be a post API request. That's really cool. Of course, there are discussions going around whether this is actually good or not. Some people with very valid reasons may still prefer API routes, and that's completely fine. You know, depending on the type of application you have, if it's an enterprise application, this is definitely something to discuss first. If you're an indie hacker, this is just a very fast solution for you. So, you know, it comes down to your preference. I personally think it's great that we have this many options. In this tutorial, I'm not really going to discuss whether this is always the best solution. And instead, I'm going to focus on mastering this server actions as much as we can. So you at the end of the tutorial can decide yourself whether this is something you like or not. Great. So the next thing that I want to do is I want to show you how to separate this inside of a different file. Because sure, this is great. But you know, we kind of have the problem of separating concerns. I would kind of like that this organization ID page is only responsible for ID. and passing the actions, but not exactly for defining actions, because as you know, in the API, so this is representing our API, we have to check for authentication, we have to check for errors, you know, a bunch of different stuff here. So that's what I want to do next is separate this into its own file. So let's go ahead and do the following. First thing I want to do is go ahead inside of my terminal here. And I'm going to shut it down for a second. And I'm just going to run npm install zod like this and I'm just going to run my app again. Make sure you have zod installed and refresh your localhost if you shut down the app. Then I'm going to go ahead and copy this function from here and remove it at the same time and I'm going to go and I'm going to create a new folder called actions and inside I'm going to create a new file createboard.ds. Actually I'm going to use create-board.vs like this. And let's go ahead and paste that here. And let's remove this user server from here. And instead, let's just go ahead and put it at the top like that. And then let's just import the database from s slash lib db like that. And let's also do an export for this asynchronous function right here. Great. And now let's go ahead and remove this and let's now import create from actions create board and let's confirm that the same thing is working so I have one record in my database here I'm going to try and create a new one I make sure you just press enter here let's refresh and there we go it's still working I have two items in my database now so one thing I want to do before we move on is just add a little button here which says submit and give it a type of submit just so we don't have to press enter all the time and in fact we can use the button component from components ui button like that there we go this looks a bit better so let's go ahead and refresh type one to three click on submit let's refresh here and there we go we have our third record here and you probably already noticed that one thing that's missing are the loading states and also the errors, right? The only kind of validation that we have right now is this native HTML required field because we put the required prop inside of the input. So now let's go ahead inside of create board right here and let's make use of that Zod library. So go ahead and import Z from Zod and let's create our schema. So const create board is going to be z.object, which accepts the title, which is a type of z.string like this. Great. Now let's go ahead and use this create board to pass our form data to confirm that everything is working as expected. Everything has the exact types that we want. So I'm going to go ahead and write const. And from here, I'm going to extract the title. And I'm going to write create board dot parse. And then I'm going to write the title to be form data dot get title, like that. And as you can see, now we no longer have to write that as a string. Because right here, you can see that Zod is going to ensure that title is a string for us. So let's go ahead and save that and let's see if it's still working. I'm going to write in Zod and click submit here. Let's refresh and there we go. It is still working because our input is a string and that validates this parse right here. Before we move on into extracting the errors from this, right, so we can display it back to our users, what I want to do is I want to show you how to actually see those results and how to fetch them. So since this is a server component, we can do the following. We can write const boards to be await db from s slash lib db dot board dot find many. And that's actually the only thing we have to do. And since we're using await, we have to turn this into an asynchronous function. And then let's go ahead and give this a class name of flex flex call and space Y4. And below this form, let's go ahead and open a div with the class name space Y2. And let's iterate over the board. So boards.map, get the individual board like that. Open up a div with a key to be board.id. And let's write board name to be board.title. So actually, it's going to be board.title here. There we go. You can see that now I have my four records here as well as in the database. But if I try to create a new one, the new one was just created. So let's check in my database. There we go. You can see this is the newest one here, but it's not visible here until I refresh. So let me show you how you can revalidate your path inside of, well, inside of your create action. So what I'm going to do for now is I'm just going to copy my URL. So make sure you do the same thing. Copy the entire URL and go back inside of your create function here. And we're going to go ahead and we're going to add a revalidate path function from next slash cache like this. And I'm going to pass in my URL, but I'm going to go ahead and remove the local who's part of it. So just slash organization and then my organization ID. So the reason I copied the URL is because we are working with dynamic URLs, right? So it's just simply easier to copy it now. But don't worry, I'm going to teach you how to of course pass the actual ID so you can do this programmatically. So make sure you have that. Let's refresh and let's write real time. Click submit. And there we go. You can see how now it was created in real time here and updated. So now I want to show you how to, we learned how to create, but how about deleting or updating a specific board? Well, obviously we can kind of guess how we would do that, right? But what we don't know is how do we pass an ID inside of this server action? Well, in order to do that, let's go back inside of this page.tsx and let's go ahead and copy this div, which represents our board. And inside of this organization ID where we have the page.tsx, let's go ahead and just create a new file called board.tsx. So we're going to delete this later. I just want to create a quick component here. So const delete board and let's go ahead and return, well, the same thing we just rendered, right? But this time we don't need this key. And let's go ahead and create a quick interface for this. So interface delete board props. Actually, let's call it, I don't know why I called it delete board. It's going to be board. So board props. it's going to have a title which is a string and an id which is a string so let's assign those props here board props and then let's extract the title and the id and then we can render the title here go back inside of page.tsx and instead of this div we can now render that board component and let's just make sure that we do an export here so export const board and then we're going to be able to import that from dot slash board like I did right here. And we just have to pass in the key to be board.id here now. And then let's go ahead and give it a title, which is board.title and id, which is board.id. So make sure you have these three props here. And as you can see, nothing much has changed. But what I want to do now is I want to go ahead and turn this into a form as well. And let's go ahead and do the following. Let's give it a class name of flex item center and gap x of two. And let's wrap this into a paragraph like this. And then let's add a little button component from UI button which is going to render delete and give it a variant of destructive Oops And let give it a size of small like this Great So now we have a little delete button here. Right now it's not doing anything. As you can see, it's just refreshing the page because we are, we've wrapped it inside of a form. So let's just give it a type of submit here. And now what I want to do is I want to create a new server action, which is going to be used for deleting our, sorry, for deleting our boards. So let's go ahead inside of actions and create a new file, delete-board.ts, like that. Let's go ahead and export asynchronous function, delete board, which passes in the ID, which is a type of string. And what I want to do in here, actually, it's not an arrow function, sorry. What I want to do in here is simply await DB dot board dot delete where and passing the ID like this. And then I'm just going to go back inside of my create board and I'm going to copy this revalidate path. Just make sure that you are in the same organization as you were when you copied this and make sure you import revalidate path from next cache. Great. So now we have the delete board right here and now we have to find a way to pass in this ID. So let's go ahead inside of board.dsx here and let's go ahead and let's write const delete board with id to be delete board which we just created so import it from add actions delete board and then do dot bind null and then pass in the id like that and give this form an action of delete board with id like that and as you can see we have a little error here that's because inside of my delete dashboard, I forgot to write use server at the top. And when I save, as you can see, we no longer have any errors. So let's try this out. I'm gonna click delete here and there we go. You can see because it revalidates the path, it's deleted in real time. So that's how you can pass in a specific ID to the server action. So what is the main purpose of server actions? Well, obviously allowing us to do mutations from server components. But one thing that is heavily mentioned inside of Next.js documentation is progressive enhancement, which, as I understand it, allows us to do these mutations without JavaScript being active, making it thus much faster than usual API calls. Great. And what I want to show you now is how to create loading states as well as displaying errors in our fields. So the only way that I know this can be done is by mix and match of server components and client components. So I'm going to close everything just so I clear my view. I'm going to go ahead and find this page.tsx where we have the form. and what I'm going to do is I'm going to go ahead and change this form right here to be a client component. So I'm going to go ahead and just as I created this board.tsx, I'm going to create a new form.tsx. I'm going to mark it as use client and I'm going to export const form and I'm simply going to render those actions here. and let's import the button from here from ui button and now we have a little problem with this create function here so for now let's just go ahead and import it like this and then go back to page and import that form from dot slash form the same way you did with dot slash board and we don't need this too anymore great so now we have our form component and the first thing i want to add is a hook called use form state. So const, let's just add an empty array for now to be use form state from react DOM. And in the first argument, we're going to pass in the create method. And then I'm going to create a constant above called initial state to have a message of null and errors, which is an empty object. And the second argument is going to be initial state. So this kind of works like a reducer. And then I'm going to go ahead and extract the state and dispatch from this useFormState here. And now let's go ahead back inside of this create function and modify it slightly. Because when used in useFormState, which is going to give us like some pending state and some errors, we have to also modify this. But just before we do that, we're no longer going to be using the create itself here explicitly. Instead, we're going to be using the dispatch. So make sure you change your form action to use the dispatch like that. Now let's go ahead and go back inside of our actions, create board. And inside of my Z object, I'm going to add a requirement that a minimum length is three like that. And we can also add a, let's see, is it the message or invalid? I think it's a message. I'm just going to say minimum length of three letters is required. So a type of error, right? So now our Zod has an error here. And now let's go ahead and let's create a type here. Sorry, export type state, which is going to have an optional errors object, which is going to have an optional array of errors for the title. And we're going to have a message, which is a string or null. So as you can see, it's going to match our initial state here. Errors, which is an object, and message, which is a null. And then I'm going to tweak this function create, that before it accepts the form data, it's going to accept the previous state, which is going to be a type of state which we just created above. Great. So now we have that. So now let's go ahead and let's modify this create board parse to use safe parse like that. So parse will throw an error and break the application whereas safe parse will not do that. So let's go ahead and change this from the structured title to be validated fields. And then we can use that to check if we have any form errors like a too short of a title. So let's write if not validated fields dot success. So make sure you put an exclamation point before that. In that case, we are going to return an errors object, which is going to be validated fields.error.flatten.fieldErrors. And a message is going to be missing fields, like that. and then instead of using title like this what we're going to do is extract the title from validated fields dot data just like that and in order to handle this type of message error let's go ahead and wrap this into a try and catch method so I'm going to go ahead and add a catch here which is going to have our error and then we're going to return a message database error like that and lastly alongside revalidate path let's also add redirect from next slash navigation so make sure you import the redirect and we're going to redirect to that very same route like that perfect and you can see that now when I added this redirect if you paid attention all the errors inside of my form.tsx have disappeared so what exactly did we achieve with this how can we actually look at our errors now well we can do that by accessing this state right here, which is going to have this kind of object, right? So you can take a look that if our Zod validation fails, that means that we're going to have an array of errors for a specific field, which matches whatever this schema is trying to validate. So below this input right here, I'm going to go ahead and let's just do it like this. I'm going to wrap this div I'm going to wrap this input inside of a div I'm going to write a class name of flex flex dash call and space y2 like that and then in here I'm going to go ahead and write if we have state errors and let's just not misspell state so if we have state errors title in that case let's go ahead and let's add a div here and let's add an else to be null and inside of this div let's go ahead and iterate over state.errors.title.map get individual error which is a type of string and let's go ahead and add a paragraph here which is going to render that error give it a key of error and a class name text rows 500 like that. So now let's try this out. I'm going to refresh here and I'm going to enter a title which is only one letter and let's see there we go. We have an error now which says that minimum length of three letters is required. So again if this is not impressive to you you're probably coming from a background of single page applications right. But this is really cool because we're technically doing this client level stuff purely inside of a server component, which has progressive enhancement and not a single line of JavaScript is needed. Sorry, let me rephrase this. You don't have to have JavaScript enabled in your browser for you to do these actions. Right. So that's why that's one of the main things of server actions, which they allow us. Great. So now you know how to create errors, right? But one thing that you still don't know how to do is how to handle loading states.\nWhen I go ahead and create something, let's say it's successful, I wanted this little submit button to be disabled or maybe even the input itself, I want it to be disabled. So how do we do that? So what I want to do now is I want to separate my input component into a separate component as well as my button component. So I'm going to do that right here. I'm going to copy this input component here and I'm just going to create it here. like it doesn't really matter because we're going to remove these files later. So go ahead and create input.tsx or let's call it form input.tsx like that. And let's go ahead and do export const form input. And let's just return this input like that. And what I want to do next is I also want to pass in this errors, right? So I want our component to be able to render both of those. So for that, I'm going to wrap the entire thing inside of a div. Like this. Great. So now we have to create a little prop, which is going to accept those errors. So I'm going to create an interface form input props, which is going to accept an errors, which is going to be an optional. well let's just do it a record string any like that and then let's go ahead and assign that so form input props let's extract the errors and then we can just use the errors directly and the errors directly like this so perhaps we can do it in a better way we can just pass in the title which is optional and the string of arrays because we know that's what it's going to look like of course this is, you can see that this form input component would not exactly be reusable because we are fixating on the title prop, but this is just for example, right? So make sure you do this and then we can remove this entire input and we can use the form input from dot, whoops, from dot slash form input. So make sure you add that input here. Great. And let's pass in the errors, which are going to be state.errors like that. And I'm just gonna pass in the question mark here just in case state is undefined. All right, and right now, I think everything should still be working exactly the same. So I'm gonna write a short title and there we go. I still have my errors. So how do I disable this while it is submitting? Well, for that, we can use a hook called use form status. So let's go ahead and first let's mark this as use client. That's important. So use client. And then write a constant here. Use form status from React DOM. So you can see no external libraries here. We are purely using what React offers us. And from here, let's extract the pending state. And now let's go ahead and let's add a disabled. Let's go at the end here and add the disabled prop to be when it's pending. And let's actually replace this input to use the input from ShadCN UI. We don't have it. So head into the terminal here. Let me just shut down my Prisma Studio and run npx ShadCN UI latest add input like that. So wait a second for that to install. All right. And let's replace this native HTML input with the input from the components UI input. Great. And we can remove this class name now. so I had this class name for border black but we don't need it if we are using the input from chat cn great and now let's take a look if anything changes so I'm going to write test here I will click submit and you can see how it was disabled for a second you can see even when I have an error but especially now you saw for a second how I have a little cursor my cursor changed that I cannot edit this. So we successfully used this use form status. When does this use form status work? Where does this pending come from? Well, it works because our form input component is inside of a form component. So that's why it is working. And we can do the exact same thing for this button. So let's copy this button component and let's create a new one called form button.tsx. So I'm just creating a random components here. And let's do export const form button. And let's just return these items here. Let's import the button from chatcn. And let's go ahead and let's add const. Use form status from React DOM. And let's extract the pending. And let's go ahead and give it a disabled prop of pending. like this. And now go back inside of form right here and replace this button with form button component like that. So now our submit should also be disabled when it's creating. There we go. You can see how my button is disabled. Perfect. And using the exact same method, we can do it for the delete button right here. So let's just quickly do that. So we've wrapped up this thing. So we have this board component where we have the delete button. I'm going to create a new one called form delete.tsx. So let me just show you there we go form delete.tsx export const form delete. and let's just import this button from components UI button and let's go ahead and let's add const pending from use form status from react dom and give it a disabled prop when it's pending and then let's go back inside of our board and replace this with form delete from this component which we just created right here and let's go ahead and make sure that form delete is a client component so use client so we get rid of this error and let's see if that is enough for this to work when I click delete there we go you can see how it's disabled until it is finished. Perfect. So you just learned a bunch of new things about server components. You see everything that is available now. And what we're going to do in the next part is we're going to start creating some abstractions around this. Basically, I want to create a reusable way to use these server actions because sure, this is great, but it seems like a lot of work, right? And that's not at fault for the server actions. You know, we have to create a structure for it. Keep in mind that we are all probably a little spoiled by these amazing libraries like React Query, which offers an easy use mutation and immediately gives us the loading status and the success message, the error message, all of those things. So we're going to attempt to recreate use mutation, kind of, to create our reusable server components. And basically, there is not going to be a single API endpoint which does post, patch, or delete in this tutorial. We are entirely going to use server actions, even for generating the Stripe checkout page. The only place where we're going to use API calls is to fetch some information inside of those drag and drop components, which simply have to be client components, no matter how much I tried to make them server components. I couldn't do it because the package itself requires work with client components but that's not a big deal you know the the existence of server components doesn't make it so that using client components is something bad if you want to you can use client components absolutely everywhere you just always have an option to use server components which definitely have its perks and right now they might seem complicated it might seem a bit weird but keep in mind that they literally just came out so we still have months and years until people create amazing packages around them and we're gonna have something like react query but for server components very soon i'm sure of it great great job so far and let's go ahead and start creating those abstractions now All right, so now that we know the basics of server actions and we've covered the most primitive way of using it, we're going to create the following abstraction. So let me go ahead and expand my screen. And here I prepared something. So we're going to extract this server action, which we have right now, as you can see in this actions folder. Let me show you. So we have an individual action like create board. So that represents this server action. We're going to create that into a folder, which is going to have types, which are where we are going to define what kind of stuff we expect the user to input. And we're also going to define what exactly we expect to output from this server action, which can either be an error, or for example, it can be a specific type from Prisma database. We're also going to create a file called schema.ts, where we are going to keep our ZOD validation. And that schema is actually going to be our input type. And lastly, we're going to have the index TS, which is going to be the server action itself. And all of that is going to be combined inside of create safe action, as you see right here. So that's going to be a wrapper, which is going to combine all of them. And that is going to leave us with the following thing. So we're going to create a hook called use action, as you can see right here. and inside we're going to pass this safe action which is going to be this wrapper which is going to combine all of those and from this use action we'll be able to extract the execute function itself which is going to be used to call the server action we're going to be able to extract the data the error if it's a server error like something went wrong in database or if it is a field error like I don't know the title is too short And besides that, we're also going to have some callbacks. So we don't have to rely on putting this stuff inside of useEffect to, I don't know, just render a success message or something. So we're going to have an onSuccess callback, which is going to give us the data inside of its params here. And that going to be a type of output which we defined all the way here So it going to be completely type safe from start to finish OnError is going to produce an error from the server And OnComplete is simply going to be something like finally right So regardless if we succeeded or not, that's what OnComplete is going to be. So let's go ahead and let's now create this createSafeAction.ts. So I'm going to go ahead inside of... First, I'm going to close everything. And then I'm going to go inside of my lib folder and create a new file called create safe action dot TS. And first thing I want to do is I want to import Zod like this. And then I'm going to write export type field errors. So these are going to be generic errors, which we are able to get from Zod validation, which are going to be individual field errors. So that's going to be a K in key of T, because the T is going to be an object with fields which have our errors. And they are going to produce an array of strings where each string is going to be an individual error. And then let's write export type action state, which will accept a generic T input and T output. that's going to be an object which is going to have optional field errors which is a type of field errors which is going to validate the input which we send to that well function and then we're going to have an optional error which is going to be a server error like something went wrong in the database right so that's either a string or null and lastly we're going to have an optional result which we represent as data, which is going to be our t output. So with these two actions, we've created generics, which will work with any type of action we have, right? All we have to do is send in the input, which we expect the user to pass, and the output, which we expect the user to receive, which can either be, you know, a success like data, which is going to be some prisma type, like a board or an error, which is a string or field errors, which is going to be, as you can see, an object which has a specific key inside and then an array of errors inside like that. And then let's actually create the safe action. So export const create safe action is going to be a T input and T output. and that's going to have a param of schema, which is z.schema passed inside is going to be t input. Besides z schema, we're also going to have the handler itself, which will have the validated data, which is t input. And we're going to return a promise, which is going to return the action state. And one more time, inside we're going to pass in the T input and T output. Like that. And let's go ahead and return this. And now let's write return asynchronous function, which accepts the data, which is T input, and returns a promise, which is action state, the input and the output. Let's go ahead and open this arrow function. And first let's validate our schema. So const validation result is going to be schema.save parse data. And if we haven't received a success message, so if exclamation point validation result.success, then let's go ahead and let's return one of the possible types for this action state. In this case, it's just going to return field errors. So there's no error in the database and there's no data to return. We just have field errors to work with if this schema validation fails using Zod. So let's return the field errors. You can see how we already have a TypeScript here and we're going to return validation result dot error dot flatten dot field errors. And we're going to manually write as field errors, which holds the T input inside. So let me just zoom out quickly so you can see it in one line like that. And then outside of this if function, we're going to go ahead and return the handler, which gets the validated data. So validation result dot data like that. Perfect. So we finished our safe action handler. And now we're going to go ahead and modify the function to actually be able to, well, be used by this create safe action. So let's go inside of our actions folder and let's go ahead and create a new folder called create dashboard and inside first let's create schema.ts and in inside let's import zod and let's write const create board to be z.object and let's render well let's pass in what we expect from the form to come here in our case for now that's just going to be a string, which is a title, of course. And we're going to pass in the required error to be title is required. We're also going to pass in the invalid type error to be title is required as well. And then let's add a minimum value of three and let's pass in the invalid type, sorry, the message inside to be title is too short, like that. Perfect. So we now created our create board schema and let's also immediately export that. And now let's create the file which is going to hold our types. So input and output type. Inside of this folder, create a new file types.ds on the same level as schema. And in here, let's go ahead and let's import zod again. Let's go ahead and let's import board from Prisma client. So that's going to be our expected output. Let's go ahead and import action state from lib create safe action. And finally, let's import the actual schema, which is create board from dot slash schema. And then simply export type input type to be z.info type of create board and let's export type return type to be action state which accepts the input type and the return type which is board like that. And now we can finally create our handler function. So that's actually going to be this create board. So let's just copy it and paste it inside of this folder like that and just ignore all the errors for now. And let's rename it to be index.ts like that. And now let's go ahead and modify slightly. So I think it's actually easier for us to remove everything from here. And let's start by marking this as use server. And then let's write const handler to be an asynchronous function, which accepts the data, which is going to be the input type, which we can now get from dot slash types, because we are in the same folder right here. And the return type is going to be a promise, which accepts the return type, again, from dot slash types. And let's go ahead and open this arrow function. And first thing that I want to check if we have user ID or not from out clerk next.js. So we're already going to start implementing the authentication inside of this handler, which you can imagine as an API route. So if we don't have a user ID, in that case, let's just go ahead and return an error. And you can see how we have type safety here, right? Because we return this return type, which can have field errors, error, or data, which is everything we defined inside of these types right here. And because we've wrapped it inside of action state from create safe action, you can see that that's exactly what we expect. So from our server actions now, we can only return specific items. So let's go ahead and write this error to be unauthorized. Great. And now outside of here, let's go ahead and let's extract the title from this data, which is already going to be validated at this point, because we're going to wrap it in safe action. And if you take a look at our safe action, once we pass in the schema and once we pass in the actual data, you can see that it is validated right here. So we are already going to have some field errors and we don't have to do that every single time we do it here. Perfect. So we can safely extract the title from the data now. And you can see how when I hover, it expects the title in here and now let's go ahead and let's simply write let board like that and let's open a try and catch block inside of the try block let's simply write board to be equal to await db.board.create and let's also import the db from at slash lib db i'm just going to move it here so let's go ahead and give it data to just be title great and now let's go ahead inside of the catch function and let's get our error and if that happens we're going to simply return the error and that's going to be failed to create or you can write database error or internal error, something like that. Perfect. And now let's go ahead and go further. So if this catch did not happen, in that case, let's revalidate the path using next cache. So I'm just going to move it here. Let's revalidate the following path. So open backticks slash board slash board ID, which we just created. So we don't have this route yet, but we are revalidating it for future cases because that's where we are going to redirect. And then let's simply return data board like this. And then last thing we have to do is export const create board to be create safe action, which I just imported from add slash lib create safe action And in the first argument let pass in the create board schema So make sure you import that from dot slash schema as I did right here And the second argument is going to be the handler like that. And there we go. No errors. So now this create board is going to be a promise which is going to return our title. Well, it's going to return our board, right? But right now, that's only the title. Perfect. what we have to do now is create a hook, which is going to be able to accept this safe action and give us all those useful callbacks like on success, on complete, etc. So let's go ahead and let's just save this form. It doesn't matter. If you have an unsaved form, just save it. It's going to have an error, but it doesn't matter because we're going to go back and fix it. Now let's go inside of hooks and create a new file use action.ts. And first thing I want to do is I want to import use state and use callback from react itself. And then let's go ahead and let's import action state and field errors from as slash lib slash create safe action. And then let's create a type action to accept the T input, T output, and a data, which is T input, and it returns a promise, which is an action state, which we just imported, and that uses the T input and T output. And let me just zoom out for a second, so you can see it in one line like that. Make sure you have that great and then let's create the interface to what we expect from this hook so interface use action options is going to work with the output it's going to have an optional on success handler which is going to give us the data which is a type of the output and let's just not misspell output and returns a void. Then we're going to have on error, which is also an optional callback, which gives us the server error. And we can then work with it if we want to do that. And on complete, which is simply going to be a finally function. So nothing in the params here. And now let's finally write export const use action, which is going to accept the input, the output. The first prop is going to be action, which is a type of action which we defined right here. Whoops. And inside of that action, we have to pass in the T input and T output. So let's give it that T input and T output. Besides that, we're going to have options which are going to be use action options and works with the output. And by default, that's going to be an empty object. So let's open this arrow function like this. And let's just not misspell the output like that. And also, I forgot the equal sign here. All right. And first, let's go ahead and let's create a state for field errors. And let's create a set field errors. That's going to be working with use state. and that use state is going to have a type of field errors and a generic inside T input. Great. And by default, let's give it undefined. And besides T input, it's also, sorry, besides field errors, give it a pipe of undefined. So that's also a type of, well, prop it can work with. Great. So just make sure you have it written like that. besides field errors, we're also going to have the actual error state from the database, so set error, and inside use state, which is a type of string or undefined, and let's give it undefined by default, and const data set data, which is again use state, which works with the output or undefined. And by default, it is undefined. And lastly, we're going to have is loading and set is loading, which is use state. And it's going to be a Boolean, which by default is false. Great. So now we have this fields here and we can work with them. So let's write const execute to be use callback, which is going to be an asynchronous function, which accepts the input, which is T input. And right on the start, we're going to set is loading to be true. And then we're going to open the try and catch block. So let's try and get the result from the handler. So const result is await action and pass in the input, which is a type of T input, as you can see here. If we don't have any result, just break the function. And then if we have result field errors, in that case, let's set field errors to be result field errors. If we have result.error, like a server error in that case set error to be result.error and let's go ahead and write the last one so if result.data in that case set data to be result result.data and while we are here let's also assign some callbacks so we can do that for the error right here let's write options.on error question mark dot result dot error. And we're going to do the same thing, but for the data here. So options on success result dot data. Great. And now outside of this try and catch block. So let's add the catch block here and let's go ahead. Sorry, not the catch block, the finally block. Right. So just like that, we're going to set is loading to be false because it finished and options on complete and just execute it like that. Perfect. So now let's go ahead and let's pass in the action and the options. There we go. Perfect. So let's take a look at it again. So we have this execute function, which we are going to call inside of our components, which is an asynchronous function, which accepts the input which we have. And we send that input inside of our action. And then that action is going to run it through our action state and make sure it's validated. If for any reason we don't have any result, there's nothing left to do with this action. We're not gonna have any callbacks because obviously something went wrong out of our reach. If we have field errors, it means that something went wrong with the validation. And we don't have a callback for that because, well, we don't really need one. We're simply going to extract the field errors and then we're gonna pass them to the components which need them. If we have a server error, we're going to set the error and we're also gonna add a options callback on error because that's not the type of error which we are going to render under the input field. That will be an error which we will render inside of a toast, right? Like something went wrong or internal error. And finally, if we have result data, it means that it successfully created the record and then we're gonna add a callback on success so we can work with that record, like redirecting to a specific ID of that record or simply showing a success message as well. And finally, we're going to have on complete, which well serves as the finally prop, nothing more, nothing less. And the last thing we have to do is actually return all of those. So return, execute, field errors, error, and data, and is loading. great so we just finished our use action handler so just a quick reminder if any of this feels too much for you or you feel feel like you've made some mistakes you can always visit my github and take a look at this big files which we just created which are this use action and this lib create safe action right here, as well as the actions folder where I have my index schema and type. So you can always go ahead and check them in my original source code. Perfect. So what we have to do now is we actually have to use that action inside of our form. So let's go back inside of our app folder, platform, dashboard, organization, organization ID. And in here, we should have the form where we are using this use form state to get the create function. So we can now remove these two items from here and we can remove this create instead. What we're going to do is we're going to import create board like that and we don't need the button or use form state. And let's go ahead and let's do the following. So we're going to write const use action from hooks use action, which we just created. And inside, we're going to go ahead and pass the create board action like that. And then from here, we're going to be able to extract the execute function, as well as the field errors. So let's replace this state errors with those field errors. And let's go ahead and write const on submit to accept form data, which is a type of form data. And we're going to pass that inside of this action here. And then let's extract the title to be form data dot get title as string. And well, that's it for now. That's the only field we have in the database. And all we have to do is call the execute function and pass in the title. just like that and the cool thing we can do now is call this callbacks so just add a comma and open up an object and in here as you can see we have the on complete on error on success so let's call the on success let's get the data and let's console log the data success like that let's add on error here, which will get our error. And in here, console error, error. Perfect. So let's try that out now So I going to go ahead and I going to refresh my app here And I going to add test click submit And there we go You can see that my callback is working and I have the immediate access to this record which was just created in my callback Perfect. So let's try it out and let's throw an error, for example. Well, actually, let's do the quicker thing, which is just entering a very short name like T. There we go. You can see that it works as well. Perfect. So this field errors is working. and now let's go back inside of our actions create board index right here and inside of here let's throw new error it doesn't really matter let's just throw an error like that and let's see if that's going to give an error inside of our console which is later going to be our well post notification that something is wrong so once I click submit there we go I have an error failed to create which is the exact error which we send from the handler right here as you can see in the catch function we send an error failed to create and then we have access to it inside of this use action hook on error callback. Perfect so you just flawlessly executed our vision which we had right here so we combined our server action with three individual files all that so we can use it in a reusable way using use action. So this was my idea of creating a kind of a clone of use mutation, which React query offers us, which we are used to, right? Of course, this is my first time working with server actions. And I just kind of experimented with this. If you have any criticism, I will be very happy to hear it, especially if it's optimization wise or security wise, that would be great. And you know, your general opinions about this hook, feel free to leave them in the comments below. Great, great job. Now that we have our abstraction around server actions, let's go ahead and let's create some custom components similar to these ones which we just worked with, which are the custom input component, which we named form input, custom form button and similar to that. So I'm going to go ahead and close everything here and I'm going to go inside of my components folder and in here I'm going to go ahead and create a new folder called form like that. So inside of here I'm going to keep everything regarding my reusable forms. So first thing I want to create is the actual form input which has the same name as the one we created in the organization id folder. We're going to delete it later but for now I just want to create a reusable component that is going to behave similarly to that one. So first things first, we're going to mark it as use client because I already know that we're going to use that use form status to extract the pending state from it. So this can easily be used inside of any form and it will automatically be pending once the form is submitting, as well as the fact that we're going to have a lot of handlers like on blur, on change, and similar to those. So let's create the interface form input props. And first things first, we're going to need an ID, we're going to have an optional label, we're going to have an optional string, sorry, an optional type, an optional placeholder. So all of these are type of string, then we're going to have a required field, which is an optional boolean, a disabled field if we want to manually disable it. And we're going to have some errors, which are going to be a record string and string array or undefined. My apologies, undefined goes here. So like this. All right. And then we're going to have a optional class name. if we want to modify the style of this input. We're going to have default value, which is going to be an optional string. And let's just not misspell this. So default value. And we're going to have on blur, which is going to be an optional void. So that's all we're going to need for now because let's go ahead now and let's do export const form input. And first things first that I want to do is I want to enable a ref forwarding inside of this component. So we're going to write forward ref and we're going to import that from React. So let's go ahead and let's give it the types of HTML input element and form input props like that. Great. Go ahead and open parenthesis and go ahead and open parenthesis again. And this represents the props. So inside, let's extract the ID label, the type, not the prop types, my apologies. just the type placeholder required disabled errors class name default value and we're also going to have on blur and let's go ahead and give the default value a type of empty string like that and then in here add a comma after this destructured props and extract the ref and then before this parenthesis end, go ahead and open up this function. And now let's go ahead and before we do anything, so we get rid of this error, whenever you use forward ref, you need to add a display name. And we can do that quite easily by just adding form input dot display name to be form input. And there we go. Great. And now let's go ahead and let's actually extract pending from use form status, which we can import right here from react DOM. And then let's go ahead and let's return a div with a class name of space y2. Then inside, let's go ahead and add another one with a class name of space y1. And then we're going to conditionally render the label. So for now I'm just going to write label here and add an else here to be null. And let's now let's actually create this label. So for that, I'm going to use a ShadCN label component. So go ahead and add npx ShadCN UI at latest add label. Wait for a second for this to be added to your project. And there we go. Go back inside. And now we can replace this with a label which we can import Again, not from Radix, but from dot dot slash UI label, as you see right here. But I don't like using it like that. Instead, I'm going to write components. You can, of course, choose the one you like most. Great. So now that we have this label, let's add an equivalent close tag here. And let's give it an HTML4 to be ID. So these are going to be some attributes which are going to improve the accessibility. And it's easy to do that here. And it's worthwhile because this is a reusable component. So we only have to do it once and our forms are going to be accessible throughout the entire project. And let's give it a class name of text extra small font semi bold text neutral 700. Great. And then let's go ahead and let's actually render the input component, which I just imported from dot dot slash UI input. but I'm going to replace it with slash components. Input is going to be a self-closing tag. So go ahead and pass in the on blur to be on blur. Default value to be default value. We're also going to have the ref, which is going to be the ref. Required, required name, which I'm just going to map to ID. And it's also going to have the ID, which is ID. Then we're going to give it a placeholder to be placeholder. We're going to give it a type to be type. And let's go ahead and give it a disabled prop of either pending or manually disabled. And let's give it a class name, which is going to be dynamic. So let's go ahead and let's import a CN from at slash lib utils. Let's add the CN here. Let's give it the default classes, which is text small, px2, py1, and h7. And let's add the conditional class name, which the user can pass in case they want to extend this. And then let's add area dash described by to be id dash error. Like that. Great. And now what I want to do next is I want to create a reusable component for rendering our errors. So let's go ahead and do that. We're going to do it first. We're going to assign it just inside of this component here. So outside of this div, which is wrapping our label and our input, go ahead and render the form errors like this. Don't worry, the fact that we have this error that it's not defined because we're going to create it now. and go ahead and pass in the ID, which is the ID and errors, which is the errors. Great. And now let's go ahead and let's create a new file form-errors.tsx inside. And let's go ahead and let's import XCircle from Lucid React. So whenever you're working with Lucid React, I've noticed that you can do both XCircle and you can always do XCircle icon, right? So if I try to import, let's try something m square, you can also do m square icon. So that's a cool little tip for you if you're ever wondering why you have different imports. So either x circle or x circle icon. Let's create the interface form errors props. Let's give it an ID to be a string and errors is going to be an optional record which accepts the string as the first parameter and the array of strings or undefined as the second parameter. And then export const form errors. We get the ID and the errors. Let's give it a deform errors. Props type here. And let's go ahead and check if we don't have any errors. There is no need to render this component. Otherwise, let's go ahead and let's return a div, which is going to have an id of id dash error. It's going to have area dash live to be polite. And we're going to have a class name of mt2 text extra small text rows 500. Great. And now inside, let's go ahead and write errors, question mark, open parentheses.\nThis ID.map, again, with the question mark here. And let's go ahead and let's extract the individual error, which is going to be a string. And let's go ahead and render a div inside. And let's just render that error. Let's give it a key of error. And now let's just style it a bit. So I'm going to give it a class name of flex. Items center. Font medium. Padding 2. border, border rows 500, BG rows 500 slash 10, and rounded small. And then next to the actual error, we're going to render the X circle from Lucid React. Sorry, we already have it imported, right? Okay, so just one is enough. We don't need both variations. So X circle, make sure you have that. And let's just quickly give this some styles. So h-4, w-4 and margin write 2. Perfect. And now we can go back inside of the form input and we can import the form errors component from dot slash form errors. And since they are in the same folder, I'm not going to replace that with the alias because I think this is good enough. Great. And now let's try this out. So now what I want to do is head back inside of our app folder, platform, dashboard, organization, organization ID. And in here we have the form component, which is currently using the form input from dot slash form input. So let's remove this import. And let's remove this component form input inside of the organization ID page, right? You can enter it, just confirm that it's not the component which you just created, right? So let's go ahead and remove that. And let's go inside of this form now. And let's go ahead and add our form input from components form form input. There we go. So we have our new component here. Let me just align this stuff here. Great. So make sure you have form input from s slash components form form input, which is the component we just worked on. And besides the errors, this one, as you can see, also needs to have an ID. so id is title like that and let's go ahead and let's see how this component looks like so make sure you refresh your localhost there we go and let's go ahead and write a text and there we go you can see how now we have custom errors here and when we're submitting you can see that it still has a little pending state but let's also test out the label right because that's another thing we added. So let's give it a label to be board title, for example. Oh, and it looks like we are still rendering the hard coded label. So let's go ahead and fix that. So inside of the components folder, inside of form, form input, we have the label prop, but seems like we are not using it at all. So label instead of the hard coded. And there we go. Now we have board title here. Great. And now what I want to test out is whether this is actually working, because I don't think this should yield that the title is too short. So let's go ahead and try that out. I'm going to debug with you here. So let's go inside of the form right here. And what first thing I'm going to do is I'm going to console log the title here. And I like to do it inside of an object because it's just easier to find in the inspect element. So let me refresh this page. Let me open my inspect element. and let's write test and click submit. And it looks like it is right here. Oh, it looks like it's working. Great, great, great. So maybe that was just some hot reload thing. When I add a short one, we have an error. Oh, the error is not clearing because we have that throw error inside of our action. I think that's why. Let me just go inside of our actions, create board. Yeah, we added an artificial error here. So let's remove the error. All right. And let's try it out now. When I click submit, there we go. It looks like there is just this errors that's still showing up. But we're going to work with that later. We're going to see if it actually creates any problems or not for us. Great. And now I want to do the similar thing, but for the submit button. So I want to create a reusable submit button. So it's going to be, again, similar to like this form button component, which we created here, right? There we go, this form button. So let's go ahead and let me just close this stuff. Let's go inside of components form and let's create form button dot psx. And first things first, let's mark this as use client. And actually, it's not going to be called form button. I want to call it form submit. So that's what its purpose is going to be. Otherwise, we don't even need to use it, right? And let's go ahead and let's import use form status from React DOM. Let's go ahead and let's import CN from lib utils. And let's import the actual button component from .data slash UI button or slash components UI button. Let's create the interface form submit props. It's going to have children, which are type of React, React node. disabled, which is an optional boolean, last name, which is a string and a variant, which can be either default, destructive, outline, secondary, ghost, link, or primary. and let's export const form submit here and let me just assign this props so form submit props and let's extract the children let's extract disabled last name and variant and let's open this arrow function here and first thing i want to do is extract the pending state from use form status. And then I'm just going to return a button component, which we imported and render the children inside. And then let's go ahead and assign all the necessary props. So disabled is going to be pending or is manually disabled. Type is always going to be submit. Variant is going to be variant. Size is going to be small. class name is going to be cn last name. So in case you have an error with this variant prop, it probably means that you have a mismatch between what you wrote here and what the button actually accepts. So just make sure that you don't have any typos here. You can always visit my GitHub or even better, we can go directly inside of the button component because it's right here in the UI folder. And in here, you can see exactly what variants you have. Great. Now let's go ahead and use this button. So make sure you save that. And let's go inside of the app folder, platform, dashboard, organization, organization ID here. Open up the form. And let's already delete form button, right? We're not going to need it here. Great. Go back inside of the form, remove this. and let's go ahead and let's add form button. Maybe I forgot to export it. Components form. Oh, I named it form submit, of course. Yeah, form submit. There we go. So form submit from components form, form submit. And let's just replace this form submit and let's give it a save text inside. and I don't think we need to pass anything else and this should work exactly as it was before right but it has built in pending status perfect great great job so you just finished creating this reusable form components what we're going to do next is we're going to create a pop over which is going to be triggered when we click on this plus icon here and then we're actually going to be able to create our first board. And we're also going to learn how to connect to Unsplash API so you can load those random images. All right, so regarding this little bug we have that when we type something short, and then we go ahead and type something again, and it works, but the error doesn't reset. I think I found the culprit to that. So it's actually inside of our use action hook right here. This is something that I did not notice during my initial development. So this is what happens. We only update the field errors, which is what is showing right here, if we have result.field errors. But let's take a look at our lib create safe action here. As you can see here, we only return field errors if this was not successful, meaning that this set field errors will not update if we fix our error and we don't return anything in here. So we can do that quite easily by just removing the if clause. So this is inside of the use action. So we are always going to update the field errors regardless if they exist or not. So let's try that out now. When I write something that's too short, I get an error. But when I write something that has the proper length. I create a new record and the error is gone. Great. So I think that should work. We are of course going to see later in the development if this is going to create any problems, but I think it should be just fine. So now it's time for us to actually clear this screen up so we can actually create, you know, some information about the current organization here at the top and a list of our boards here, as well as the button to create new boards. So let's go ahead inside of our organization ID page. So dashboard, organization, organization ID. And in here we have page.tsx. And we can now go ahead and we can remove this entire form. We can remove this entire fetching of the database. And we can remove all of these imports. And let's also remove everything we don't need. So we don't need this form anymore. That was just for us to test things out We don need form delete anymore And we also don need the individual board So inside of my organization ID you should have the underscore components page sorry folder the settings page, the layout file, and page.tsx. So let's go ahead and modify this by giving it a W full and margin bottom of 20. And then inside, we're going to render the info component, which we don't yet have. And let's go ahead and create it now. So inside of underscore components here, create a new file, info.tsx. And let's go ahead and mark this as use client. And let's create an interface, info props. Actually, we're going to do that later when we implement subscription. Sorry for that. So for now, all I want you to do is export const info props. And go ahead and simply return native. info. Actually, export const info, sorry. All right. Go back to page.psx and then you can import the info component from .slash underscore components info. And once you save, you should see the text which says info inside. Now let's go ahead and let's import use organization from clerk next js. And inside of here, let's use that hook. So use organization. and let's go ahead and extract the current organization and is loaded. And now let's go ahead and let's write if is not loaded. We're just gonna go ahead and return paragraph saying loading for now. So there we go. Now, when you refresh, you have loading for a couple of seconds and then the text info. And now let's go ahead inside of this return function and give this div a class name of flex items center and gap x4. Go ahead and open up a new div with a class name of w60 pixels, height of 60 pixels as well and a relative. And inside, we're going to render an image component from next slash image. So make sure you have that imported here. It is a self-closing tag. So let's go ahead and give it a fill property. Let's give it a source to be organization?image.url. Let's give it an alt to be organization. And let's go ahead and give it a class name of roundedMD and object-cover. And to fix this error, we can just put an exclamation point at the end. And there we go. Now our info component shows a big image of our current organization. You can see how it changes slightly when I change my organizations. Besides the image, we also have to render the name of the actual organization. So outside of this div, which wraps the image component, open up a new div and open up a paragraph and render organization question mark name. Let's give this paragraph a class name of font, semi bold and text XL. Great. And now go ahead and give this upper div a class name of space Y1. And below the paragraph, open up a div, which is going to render the credit card icon from Lucid React. So just make sure you add this credit card icon. Let's give this credit card icon a class name of H-3, W-3 and margin right of one. And just write a small text which says free. So this is later going to be dynamic when we implement subscriptions. And give this div a class name of flex, items center, text extra small, and text muted foreground. There we go. So now we have a nice little info component here at the top. And now let's go ahead and let's create a board list. Actually, just before we do that, I forgot that we have this loading state, which we have to improve. So let's go to the bottom of this info component and let's add info.skeleton to be function skeleton info and let's return a div with a class name of flex items center and gap x4 then open up a new div with the class name of w60 pixels height of 60 pixels and relative inside add a skeleton component from add slash components ui skeleton so just make sure you add that and let's go ahead and give this skeleton a class name of w full and h full and absolute property outside of this div open up a new one with a class name of space y2 and inside add a new skeleton component with a class name of h h-10 and w200 pixels open up a new div with a class name of flex items center and inside we're going to render two skeletons the first one is going to have a class name of the height h4 wh4 sorry h4 and w4 and margin right of two and the lower one is going to have a class name of h4 and a w of a hundred pixels and now let's go ahead back inside of this is loaded and instead of rendering this we're going to render info dot skeleton So save that. Let's refresh our local host. And there we go. You can see how now we have a nice skeleton here representing the image, the title, and these two little icons here. Great. So what I want to do now is go back to our page where we render this info component. And just below the info component, go ahead and add a little separator. we already have that and you can import it from add slash components ui separator again just make sure you don't accidentally import it from radix and just go ahead and give it a class name of my four great so now you should see a little line here and below that for now let's just go ahead and add a little div with the class name of px2 md px4 and then render the board list component which we are going to create just now. So go ahead inside of your underscore components here and create a new file board list dot dsx and let's go ahead and add export const board list here. So this is also going to be a server component. We're not going to turn it into a client one. And just render board list for now. Go back to page. And then you can import board list from add slash components board dash list. And there we go. We have a little board list here. So now let's go ahead and let's create the UI for that. So class name for this first div is going to be space Y4. Then we're going to go inside of here. And we're going to open up a new div with a class name of flex items center font semi-bold text large and font sorry text neutral 700 let's go ahead and render the user 2 icon from lucid react so make sure you add that import it's a self-closing tag and give it a class name of h-6 w-6 and margin write off 2 and write your boards there we go so now we have this little text here and then we're going to render the boards. We're going to render the boards inside of a grid. So open up a div and let's turn it into a grid by adding in the class name grid. And then we're going to define the amount of columns it can have on each viewport. So on mobile devices, it's going to be grid calls two. On small devices, grid calls three. On large devices, grid calls for and gap is going to be four. And I forgot to add a little space here. So this should be joined LG grid calls for let me just zoom out for a second. So you can see all of this in one line. So pause the video if you haven't. Great. And now inside of here, usually we would go ahead and fetch the boards here and then iterate over them. But since we don't really have all the necessary information to render the boards the way I want to render them, we're going to go ahead and just create the first item inside, which is going to be the button to create a new board. So go ahead and create a div, give it a roll of button, and go ahead and create a class name with aspect-video, relative, h-full, w-full, bg, muted, rounded, small, flex, flex-call, gap-y-1, items center, justify-center. So everything inside is going to be in the middle. Hover opacity-75 and transition. Great, you can see our little box right here. And inside of this box, go ahead and add a paragraph, which is going to say create new board. And let's give this paragraph a class name, which is going to say text small. below that, let's go ahead and add a little span element, which is going to say for now, five remaining. And let's give this span a class name of text extra small. So this is what it's going to look like. Great. And now what I want to create is a component, which is going to be used as a hint, right? So when we were going to have a little like question mark here. So when we hover, It's going to open a little popover and it's going to explain to the user. It's going to open a tooltip, not a popover, which is going to explain to the user what does it mean that it says five remaining. If you saw the demo, you already know what that means. But let's go ahead and create that component now. In order to do that, first, we have to install the tooltip component from ShadCN. So let's go inside of our terminal here. And let's run npx chat CNUI at latest ad tooltip. Wait a second for this to install. And after it's done, we're going to create a component called hint. Great. So it's right here. Let me just zoom back in. And let go ahead and let go inside of our components folder and create a new file hint Let go ahead and let import everything we need from at slash components UI tooltip So we're going to need the tooltip itself. We're going to need the tooltip content, the tooltip provider, and the trigger. So tooltip content, tooltip provider, and tooltip trigger. Now let's create an interface. And props. Children are going to be React React node. Description is going to be a string. Side is going to be optional. And the type of either left or right or top or bottom. And we're going to have side offset, which is an optional number. Great. And now let's export const hint. Let's assign those props. So hint props. And let's go ahead and extract the children, the description, side, and side offset. And let's go ahead and give the default value of side to be bottom. and the default side offset to be zero. And now inside, all we have to do is return a tooltip provider. Inside, add a tooltip itself. Let's give the tooltip a prop called delay duration zero. So I don't want any delay with opening the tooltip, right? I want it to instantly open when the user hovers on it. Let's add the tooltip trigger to be our children. And outside of the trigger, go ahead and write tooltip content and render the description inside. And now let's go ahead and fill this content with some props. So side offset is going to be side offset. Side is going to be side. And class name is going to be text extra small max W 220 pixels and break words. Great. So we have our hint component finished. And now we can head back inside of our board list component inside of our organization ID components right here. So go inside of the board list and let's go just below this span element where we wrote five remaining and add a hint component from add slash components hint. So let me just show you where I imported that here at the top. So hint from add slash components hint. and let's go ahead inside and let's render the help circle component from lucid react so make sure you add that icon it is a self-closing tag and go ahead and just give it a class name of absolute bottom dash two right dash two h 14 pixels and w 14 pixels and then let's go ahead and give this hint a side offset of 40. And let's write a description. So you can open back things like this. And inside, I want to write three workspaces can have up to five open boards or unlimited boards upgrade this workspace. Like that. And now, as you can see, we have a little help icon here. and when I hover on it, we'll have a useful information for the user so they know what this means. Perfect. So now let's actually turn this into a popover and then we're going to go ahead and create, well, the connection with the Unsplash and all the other things. So I'm going to create that inside of our components form folder. And before we can do that, we have to install the popover component. I don't think we have it. We're just going to check now. So npx shad cn dash ui at latest add popover. Looks like we don't have it. So it's installing popover. Great. Once that is done, go back inside of your components folder here, inside of the form folder and create a new file form dash popover dot CSX. So the reason I'm putting this form inside of the reusable components folder is because we're going to reuse it both in the navbar, as you can see with this little plus icon here, and also in this board list component. So that's why I decided to put it here. So let's go ahead and let's mark it as use client. And now let's go ahead and let's import everything we need from s slash components UI popover. so we're going to need the popover itself the popover content and the popover trigger great and now let's go ahead and let's import use action from hooks use action and let's import create board from actions create board so those server actions which we recently created right and let's go ahead and let's import form input which we have from dot slash form input and let's import form submit from dot slash form submit. Great. And now what I want to do is I want to go ahead and just, well, render the most primitive version of this. So export const form popover. And it's going to be an arrow function, so like this. And let's go ahead and create an interface form popover props. it's going to accept children, which is react.reactNode. It's going to accept a side, which is either going to be left, right, top, or bottom. Besides the side, we're also going to have the align optional prop, which can be start, center, or end. And we're going to have an optional side offset as well, as we did in our hint component. Great. So let's go ahead and assign those props to this form popover. So form popover props, get the children, get this side, and let's give it a default value of bottom. The align can stay empty and side offset. Let's give it a default value of zero. Great. So now let's go ahead and let's return a popover component, which is going to have the popover trigger. We're going to give it a prop as a child. And inside, we're going to render our children. And then let's add the popover content. Let's go ahead and let's give it an align prop of align. Let's give it a class name of W80 and padding top of three. Side of site and side offset of side offset. Now let's go ahead and create a div. With a class name, text small, font medium, text center, and text neutral 600, and padding bottom of four. And inside, let's just render, create board. Perfect. And now what I want to do is I want to use this form popover already before we start adding this other stuff. So for that, we have to head back inside of our board list, which is in the platform dashboard organization, organization ID components, board list. And I want to wrap this entire div, which is our button, right? It's right this, which says create new board. I'm going to wrap that entire thing inside of the form popover, which I just imported from as slash components forms, sorry, form form popover. Great. wrap the entire thing inside of that. So that's going to serve as the trigger to open up the popover. That's going to be our children, right? We accept the children and we pass it here to be a trigger, which is then going to open this content here. Great. And let's go ahead and just give it a side offset of 10 and a side of right. So let's try it out now. If I go ahead, I'm going to refresh and I click here. There we go. You can see a little popover here which says create board. Now I want to add a little close icon to it but we have a little issue here. So let's take a look back inside of our form popover. It looks like I've exhausted all imports inside of my popover right. There is nothing else inside of here that's being exported. We have the popover, the popover trigger, and the popover content. That seems to be it. Well, thankfully, because ShadCN offers us to enter direct components in here, we can go ahead and add it ourselves. So let's do that. Find the UI folder and go inside of your newly installed popover.tsx. So you can either find it in the folder, or you can just use this form popover component, which we are working in called command or control and click on it and then just click inside like this. So just make sure you're seeing this and we can do it quite easily. So let's write const popover close to be popover primitive dot close. And then we have to export it at the end. So after popover content add popover close. And that is it. We just modified this component so it serves our needs. So outside of this div, which says create board, go ahead and open the popover close component. Give it a prop as child. And of course, we have to import that. So let's just import it here. There we go, popover close. And inside of here, add a button component. So I just imported that from .data slash UI button, but I'm going to change that to slash components. All right. And inside of the button component, I'm going to render an X icon from Lucid React. So just make sure you do this. There we go. Import X from Lucid React. And now let's render that here. Let's give this X a class name of H-4 and W-4. And let's go ahead and give this button a class name of H-auto, W-auto, padding to absolute, top dash to right dash to and text dash neutral 600 and let also give it a variant of ghost and let take a look at our popover now So when I click here there we go I have a little close button here and it is working great. Now let's go ahead and add some input elements inside of this popover right here. So I'm going to go back inside and inside of this popover content after the popover close, let's go ahead and let's render the native HTML form element. And inside, create a div with a class name of space Y4. And let's render our form input component, which we imported at the beginning. Let's give it an ID of title. Let's give it a label of board title. And let's give it a type of text. So now when you click on your component right here, you should have the board title visible. Perfect. Now besides the form input, we're also going to need the form submit. So let's go ahead and give this form a class name of space y4 as well. And then outside of this div, which is wrapping this form input, which is going to have some more elements inside. So just so you don't get confused why we're even doing this. And then render the form submit, which I think we also have imported. So form submit and form input, and go ahead and say create inside, and then create a class name with W-full here. And now when you take a look, there we go, we have the board title and our create button here. So what I want to do now is I want to connect that to our actions. So let's go ahead here at the top, and let's include execute and field errors from our use action hook, which we have imported, passing the create board. And let's also add some callbacks. So on success, I want to receive the data and I want to have the data logged in my console inspect element. And same thing on error. I want to get the error and I want to console log the error if it happens. Perfect. So now let's go ahead and let's create a little on submit function here. So const on submit is going to work with form data, which is a type of form data. Go ahead and get the title, which is form data dot get title as string. and let's call the execute function and pass in the title. And now let's assign this on submit to be this form's action. Great. Let's go ahead and refresh this. Let me open up my inspect element here. I'm going to create a test board. And you can see it's loading. And there we go. We have the new test right here. let's try the field errors now oh that's not working because we forgot to pass in the field errors so let's go ahead and let's actually use those field errors let's see yes we extracted them from use action but we're not using it anywhere so let's go inside of the form input here and get the errors and just pass them here there we go they immediately showed up So let's refresh and try it again. There we go. We have a short title here. Let's go ahead and try and fill it up. And it goes away. Perfect. So now what I want to do is I want to add a toast component, which is going to show up when we successfully create something. For that, I'm going to be using Sonor because I think it's just a nice and slick component. So let's go ahead inside of our terminal and let's run and install Sonor. Very simple, just like that. And then first things first, let's go inside of our layout component right here in the app layout right here. And just inside of here, actually, we don't need to do it in the root layout. We actually only need it in the platform layout. So this is where I actually want to do it. Inside of this layout, which has the clerk provider. Let's go ahead and let's render our toaster component. I think it's from Sonar. So let's go import toaster from Sonar. Yes, looks like that is it. And now I want to go ahead and... Well, I don't want to do anything else, actually. I think this is just enough. So let's go back inside of our form popover. So that is located in components form form popover here. And let's go ahead and in here, let's add toast from Sonar. So make sure you add this toast import. We just learned how to import toaster, but this time we're importing the toast from Sonar. And let's run toast.success and let's write board created. and for the error let's write those.error and we can actually log the exact error which is going to come from our database because we take make sure that that is just a string like not an object or something weird so let's try that out now I'm going to refresh here test there we go you can see at the bottom I have board created here and now let's try and simulate an error here so I'm going to go inside of actions, create board index. And inside of this try, I'm going to throw new error. Let's try that out now. So again, I'm going to refresh. I'm going to write test. And there we go. It says failed to create. Great. So just make sure you remove this artificial error here and save all of your files. Perfect. So you're on a good track on creating this form popover. what we are going to do next is implement another form component, which is going to be called the form picker. And that's going to stay above this board title. And it's going to load nine random images from the Unsplash API. So you're going to learn how to do that next. Great, great job. So now I want to go ahead and I want to connect to the Unsplash API before we wrap up this entire create board component. So go ahead and Google Unsplash developers or just go to unsplash.com slash developers and go ahead and register as a developer. After you do that, go ahead and go back to unsplash.com slash developers because there is a chance that it redirects you and just go ahead and click on your apps. So you're going to see I have a different button now that I'm logged in. And as you can see, I already have a Trello application here, but I'm going to go ahead and guide you through the process of creating a new one. So find a button which says new application, and you have to agree to all of this, well, guidelines right here. And we are, of course, going to oblige by these guidelines. For example, they require you to show the user which created the picture and a link to redirect to that user's original picture. So we're going to go ahead and oblige by that and just click accept terms. Let's give this application name a Trello-Tutorial. And I'm going to go ahead and do the same thing for the description and click create an application. And as you can see now, it redirected us to this page where it says that we can apply for production. So you can do that, of course, if you want to. If this is just for your demo, you don't have to do that. production is free, as I understand, and they allow you up to 5000 requests per hour. If you need more than that, you can well get in touch with the theme, of course. So let's go ahead and scroll down here. And as you can see, for the demo, we have 50 requests in an hour, of course, this is completely free. So let's go ahead and let's use this access key to create our library called Unsplash. So I'm going to go ahead and close everything here and I'm going to go inside of my lib folder and create a new file Unsplash.ts. And inside of here, I'm going to go ahead inside of my terminal first and I'm going to run npm install Unsplash-js like this. So a package from the official Unsplash documentation and go ahead and import create API from Unsplash-JS. And then export const Unsplash is going to be create API. And in here, we're going to pass in the access key to be process.environment.next underscore public underscore unsplash underscore access underscore key and put an exclamation point at the end and fetch is just going to be our regular fetch and now let's copy this environment key and let's add it to our dot environment after the database url and then go back to your application and copy the access key from here so just click copy and go ahead and put it here so that's it That's all you need to do. And I actually want you to keep this open for now, just because I want to show you how this is measured right here. Great. So I'm just going to switch back to this screen now. And now that we have this, I want to go ahead and I want to create a component called form picker. So let's go ahead inside of our components form and create a new file form picker.vsx. and let's go ahead and mark this as used client and then let's create an interface form picker props that have an ID which is a string and errors which are going to be a record string and string array or undefined and export const form picker here and just return a div form picker and now I just want to assign the props inside So form picker props and extract the ID and the errors. Great, so now I wanna go ahead and add this component inside of our form popover right here, which we are working in. So above the form input where we have the label board title, let's add form picker component from .slash form picker. And let's go ahead and\nwith an ID of image and errors of field errors. Great, so now when you click here, you should have a text which says form picker just above the board title. So we are now slowly going to style it so it actually renders the images. So we'll go back inside of form picker here and let's go ahead and let's import our Unsplash library here. So import unsplash from add slash lib unsplash here. Let's also go ahead and let's import use effect and use state from react. Now let's go ahead and let's assign the initial images here. So const images and set images are use state. And by default, it's an empty array. and let's go ahead and define it as array and record of string any. Like that. And then let's go ahead and let's create a useEffect, which is going to fetch the images using the Unsplash API. So useEffect. Let's go ahead and pass in the empty array. and let's write const fetch images to be an asynchronous function and go ahead and open a try and catch block. Let's resolve the error very simply by adding console log error so we know something went wrong. And let's add set images and let's just put an empty array in here. And now let's go ahead and let's write const result to be await unsplash.photos.getrandom. And now let's give it a property to only read from specific collection IDs. And that's going to be 317099. So where did I get this number from? Well, this is just an ID of an album from Unsplash. This specific one is the same that the original Trello uses. So I went and looked at their network requests. And I saw that they're making a request for this collection ID. And the reason I think it's really good. Well, first of all, this collection is the official collection from the Unsplash editorial team. And the second thing that's really good about it is that most of the images inside are compatible to be wallpapers. Because as you know, on Unsplash, we might have some images which are not in the resolution or aspect ratio which fit the wallpaper. So that's why this collection ID is perfect for what we need. You can, of course, create your own collection on Unsplash of free images, or you can just find any other collection that you like. Great. And besides that, we're going to pick a count of nine. So no more than that. And then let's go ahead and run if we have result. And if the result has a response, in that case, const images, let's simply define them as result dot response as array record string and any. And then let's write set images to be images. And I just misspelled images like this. Great. Actually, it looks like this constant images and this constant images is the same. So let's call this result. And then actually not result. Let's call it okay, new images. So just something different. And then set images, new images, because you can see it's confusing. When I write images here, there's no error here, because we defined images in here. But this code will not work. So let's be explicit. And let's rename this constant to new images, and then assign the new images inside. Great. And then let's write an else function to be console.error. Fail to get images from Unsplash. Great. And now let's go ahead and let's simply add a loading state here. So we indicated the user that you're loading the images. So is loading and set is loading is going to be used state and by default is going to be true because we immediately start loading. And then just in the finally block, set is loading to be false. great and then outside of this constant fetch images we have to actually call it so at the end of use effect just fetch images like that perfect and now let's go ahead and write if is loading in that case let's return a div with a loader2 element from lucid react so make sure you import loader 2 and let's give it a class name of h-6 w-6 text-sky-700 and animate-spin and let's go ahead and give it a class name of padding 6 flex items-center and justify-center. Great and now inside of here I want to go ahead and actually render the images but just before we do that, let's go ahead and let's add one more state here. So const selected image ID and set selected image ID is going to be use state null. And let's also extract the pending state from use form status because this component is also going to be used exclusively inside the forms so we can do this and then we can we don't have to pass in any outside loading state instead we can use this pending one regardless if this is going to be an input or not right great so now let's go inside of the form picker itself and let's give this div a class name of relative let's go ahead and open up a div with a class name of grid grid dash calls dash three and gap dash two and margin bottom of two. Let's go ahead and let's do images dot map. Let's get the individual image here and let's create a div with a class name. And let's actually collapse it like this. Oops. So this is going to be dynamics at CN from at slash lib utils like this. and let's go ahead and add the cursor-pointer, relative, aspect, dash video, group, hover, opacity, dash 75, transition, and BG muted. And then let's add the dynamic. If it's pending, in that case, opacity is 50, hover, opacity 50 as well, and cursor is auto. And let's give this a key of image.id. And then let's go ahead and give it an on click option. If it's pending, break the function. Otherwise, set selected image ID to be image.id. So we show the user which image they selected. Perfect. So now let's go ahead and let's try this out. so inside of here we're going to render an image component from next slash image so just make sure you add next slash image import here it is a self-closing tag and let's go ahead and give it a property fill let's go ahead and give an alt of unsplash image let's give a class name of object dash cover and a rounded small. And let's give it a source of image.urls.tumb like this. And if you save, you should get a little error. So let's go ahead and try this out now. So I'm going to refresh, I'm going to click here. And there we go, we have an error because it's trying to load an Unsplash image, but we don't have the host name images.unsplash configured. So the same thing that happened when we try to render organization image from Clark. So go back to next.config.js and go ahead and create a new remote pattern. So the protocol is HTTPS and host name is images.unsplash.com. And what I recommend you do is actually restart your entire application. So let me just do that. I'm going to close this and let's do npm run dev again. Let's refresh the entire application here. Let's just wait a second for this to initialize. All right. And when I click here, there we go. Look at all of these beautiful images. And we have a nice little indicator that we are hovering over them and that we can click on them. But let's take a look at our requests here. So I'm going to refresh. And there we go. You can see that I already used six requests from this hour, right? So while you're developing, I think like 50 is probably something you're going to surpass. So this is what I'm going to show you to do next. So I want to create a fallback, right? In case something goes wrong with the Unsplash API, I want to have like a constant of nine images, which can always be used. even if we run out of our requests in an hour. So two options for that. You can go inside of my GitHub. You can go inside of constants here and select images. And you can just copy this entire images here. You can see there's nine of them. And it's a bunch of objects and all kinds of information that we need to render them. Or you can create your own version of that. So for that, you're gonna go ahead. Well, let's just prepare that first. So let's go ahead and create, I think we have the constants folder. Do we? We have the config folder. We don't have the constants. Okay. So let's go ahead and create the constants folder. And inside, let's create images.ts like that. And let's write export default images. Sorry, export const default images like that. and let's just make it an empty array for now and now what I want to do is I want to open my inspect elements and I want to open my network tab and in here when I click we should be seeing wait let me just try and click all here there we go okay so you should be seeing well an API request which starts with random so you can go ahead and search for random inside of here and there we go you should see the images here So go ahead and click on copy And I not sure which one it is I think it copy response Let's try that. So I'm going to go ahead and try and copy the response from that and paste it here. I don't think it's that one. It doesn't seem formatted. Perhaps we can just copy it like directly from here. Just copy everything. Let's try that. So this is how I did it. I just want to show you how you can do it yourself. There we go. That's it. Now it works. So again, you can either, if you don't want to do this, like there's no need, this is, you know, just cosmetics. You can just go inside of my GitHub and go into constantimages.cs and just copy them. So this is also going to save you if for any reason you didn't manage to set up the Unsplash API. And this is what we're going to do next. So we're going to make sure that we use this default images. So let's go back inside of our form picker component. And now let's go ahead and let's import those default images right here at the bottom. So let's write import default images from constant images like that. And what I want to do is inside of this error, if something went wrong, let's just set the images to be those default images. or you can also add them to be inside of the, well, the initial like array of images, right? So let's try if that's working. So when I click here, well, it seems to still be working. Let's try and kind of break this function. I don't know. I'm gonna go ahead and I'm just gonna throw an error already. So something like that. Let's say something went wrong in our API. when I click here, there we go. Something went wrong, but you can see that every time I open, I have the same set of images. And that's enough for us to work with. Perfect. So what we have to do next is Oblige by API Unsplash Guidelines. And they say that we need to hotlink to the original creator. So we're going to do it the same way Trello does. And that's by creating a little black bar at the bottom of each image so we can see exactly who created it. So let's go ahead and let's find where our image component is. It's right here. And let's go to the bottom and let's add a link component from next slash link. So just make sure you add this import. And let's go ahead and let's give it an href to be image.links.html like that. And I'm just actually going to collapse all the props we're going to have inside. So besides the href, we're also going to have a target, which is just going to be underscore blank. And then we're going to have a class name of opacity zero, group-hover, opacity 100, absolute, bottom-0, w-full. Then we're going to have some very small text of 10 pixels, truncate, so if name is too long, it doesn't overflow. text-white, hover, underline, and padding one, and VG black slash 10, like that. Perfect. So let's just go ahead and confirm that for this group hover, we actually added a group element with it. Great. So now whenever we hover on this top div, this thing is going to become visible. Perfect. And inside of this link, let's go ahead and run their image.user.name. So let's try that out now. and we can actually remove this error. So make sure you remove this error from fetch images. Great, and let's try it out now. When I click here, there we go. You can see how we have a nice little hot link to the actual user. I just feel like this can be a bit darker, this background, right? Because it's barely visible here, for example. So let's just see if we did that correctly. So I'm gonna go all the way here. So BG Black 10, how about we do BG Black 50, for example? Is that better? There we go. Yeah, that looks better. So it has kind of a darker background. And if you click on here, it's going to hot link you to the original creator of that image. So this is what we need to do to oblige by Unsplash API guidelines. If you ever want to click on this apply for production thing, you of course have to read the entire guidelines, right? so you can see everything you need to do here. Perfect. So now that we have that, let's actually create the functionality that when we click on an image, it shows that it is selected. So I think we already have that. Yeah, we have the on click and we set the selected image ID, but we don't have any indicator that that is true. So above this image, let's go ahead and write, if selected image ID is equal to the current image ID, which is in the array. In that case, let's go ahead and let's render a div with a class name of absolute inset y0hfulwfulbgblack30plexitemscenter and justifycenter. And inside, I just want to render a check icon from Lucid React. So make sure you import the check alongside loader to here and let's give it a class name of h-4 w-4 and text dash white and let's try that out now so I'm going to refresh and when I select an image it should be selected but it is not let's see and debug why that is not happening here so let's see set selected image id image id let's go ahead and debug uh why this is happening so set selected image id let's first see if this is working this on click so console log image id selected image let's try that first so i'm gonna go ahead inside of my inspect element when i click here Okay, it obviously sends the correct ID and it definitely sets it in the store here. And then in here, we check if that ID is matching, but for some reason it's not showing. Oh, I think it's because I have to put this below the image component. I think it's because of that. Let's try if this is then going to work. Yes, that's it. As you can see now, when I click on something, it shows a little check icon in the middle. Perfect. So that is what we needed. And now I can remove, did I remove the console log? I did. Great. What I want to do now is I want to add the errors component. Because remember, we are also passing in the errors in this. So let's go all the way to the bottom. just by the end of this div and render the form errors component from dot slash form errors. So just make sure you import edit from dot slash form errors here. And let's go ahead and let's pass in the ID to be image and errors, errors like that. Great. And now I have to add a little hidden input here, which is actually going to trigger when we select a specific image. So for that, I'm going to use the input type radio. So just write a native HTML input element and give it a type of radio like that. And now let's go ahead and give it an ID of ID, a name of ID, and let's give it a class name of hidden. Let's also go ahead and write checked to be selected image ID to be image.id. Whoops. Let's go ahead and give it on change to, well, just be an empty. Actually, I don't think we even need to pass on change. And let's give it a disabled prop of pending. And we also need to give it a value of the image which we select, right? So let's take a look at our network request. For that, I can just go here. Let's take a look at everything we have. So we're not going to be working with any of this stuff. What we're going to save in the database actually is just the actual, let me try and find actual URL. So we have raw, full, regular, small. So these are our sizes, right? So we're going to save a couple of them and we're going to do that in this way. So in this input type radio, give it a value of and open up back text. So first it's going to have the image ID. and then we're going to add a pipe and we're going to open this template literal again and write image.urls.thumb open a pipe again and then we're going to have image.urls.full then open a pipe again and then we're going to write image.links.html and then image.user.name that's going to be the last one so let me zoom out so you can see this in one line So value is image.id, pipe, imageURLs, thumb, pipe, imageURLs, full, pipe, imageURLs, sorry, imageLinksHTML, pipe, and imageUserName. So these pipes are important because in the server action, we're going to extract them. We're going to split this string by pipes. And then each item in the array is going to represent the ID, the thumbnail, the full image, the HTML link to link to that image and the user that created that image. So make sure you do it exactly like this. If you have a feeling you did something wrong, you can always visit my GitHub, find the form picker component and just copy the value which I assigned to this hidden radio right here. Great. So let me refresh now. Let's just confirm that nothing weird is happening. Great. So why did we have to add that input? So now when we submit our form, we're going to have access to the ID of that image inside of our form data. So let's go ahead and let's go back inside of the form popover. So let's go into form popover here. In here we have the on submit and we extract the title So now let write const image to be form data dot get image as string And let's console log the image. And I'm going to comment out of the execute function for now. So it doesn't mess with anything else. So I'm going to open my terminal now. When I select an image and click create, there we go. You can see my object. that image is a string which has a lot of URLs and each of them is separated by a pipe. So that's exactly what we wanted to achieve. Perfect. So why is this working? How do we know that we can access the selected image inside of formdata.get image? So if you take a look at our form picker here, we pass the ID to be image. And inside of our form picker, we assign that ID and that name to this hidden input component, which user clicks, which is checked when the selected image ID matches, right? And we do that by clicking on the outer div. So that's why this is working. Perfect. So now what we have to do is we have to modify our schema Prisma to actually accept all of these values. So let's go back inside of our Prisma schema. So where is it? Prisma schema. And let's now modify this board a bit. So besides the ID, it's also going to have organization ID, and that is going to be a string. It's going to have the title, and then it's going to have image ID, which is a string. And let's just write lowercase d. Image thumb URL, which is a string and db.text. So it can accept a very long amount of characters. Then image full URL, also a string and db.text. Then we're going to have image username, string and db.text as well. And image a link HTML to be string db.text as well. And while we are here, let's also add the created at the date time and default now. And let's also add the updated at the date time at updated at. Perfect. And this is what I like to do is I like to keep all of those aligned the same, right? just a small little cosmetic here. Okay, great. And now we have to update our schema. And let's go ahead inside of our terminal here. And first, I wanna show you how to reset your entire database. So just make sure you're doing this in development, make sure you're not doing this in production database, even though you should have necessary systems in place to prevent this from even attempting to happen. so let's write npx prisma migrate reset so this will reset the entire database and as you can see it's asking us if we are sure so just go ahead and press y and now it's going to reset the entire database so the reason i'm resetting the database is because we have a bunch of those boards created from before which don't have all the necessary data which we need and now we can write npx prisma DB push and then that is going to assign this new schema Prisma to your MySQL database and let's also just in case run npx Prisma generate so we locally have all of those new types. Perfect so now we are ready to actually save that in the database. So now we have to modify our schema a it, but not Prisma schema, but the action schema for create board. So go inside of schema.ts here. And alongside title, we also need to validate the image to be a string. And let's give it a required error of image is required. And let's also give it an invalid type error of image is required as well. Great. And now let's head back inside of index.ts. And you can see we already have some errors here because our requirements to create a board have changed. So first things first, besides user ID, I also want to extract the current organization ID. And we will not proceed further if that is not available as well as the user ID. Great. And now, besides extracting title from the data, we're also going to extract the image from the data. And then let's go ahead and let's write const, open up an array like this, and write image dot split. And we're going to split by that pipe element because inside of our form picker, as you can see in the value here, we separate all of these items by a pipe element. So let's go ahead and now extract all of them. So they're going to appear in the array by the order they are in. So first we have the ID, then the thumbnail, then the full, then the HTML link, and then the username. So let's go ahead and extract the image ID, the image thumb URL. Then let's extract image full URL, then image user name, then image link HTML. Actually, I think it goes link and then user. So let's just reverse this too. So first link and then user. Perfect. And now let's go ahead and let's write if there is no image ID or if there is no image thumb URL or if there is no image full URL or if there is no image username or if there is no image link HTML. In case any of those is missing, return an error, missing fields, failed to create board. All right. And now let's go ahead and let's modify our DB board create here. So we're going to pass in the title, the organization ID, the image ID, the image thumb URL, the image full URL, image username and image link HTML. And let's just see if we have this image link HTML, it's right here. And it seems like our has a little error here, could be because it's differently stored in the schema. Let me just revisit my Prisma schema here. So image link HTML, this should be available here. So image link HTML, can I just pass it like this? Oh, so I think we extracted it incorrectly. Oh yes, we extracted it with lowercase tml so let's just remap this to be html in capitals and then also modify the if clause to use that and then we can safely use it here perfect and i think that should be it yes that looks like it's good enough and what i want to do next is well i want to help you out a bit just in case you know this is a bit complicated what we're doing with the images so go ahead and just add console log here and I just want you to copy this array and paste it here and I just want you to confirm that you have all of this here and actually change it from array to be an object. It's going to be easier for you to notice if something is missing. So let's go ahead and try this out now. I'm going to go ahead and open my terminal here and make sure you do npm run dev because we updated our Prisma schema. So let's go ahead and try this now. So once I select a image and write test and click create, is there an error happening? Let's see why is it not working? Whoa. let's try test form popover okay let's debug let's go back inside of our form popover component where we called oh it's because i logged out the execute okay so yeah and passing the image here as well so i forgot to log out the execute okay let's try it out again so i'm gonna prepare my terminal here just because I want to see my image selection. So when I click create, there we go. Confirm that you have image username, confirm that you have image link HTML, full URL, thumb URL on image ID. None of this should be undefined. If some of these are undefined, I highly advise you that you double check that your form picker is exactly as mine is with this pipes right here and inside of the index right here confirm that you don't have any misspells in the name here and that you're using them properly throughout this if clause and as well as here but I mean you're obviously going to get some errors if anything is missing perfect so we can remove this console log and mine was successfully created I don't know if you saw the message so I'm going to go ahead and just run npx prisma studio so we can actually see this in the database and there we go look at my board right here i have an id i have an organization id the title image id image thumbnail full url username of the author the hot link and we have created that and updated at perfect so this is what we needed to do to finish up our form popover. So just two more things that I want to do here. First, I want to go back inside of my components form, form submit, and I'm going to change this variant in the props to be a default primary like this. So I want it to be this bluish color. The second thing I want is that when we successfully create a board, I want this popover to close. So let's go ahead and do that. Now we have to go back inside of our form popover here and we have to create a ref called close ref. So let's go above this use action and add const close ref to be use ref from react. Make sure you add the use ref here. I'm just going to move it here to the top. All right. And let's go ahead and give it an element ref, which you can also import from React. Open pointy brackets and inside just write a type of button. And by default it is going to be null And now we have to assign this close ref to the popover close So find the popover close right here It wrapping our button and give it a ref of close ref And then here on success what we're going to do after we, well, we no longer need this console logs, right? we now have the toaster and what i want to do here now is do close ref dot current question mark dot click like that perfect so now if you try it out and click here after it's success there we go you can see the board now closes perfect and i want to add one more thing i want to add the router here so const router use router from next slash navigation Let me show you where I imported that. I'm just going to move it here with the big imports. And then I want to do router.push slash board data.id like this. So make sure it's board and not boards. And I think this is incorrect because my data, oh, my data is unknown. Oh, that's not good. Let's try that out. Let's go inside of actions, create board. And let's look at, oh, yeah, we have some errors, it seems. Okay, let's take a look at types here. Board, why is there an error here? All right, all I had to do is press Command-Shift-P or Control-Shift-P and then type reload window here. Looks like that our types were a bit outdated because this board, which we use, you can see now has all of these new items, but looks like something happened with Visual Studio Code IntelliSense. And since we updated our Prisma schema, it didn't really sync up. Right now, there are no errors here. And in my phone popover, you can see that it says that data ID is a string. Perfect. So let's just try this out as well. now it should redirect me to a 404 page. There we go. So it goes to slash board and then that board ID, which is a 404 page. And last thing we have to do is just enable that to be opened when we click on the create button here at the top. So let's go inside of the navbar component. So let me just close everything here. And let's go inside of app folder, platform dashboard components navbar and in here let's go ahead and let's import form popover from components form form popover and go ahead and wrap the button inside of form popover like this and let's go ahead and let's give it an align of start let's give it a side of bottom and let's give it a side offset of 18 so make sure you are on desktop mode so have this big create button and there we go you can see how now we can do it from here as well as from here and we also have to wrap this mobile version into a form popover and for that one we don't need to do any additional stuff. So now make sure you are on mobile mode. And there we go. It's right here. Perfect. Great. So you just wrapped up creating the form popover component, which is used to create boards and redirect users to that board. What we're going to do next is render all of our active boards here inside of that grid. Great, great job. So now that we have our working form picker and our working input and everything is working fine, let's go ahead and let's actually render a list of our boards. So inside of your Prisma Studio, just make sure that you have a couple of boards here, which have all the necessary fields like thumbnail URL, full image URL, and some other stuff here. Great. So I'm going to go back inside of board list component, which we have inside of the app folder, platform, dashboard, organization ID, components, board list right here. And let's go ahead and let's import the database. Because remember, this is a server component. So we can go ahead and fetch the actual boards from here. And let's also extract the current organization ID from out util, which we get from clerk next JS, which I'm just going to move here to the top. And then let's say if we don't have the current organization ID, let's go ahead and return redirect from next slash navigation to slash select dash org. And I imported next navigation from here. Great. So if there is no organization AD present, even though this shouldn't happen, because our middleware prevents it from happening. But keep in mind that this one can be null or undefined. So it's just easier to do this check rather than, you know, checking it every time. And now let's go ahead and write const boards to be await db.board.findmany. And since we're using await, we also need to turn this board list into an asynchronous function. And let's write where organization ID, that's the only criteria we need. And let's order by created at descending. Great. Now that we have that, we can go ahead and iterate over those boards. So let's go ahead and just above the form popper here, let's do boards.map, get the individual board here. And let's go ahead and add a link from next slash link. So I just added that input here. Let me move it to the top. And let's go ahead and give it some props. So href is going to be slash board slash individual board ID. Then we're going to have a key, which is board ID. So let me just make that the first attribute here. Great. Then we're going to have a style, which is going to be background image. And let's open back to be URL. And inside, let's go ahead and access the board image thumb URL. So we save the thumb URL purposely because it takes only a few seconds to load that small image. But then later, we also save the full image so that we can actually load it when it comes to, well, loading a board entirely. And let's add a class name here, which is group relative aspect-video bg-no-repeat, like that, bg-center. We're also going to have bg-cover. And let's also add bg-sky700 in case it's still loading. rounded small h-full w-full padding 2 and let's go ahead and add overflow hidden like that great and now inside of the link let's go ahead and let's open a div here which is actually going to be a self-closing tag so a div like this and let's add a class name to it to be absolute in set zero bg black 30 and group dash hover is going to be bg black slash 40 and transition and now let's add a paragraph which is going to render the actual board title and let's give it a class name to be relative, font semi bold and text white. There we go. And here we have a list of our boards now. And you can see how when we hover, we have a nice little darkened effect. And when we click on individual one, it redirects us to that specific board ID. Perfect. So what I want to create now is a little loading state, right? Right now, as you can see, it's just plain like this. So let's go ahead and let's create an actual loading state for that. And then we're going to wrap the entire thing in suspense so it doesn't block the UI while it's loading. So let's go to the bottom of the board list and write board list dot skeleton to be function skeleton board list. And let's go ahead and return a div with a class name to be grid, grid dash calls dash two, small grid dash calls dash three, large grid dash calls dash four and gap four. And inside, let's render the skeleton component from add slash components UI skeleton. And let's go ahead and give it a class name of aspect-video, h-full, w-full and padding of two. And let's go ahead and just copy this perhaps eight times like this. Let's try that out now. Well, actually, we don't actually use this skeleton anywhere. So before we go ahead and do that, let's head back. Make sure you save this in the board list. And let's head back inside of page.psx, which renders the board list. And let's go ahead and wrap it inside of suspense from React. So make sure you import suspense like this. And wrap the board list inside of suspense. And go ahead and give it a fallback to be boardlist.skeleton, which we just created like that. And let's see this now. So when I refresh, there we go. It's very fast, but for a second, you can see how there is a skeleton in place of my boards here. Yeah, especially when we switch between organizations, you can see how they're still loading. Perfect, so exactly what we wanted to achieve. So we are now officially, well, regarding UI done with this page right here. We can create new boards. Because what we'll focus on now is creating this 404 page, which is going to render the individual board.\nwhere we're going to be able to change the title of the board, delete the board, and then we're slowly going to go ahead and implement the drag and drop functionality. And then when we get to that, when we get to a specific card and task functionality, that's when we're going to add the activity. And then we're going to wrap up both the activity here and also in the individual card. So we're going to leave the activity for a bit later. And then we're going to wrap it up all with, of course, Stripe subscription and limiting the actual boards. Great, great job. So now let's go ahead and let's fix this 404 page. But just before we do that, I forgot about one thing that I want to do. And that's when we are in a specific organization, I want to display the name of that organization in the tab right here. Currently, it only says Taskify, right? So let's go ahead and see how we can do that. Because we're going to do the same thing when we click on a specific board. But first, I want to do it for the current organization. So let's go inside of the app folder, platform, dashboard, organization and organization ID. And in here we have layout.tsx. So first thing I want to do is simply go inside of my terminal and I will just shut down the Prisma Studio and write npm install Lodash like this. And I believe we also need to install the types for that. So let me just try and import x from Lodash. Yes, we also need the types. So get back in the terminal. And after you do Lodash, do npm install dash d at types slash Lodash as well. So we have the types and that's it. We can close this and there we go. And from Lodash, go ahead and extract the start case. And now let's also go ahead and create another function here. Export asynchronous function generate metadata. And go ahead and extract organization slug from auth from clerk next JS. So let me just move that here. and let's go ahead and return title to be start case organization slug or we're just going to put in organization like this great and let's save this and let's see if it's working and there we go take a look at my tab now it says another and we have a pipe and then it says taskify if I switch to a new organization. Now it says test pipe taskify. So how does it generate that pipe taskify? How does it know that? Well, if you revisit our main layout here, you can see that we created a template, right? So when we add a new title inside of a layout, inside of a different layout, which is not the root layout, it keeps the title, but it moves it here to the side and we actually fill only this variable so it keeps the pipe in between. Perfect. So now what I want to do is I want to create this 404 page right here. So as you can see, the URL for that is slash board and then a specific ID. So let's go inside of the app folder here, inside of dashboard, and in here, create a new folder called board, and then create another folder. And this time it's going to have dynamic ID, so board ID like that. And let's go ahead and write page.tsx inside and let's do const board ID page. And let's go ahead and return a div saying board ID. And don't forget to export default board ID page. Great. So once you save that, you should no longer be having any errors, but it should also be a completely blank screen, right? And you still have the ability to do that from here. And now, if I'm not mistaken, when you create a new one from here, you should also get the redirected. Yes, as you can see, my redirect is working as well. My URL changed, but still, this is the page that, well, has no content inside currently. And the reason the board ID is not visible is because it is hidden inside of this navbar here. So we're going to have to move it inside of our layout. So let's just leave the board ID page like this for now. And now I want to go ahead and actually create our board ID layout. So inside of board ID, create a layout.vsx like that. And let's do const board ID layout. And let's extract the children. and we can immediately map the children to be react.react node. And let's just return a div here, which renders the children. Let's export default board ID layout. And after you save, there should be no more errors. And now let's go ahead and let's add a main element here around the children. and let's give it a class name of relative and pt28 and h-full. And there we go. Now we can see that board ID text here. And now what I want to do is I want to fetch the current board by ID. So let's go ahead and do the following. Besides the children, we're also going to have access to the params because remember layout is a server component. So every server component, including layout, also always has access to params. So let's add params to the types here and we know what it's going to be inside. There's going to be a board ID, which is a string. How do we know that? Well, because this board ID matches exactly what we named our folder. So ensure that you have no typos. And I wrote the board ID with a capital I. So that is also very important. And if it's lowercase, you're not going to be able to access it like this. Then you're going to have to be able to access it like this instead. So make sure that you keep track of cases and everything. Great. And now let's go ahead and let's get our organization ID from out. And we did that using clerk next to JS. If there is no organization ID, we can just redirect. So we do that from next slash navigation and just redirect to select organization. There we go. Make sure you have next navigation imported. And now what I want to do is fetch the current board. So constant board is await db. I imported that from add slash lib db. So let me just move it here. And since we use await, it means we have to make this an asynchronous component. So await database.board.find unique, where ID is params board ID, and the organization ID is the one which the user is currently using. Great. And if there is no board, this is a cool thing that I just recently discovered in Next14. you can manually trigger a 404 just by using not found, which you can import from navigation. Just like that, you redirect to the not found page. So there we go. And we can, of course, style the not found page, but we'll leave that at the end if we have time to do it. All right. So make sure you import not found. And now let's go ahead and give this div a style to be background image, open backticks, URL, and go ahead and use that board to get image full URL. So this is where we're gonna use that full image which we stored and give it a class name of relative, h-full, vg-no-repeat, vg-cover, and vg-center like this. And there we go. Now we can see our beautiful image here and let's try and click on another image here. There we go. You can see how all of our images are now loading in their full size after we click on a specific board. Perfect. So exactly what we wanted. What I want to do now is that when I click on a specific board, I want to change the name to, well, the same thing I just did with organization, right? I want it to say test in the tab right here. So the user knows that that's the board that they have. So we're going to do that in the board ID layout as well. So let's go ahead and do export asynchronous function, generate metadata. And metadata, of course, also has access to the params. so let's map them params is an object which holds the board id which is a string and it's not an arrow function so just open it like this and let's go ahead and extract the organization id from out which we already have imported if there is no organization id in that case just return title to be board so a generic title nothing more nothing less and then let's attempt to fetch the board. So const board is await db.board.find unique. And it's the exact same query we just did below. So id is params.board id and org id. And let's just return title to be board?title or just board generic as we did above. And there we go. You can take a look now. and in your tab, you should see the title of your exact board. So if I click on test one, two, three, my tab now says test one, two, three. Perfect, great, great job. So that's what I wanted us to do. What we're gonna do next is we're going to, whoops, what we're gonna do next is we're gonna go ahead and create a little navigation bar here. So we're gonna be able to rename the board if needed. So inside of the board ID layout here we going to add a new component called board navbar So just above this main element which is wrapping our children we going to add a board navbar like this. Whoa, I completely butchered that. Sorry. So board navbar like this. And if you save you, of course, we're going to get an error because board navbar does not exist. So let's go inside of the board ID folder and create another underscore components folder inside and let's create the board dash navbar dot CSX. And in here let's go ahead and let's write const board navbar, actually export const board navbar and let's just return a div saying board navbar. like that go back to the board id layout and now import that from dot slash underscore components board navbar and when you save you should no longer have any errors what i want to do before we had inside of the board navbar is actually just uh darkening this uh background a little bit so it's great that we can see it but i just want to darken it a bit because we're going to have you know, lists here and the lighter the image, the lesser it's actually going to be visible. So let's go ahead just below this, just below this board navbar, create a div element which is going to be a self-closing tag and give it a class name of absolute in set zero and bg-black slash 10. So we just darkened it by a little bit. It's not even noticeable. All right. And now inside of this board navbar, I want to go ahead and just define the actual props here. So interface board navbar props. And let's go ahead and let's accept the ID to be a string. So let's go ahead and do the same thing here. Let's extract ID and let's write board navbar props like that. And then let's just go ahead and pass in the ID to be params.board ID. Because the board navbar is still a server component, so we can actually repeat this fetch from here. So let's go ahead and mark this as an asynchronous function. Let's go ahead and import the database util from s slash lib db. And in here, let's go ahead and import that. But this time, we can immediately get the ID from the props. And we just have to find a way to get organization ID. And lucky for us, that's very easy. Just using out like this. And let's go ahead and make this org ID like this. The reason we can do that here is because we're going to redirect already here if we don't have the organization ID. So no need to do that otherwise. Great. And now let's go ahead and let's give this div a class name of w-full height of 14, z 40 pixels, sorry, 40, bg black slash 50, fixed top 14 flex items center px-6 gap-x-4 and text-white. Great. So now as you can see we have a nice little navbar below our first navbar here. And actually I noticed that we are kind of repeating a lot of stuff here. So how about we change that? I mean, there's no reason that we cannot pass the existing board right directly in here, right? So let's actually do that. I think it's kind of going to save us some time. So let's go ahead and pass in this board here. And let's go ahead and accept the data to be board from Prisma client. And then we are working with data, right? And then we don't need any of this. Great. I think it's just a bit simpler. All right. And now let's go ahead and let's create a component which is going to properly render the title of which is this board. And when we click on it, it's going to allow us to rename the board. So we're also going to have to create the actual, well, server action for that. So we're going to do that here inside of a board navbar. So let's create a component called board title form. So it's going to be in the same level as this board navbar. So let's write board dash title dash form dot CSX like that. Let's go ahead and let's mark it as use client. Let's export const board title form. And let's go ahead and let's return a button component like this. And let's immediately create an interface for it, board title form props. And inside of here, we expect the data, which is a type of board from Prisma client. So let's immediately assign those props here. word title form props we extract the data and then inside of this button let's go ahead and let's render data dot title and let's go ahead and give this button a class name of font dash bold, text large, H dash auto, W dash auto, P dash one, and PX dash two. And let me just reorder my imports a bit. All right. Let's save this and let's go back inside of the board navbar here. And instead of this text here, let's render the, well, the board title form from dot slash board title form. Make sure you have that imported here. And let's pass in the data. Great. And now you can see I have a big button here, which says test, which is the name of my board. And now I want to go inside of the button component so I can add a new variant, which is going to be more suitable for this kind of transparent mouth bar here. So let's go ahead inside of components UI and find the button.tsx. And just below this primary type of variant or whatever is your last one, go ahead and add a new one called transparent. We're going to give it a BG of transparent. Text is going to be white. And on hover, BG is going to be white slash 20. and now go back to board title form which is inside of your board id components board title form and give this button a variant of transparent and there we go now it looks much better it's flush with the nav bar but when we hover there is a very obvious action which we can do here so now let's actually make it do something so I'm going to go ahead and import use state from react. Let me just move it here. And let's go ahead and let's add a constant is editing and set is editing here to be use state. And by default, let's give it a false value. And now I want to create a constant disable editing, which is a function, which is very simply going to set is editing to be false. Great. And now let's go ahead and let's create an equivalent enable editing. And I'm just going to add a quick comment here to say to do focus inputs. But since we don't have any inputs at the moment, let's just do set is editing to be true. All right. And now let's go ahead and give this button an on click to enable editing. And now in here, let's write if is editing. In that case, we're going to go ahead and actually render a form. So return a form element. And let's just leave the action empty for now. And let's give it a class name. flex items center and gap x too. And let's add the form input component from add slash components slash form form input like this. And let's go ahead and give it an ID of title. Let's give it an on blur. Well, actually, it's just an empty arrow function for now. But we're going to have some blur action here happening. Let's give it the default value to be board.title, sorry, data.title. And let's give it a class name to be text lg font bold tx-open brackets seven pixels py-1h-7 bg-transparent, focus-visible, outline-none, focus-visible is going to have ring-transparent, and we're going to have border-none. Great. And now let's go ahead and let's create a couple of refs here. So right here at the top, I'm going to add const form ref to be use ref from react, and also element ref from react. So make sure you have element ref, use ref, and use state from react. And let's give this first type of element ref to be form. And by default, it is null. and then let's add const input ref. We use ref, again, element ref, a type of input and null. And now let's assign this to refs properly. So the form ref is obviously the form ref and the ref for the input is input ref great and now let go ahead and let create let's modify this enable editing here to actually focus right so i'm gonna add a little set time out here. And let's do input ref dot current focus. Input ref dot current select. And let's go ahead and try this out now. So when we click here, there we go, you can see how we switched to an input type. Perfect. Now let's just go ahead and prepare the form for submitting. So I'm just going to go ahead and create a new constant on submit. We're going to get the form data, which is a type of form data. And let's go ahead and just console log I am submitted. and we can also immediately extract the title to be form data dot get title as string and let's log that as well and now we can pass that to be the action of our form great so let's try it out I'm going to open my console here and when I write one two three and press enter there we go it says I submitted one, two, three. Perfect. And now it's time for us to actually go ahead and we'll create the actual form action. But just before we do that, I just want to fix this little, like an outline, which we have visible here. So I mean, if you like it, you can leave it. But I think in the demo, I didn't have any outline. So I just want to show you how to resolve that. we have to go inside of the components folder inside of UI and find the input from ShatCN. And in here we have the focus visible ring offset and let's go ahead and change it from two to zero. And I think that once we modify that there we go the offset is no longer visible. And while we're here let's also change the rounded MD to be rounded SM like this. So this way it just fits more to what Shat Sien actually, not Shat Sien, but the Trello actually does. Perfect. So now we have this kind of flushed experience when changing a board. There is no difference between, you know, the original one and this one. Very nice. Great. And now let's go ahead and let's actually, well, just wrap up this board title form on Blur here, right? So when on blur occurs, what I want to do is I want to manually trigger the submit as well. So we can do const on blur form ref dot current request submit. So that's going to trigger this function then. And let's go ahead and just pass it here. And I'm going to go ahead and open my console. so I just want to give the kind of a neat experience here if user types something goes outside I want that to save right because that's how Trello behaves as well perfect so we have all of that set up and now we are actually ready to create our server action which is going to modify this new title from here so I'm going to call that action update board so let me close everything here. Let me go inside of actions and let's create a new folder update board. And we can actually remove this delete board, all the server action, which we have. And inside of this update board, first let's create schema.ts. So inside of schema, we're going to need to import zod of course and then let's define export const update board to be z.object and inside all of the inputs we expect so obviously we expect the title which is a string it has a required error of title is required and it has an invalid type error and I'm just going to say title is required as well for this one. And let's also keep the minimum value of three to stay true to the creation of the board, right? Title is too short in case this happens. But besides the title, we also expect the user to pass in an ID, which we're just going to define as a type of string. So using the ID, we're going to confirm which board does the user want to update. And now let's go ahead and let's create the types. So go ahead and types.vs here. And again, let's import z from zod. Let's import board from prisma slash client. Let's import action state from lib create safe action. And let's import update board from dot slash schema. And now we can export type input type to be z.infer. whoops, z.infer type of update board and export type return type to be action state input type and board. Great. And now let's finally go inside and create and create index.ds. Let's mark this as use server. And let's go ahead and write const handler to be an asynchronous function, which accepts the data, which is a type of input type from dot slash types and returns a promise, which gives us a type of return type again from dot slash type. and let's go ahead and return this arrow function here and let's extract the user ID and organization ID from out using clerk next JS and I will just separate those two here in the inputs. Let's check if we are missing a user ID or if we are missing the organization ID. If either of those is true, let's return an error saying unauthorized. Great. And now let's go ahead and let's extract the title and the ID from the data. And let's write let board here just so we prepare it. And let's open a try and catch block here. So inside of the try block, let's assign the board to be await DB, which we can import from at slash libDB. I'm going to move it here. So await DB dot board dot update where ID matches and of course organization ID matches as well. And let's use data to be titled. So using this organization ID, we have prevented anyone from outside of this organization to update this board. So only the person which has the proper organization ID inside of the alfutil, which cannot be mocked or faked in any way, can actually update this board. So if you watched my previous tutorials, this is similar to checking that the current type of user which is trying to update this entity has a matching user ID. But since this time, we're not working with user IDs. We are working on an organization base. So that's why we are checking for the organization ID. So this is a kind of security for that. Great. And now let's catch the error here. And let's just do return error failed to update. All right. And now let's go ahead and let's revalidate our path. So import that from next slash cache. And let's revalidate the path to be slash board and then the individual ID, which we extracted from the data. So it's immediately updated. And let's return data to be the board. Perfect. And then let's export const update board to be create safe action, passing the update board schema. So you can import that from dot slash schema. Let me just move it here. So make sure you have create safe action from s slash lib, create safe action and schema. And in the second argument, pass in the handler. Great. And now we can go back inside of our board title form. So let's go ahead and find where that is. So inside of the app, folder, platform, dashboard, organization, organization ID, sorry, board, board ID components, board title form right here. Let's go ahead and import that action. So update board from actions, update board. And let's also import the use action from hooks, use action. All right. and let's go right here i'm gonna do this at the top level here let's extract execute from use action and let's pass in the update board and let's go ahead and extract the on success here we get the new data and inside let's call the toast which we imported from sonar I'm going to move it to the top here. So toast.success. And let's immediately say, so open backticks, board, open annotations, and we're going to spell out the exact title of this board. So data.title. So board, the title of the board, update it. So the user can immediately see that we successfully reflected those changes, right? and let go ahead and call the disable editing here great and now let go ahead and use this execute So I going to go inside of the on submit here And let go ahead and let call execute Let pass in the title and pass in the ID to be data Like that Great And just before we try it out one more thing that I quickly want to do I want to keep my title inside of state rather than directly accessing it from data. So we can do optimistic updates on it. So go ahead and write const title, set title, use state by default to be data.title. And then let's just find if we use data.title anywhere, we use it right here in the form input. So scroll to where we have the if is editing clause and change the data.title to be the title from state. And also same thing in the button here, make sure it uses the title from state. And now what we can do is inside of here on success before we disable the editing, let's do set title to be the new data.title. Great. And let's also extract on error, get the error. And let's do toast.error, the error. Great. And I think this should pretty much be it. Let's try it out now. So right now it's called test. And now I'm going to change it. I'm going to press enter. And you can see it's pending. And it says board, change it, update it. And if I refresh, the name stays. So another change here. And this time I'm not going to press enter. Instead, I'm going to blur. And there we go. Same thing, board, another change, updated. Perfect. And now what I want to create is a little action here on the side, which is going to be used to delete the board. So let's go back inside of the board navbar component. I'm going to close everything here and let's find board navbar. So it's located inside of app, platform, dashboard, board, board ID, components, board dash navbar. And in here, just below our component board title form, which we just wrapped up, let's add a div with a class name of mlauto. So this way we push it all the way to the right. And inside, let's render board options. And let's pass in data. Actually, we're only going to be needing the ID. So we can pass in the ID to be data.id. And let's go inside of the underscore components and immediately create board options.tsx. Let's mark them as use client and let's export const board options here. Let's extract the ID. Let's create a quick interface board options props to accept the ID, which is a string. and let's just map those here and let's just return a div options. Go back to the board navbar and now you can import the options from dot slash board options the same way we did with the title form and you should no longer have any errors and you should see the options text in this second navbar here. And now let's go ahead and let's import everything we need from the popover component. So import from a slash components UI popover. We're going to need the popover itself. We're going to need the popover close, the popover content and the popover trigger. Great. And now let's go ahead and let's change this entire thing to be a popover. let's add the popover trigger here let's mark it as a child inside let's add a button component which we can import from add slash components ui button make sure you add that and let's simply render more horizontal from lucid react as i added right here but i'm just going to separate it here at the top. So more horizontal is an icon and let's go ahead and give it a class name of h-4 and w-4 and let's give this button a class name of h-auto and w-auto and padding of two and a variant of transparent so it fits the look of our invisible navbar here. There we go, that looks quite nice and now outside of the popover trigger let's add the content now this content right here has the class name of px0 padding top of three padding bottom of three and a side of bottom and a line of start so let me just collapse this so it's nicer to look at great and inside of the popover content let's go ahead and create a div which is going to say board actions and inside of here let's give it a class name text small font medium text center text neutral 600 and padding bottom of four and i think we can already take a look at it. So if I click here, there we go. We have a nice text which says board actions. Now let's go ahead and let's add the close button for this popover. So after this div here, add the popover close. Let's give it a prop as child and inside open up a button again and render the x icon from lucid react so just make sure you add that import whoa okay it's a self-closing tag let's give it a class name of h4 and w4 let's give its button a class name let me just collapse so we have more room h-auto w-auto padding to absolute top-2 right-2 and text-neutral-600 it. And let's give it a variant of ghost. And now we should have the close button visible. There we go. And it works. Great. And now let's go ahead and render some options for us here. So after the popover close, let's go ahead and let's add a button element again. And let's write delete this board. And let's give it a variant ghost. Just let's not misspell variant. And let's give it on click for now to just be an empty arrow function. And let's give it a class name of rounded-none, w-full, h-auto, p-2, px-5, justify-start, font-normal, and text small. Like that. And we should have a nice little action visible now which says delete this board. And now let's go ahead and let's create an action, a server action to actually delete our board. So we can do that quite easily because we have existing actions here. So I'm going to reuse this one to update the board. So I'm going to copy the entire folder and paste it inside of the actions folder. And I will rename the folder to delete the board. And first, let's go inside of the schema to change what we need. So previously, we needed, well, the title, right? But this time, we only need the ID. So you can change it to this. and let's change this schema to be delete board. Now let's go ahead inside of our types.ts and let's change the input type to be delete board. And we also have to modify the type of here. The rest can stay the same. Now let's go back inside of index and let's go ahead and import delete board from here and scroll all the way down and add it as the first argument in the create safe action. and looks like I have a typo so it's delete board and let's also rename the actual function from update board to delete board great and now let's go ahead and let's remove the title from here we don't need it the authentication stays the same and now what we're going to do is we're going to call db.board.delete and we are not going to be passing any data inside. Instead we're going to try to delete the board which has this id and the organization id which the currently logged in user has. So if someone else is trying to do that we're not going to allow if they're not from the same organization. And instead of fail to update let's say fail to delete and now let's revalidate the path, not board, but let's go ahead and do slash organization. And then let's use the org ID. And we're not going to return anything. Instead, we're going to redirect. So call that from next slash navigation. I'm just going to move it here. So we're going to be using the redirect. And let's go ahead and let's go to slash organization slash organization ID like this. Perfect. So now that we have this action, let's head back inside of the board options. Where was it? Right here. And let's import everything we need. So we're going to need the delete board from actions delete board. we're going to need use action from hooks use action inside of here let's go ahead and let's define that so we're going to need to extract execute and is loading from use action let's pass in the delete board option let's modify the on error callback if we have an error we're going to call toast, which we can import from Sonar and let's call toast.error and let's directly log that error. And I'm just going to move\nthis sonar to the top here. And the reason we're not going to be doing anything with the on success, well, there's no need, right? Because we're going to directly redirect, as you're showing the action, back to the organization's page. So the user is clearly going to see that the deletion was successful. And now let's go ahead and let's create the on delete handler. So const on delete, execute and pass in the ID. And now let's go ahead and get this is loading and let's make this button disabled if it's loading. And let's change this empty on click to be on delete. Great. So now let's go ahead and let's just confirm this. So inside of my Taskify, I have a board called another change. And let's go ahead and delete it from here. It's loading. And there we go. it is no longer here. Perfect. So we successfully finished our initial options for manipulating the board, creating the board, and deleting the board in the end. Great, great job. What we're going to be doing next is, well, finally rendering some lists here and some cards. All right. So what I want to work on now is, of course, this actual content here where we're going to be able to create new lists and also new cards. So in order to do that, first thing that I want to do is I want to go inside of our schema. So we're going to go ahead and create the list model and the card model. So let's go ahead and do the list first. go ahead and write model list and the id which is going to be a string and id and the default value of uu id below that go ahead and give this list a title which is also a string and also go ahead and give it an order which is an integer so an order is going to be what we will modify whenever we drag and drop into a different position, right? And when we initially fetch the lists, we're going to do order by, and then we're going to use this order field. Great. And now let's create a relation with a specific board. So a list needs to exist inside a specific board. So board ID is going to be a string, and then board is going to be a type of board, which is going to be a relation, which is going to have fields, which are board ID, which reference to the ID. And on delete, we're going to cascade. So let me go ahead and zoom out so you can see this in one line right here. So now that we created an ID, a relation with the board, we have to go back inside of the board and create a relation with the list there. So go back inside of this board here and very simply just add lists and go ahead and add an individual list and an array. And there we go. And now we have to add add index here for board ID to get rid of that warning. So this is it. This is the code without any warnings or errors. So we created a relation with the boards using this board ID referencing to the ID of the actual board. And when we delete a board, we instructed that this list is also to be deleted, cascading. Great. And now we have to create a card model. So let's go ahead and do that. Just below, go ahead and write model card. It's also going to have an ID of string at ID and default UUID. Let's go ahead and give it a title, which is a string, an order which is again an integer and let's give it a description which is a optional string and db text and let me just align this items like that. You don't have to do this of course, it doesn't matter, it just needs to be in one line and now let's go ahead and create a relation with the list. So list id is going to be a string and then we repeat exactly what we did above. So list is a type of list and has a relation, fields, list ID, references, ID, and on delete cascade. And let me just zoom out so you can see. So list at relation, fields, list ID, references ID, and on delete cascade. And now we go back inside of here, inside of the list, And we add the relation with the cards very simply by adding card and an array. And then we have to do the at at index list ID here. And there we go. Again, this is the code with no warnings or errors. So pause the screen and confirm you have the same. Great. And what I want to add now is just the created at and updated at fields. So let's go back inside of the list here. and after the cards, I want to add created at to be date time and have default value of now and updated at is going to be a date time as well and have at updated at. So just like that and we can copy and paste this inside of the card model as well after the list relation. Great. So now that we have that, we are ready to push them to our database. So let's go ahead inside of the terminal here and shut down your application. And first, let's go ahead and let's actually clear our entire database. So MPX Prisma migrate reset. So this is going to remove all the existing boards that we have inside. After that is done, go ahead and run npxprisma db push like this. Wait a couple of seconds for this to align. And let's do npxprisma generate. Great. And then npm run dev finally. Perfect. So again, if you have a feeling like you did something wrong, you can always visit my GitHub. You can even find the exact commit where I wrote this if that's how you prefer it. And you can double check that your code is correct. Also, if for any reason your Prisma doesn't have a syntax like mine, like this is yellow, this is blue, you can go ahead inside of the extensions and install the Prisma extension, even though I think the Visual Studio Code will recommend that for you automatically. Perfect. Now that we have our lists, let's go ahead and let's revisit our board page. But just before we do that, we have to refresh our localhost and this should now yield a 404 because I reset my entire database here. There we go. So this is a 404. So I'm going to go slash to localhost 3000. And that's going to redirect me back to my organizations here. So let me just go ahead and create a new test organization, sorry, a new test board, and make sure you are in a board ID page like this. Now let's go back inside of the app folder, platform, dashboard. Let's go inside of the board, board ID, and here in the page.vsx where we just render the board ID, I actually want to extract the individual ID of the board, and using that, I want to fetch all the lists it has. So let's go ahead and write an interface for board ID page props. So we accept the params, which is an object which has the board ID, which is a string. And now let's go ahead and extract those here. So board ID page props, and we can safely extract the params. And now let's mark this entire function as an asynchronous function. and let's go ahead and let's import out from clerk next js and let's also import db from at slash lib db and now inside of here first thing i want to check if i have access to the current organization id using the out helper if there is no organization id in that case we are going to redirect using next slash navigation. So let me move that here. We're going to redirect the user to slash select organization, select org for short. Otherwise, let's go ahead and let's attempt to fetch all the lists this board has. So const lists is going to be await db.list. We now have it because we did npx. Prisma DB push and npx Prisma generate. And now we can do db.list.findmany. And in here, let's write board. Board ID should be params.board ID. And now to confirm that only the people inside of this organization can load this list, we're also going to write if board itself. So the board that this list was created in also has the matching organization ID of the currently logged in user. And let's also go ahead and write include. And we're immediately going to fetch the cards. And let's order the cards by their order ascending like that. So our cards also have the matching order property as our lists have. And last thing we have to do is well order the list themselves. So again, order ascending. Perhaps this order field is a bit confusingly named. Maybe I should have named it position. So you can just imagine its name position if it's confusing, you're right. Basically what it is meant to represent is the exact order how it's going to be displayed on the user's screen. And this order field is what's going to be modified every time we drag and drop the card or the list itself, right? For the lists, it's actually going to be quite simple because for the drag and dropping the lists, we can only move the order, right? It's either going to be first or in between or last, something like that. But with cards, we're also going to be able to drop to another list and then we going to be able to modify this list ID as well So it going to be a bit more complicated So just ensure that you of course have all the necessary fields in your schema Prisma for that If you do, none of this should throw an error for you and it should work just fine. We, of course, don't have any lists. So this is just going to be empty. But, you know, it's good enough for now. We can go ahead and style this div now and write a class name to be padding4whful. and overflow X auto. And inside, we're going to go ahead and render the list container. And this list container is going to be a component which we are going to create, which is going to have the board ID, which is brands dot board ID. And it's going to have the data, which is the lists which we loaded. Now let's go ahead and let's quickly create this list container. So inside of this underscore components where we have the board nav bar, board options and board title form, create a new file list-container.tsx. And what I want to do in here is just quickly mark this as use client, create an interface list container props, get the data, which is going to be list from Prisma client and board ID, which is a string. And now export const list container. Let's go ahead and assign the props, list container props. And let's extract the data and the board ID. And inside, let's just return a div list container. Great. Now go back to page.tsx. And now you can import that from dot slash underscore components list dash container. And there we go. Now we have a text list container in here. But something in my interface is actually incorrect here. So what I want to create is a specific type. So as you can see, this list is actually a type of list with cards inside of them. But in my list container, I just define them as list. And since we're going to work with that type, list with cards, a lot of times in the project, I want to create a reusable type for it. So let's go to the root of our project. Let me just expand my screen. All right, let's go to the root of our project. I'm going to close everything and create a new file, not in any folder. So let me just close everything. So go to the root of your project and create a new file, types.cs. and in here let's go ahead and let's import card and list from at prisma client and let's export type list with cards to be a list and open an object cards card and let's go ahead and let's export type card with list because we're going to work with that as well to be a type of card and list list like that and now let's head back inside of our list container component which we have in app platform dashboard board underscore components list container where we defined to be an array of list but it's actually going to be an array of list with cards from types like that. And now we have a correct type here. Perfect. So now we are ready to create, well, our first kind of item here, which is going to be the list form. So inside of this list container right here, let's instead use an order list like that. And then inside, let's go ahead and let's add a little self-closing div, which is just going to represent the empty space at the end of our x-axis. So add a class name, flex shrink zero, and width of one. So this is going to help when it comes to scrolling. When we have more items and our x overflow activates, this is going to create a little bit of padding at that overflow rather than it being flushed to the end of the screen. And above that, create a list form component, which is just going to have a self-closing tag. Now let's go inside of this underscore components here. And let's create a new file list-form.tsx. Let's mark it as use client. And let's export const list form for now to just be a viv same list form. go back to the list container and import the list form from dot slash list form and you should no longer be having any errors let's just see what is going on here okay so i just had to refresh and now it says list form now before we go on to creating list form i want to create a reusable component called list wrapper so let's go ahead inside of underscore components and create a new file list wrapper dot csx in here go ahead and import uh well actually let's create an interface list of wrapper props and let's get the children to be react react node and let's export const list wrapper to accept those children. So list wrapper props, accept the children. And let's go ahead and return a list element, which will render the children. And let's give it a class name of shrink zero, age full, and W of 272 pixels and select dash none. All right. And now let's go back inside of the list form and let's go ahead and actually start styling this. So inside of the list form, go ahead and import our list wrapper, which we just created, and go ahead and replace this with the list wrapper. And now inside, we're going to go ahead and open up a form element. let's go ahead and give this form element a class name of w full padding of three rounded medium bg white space y4 and shadow medium and there we go you can already see a little box here perfect now let's go inside of here and let's create a little button which is going to say add a list. Inside of this button, let's go ahead and give it a class name of w-full, rounded, medium, bg-white slash 80, hover bg-white slash 50, transition, padding of three, plex items center font-medium and text small and one thing that i actually want to do is i want to remove this form wrapper so we're going to do that if only if we are editing right i got ahead of myself so it should actually just be a button inside of the list wrapper like that great and below above this add a list text let's add a little plus from lucid react so make sure you import the plus. And let's give it a class name of h-4, w-4, and margin right of two, like that. And there we go. Look at our nice little button here. And now when we click on it, this is going to transform into our form. So let's do that now. In order to do that, we of course have to add some states, some refs. So let's go ahead and prepare all of that here. So we're going to import use state use uh we're going to use ref and element ref from react so let's go ahead and prepare is editing and set is editing to be use state and by default false let's go ahead and let's prepare a form ref which is going to be use ref with element of form by default it is null let's copy and paste this one and let's rename this one to be input ref and element ref of type input and i just like to separate my let's put refs first and then the use state great and now let's go ahead and let's just create a couple of handlers like we did for the editing of the board title here in our board navbar. So let's write const enable editing to be an arrow function which calls the set is editing to be true and then calls the set timeout. And let's just do input ref question mark current. Actually, we can directly access the current and then dot focus like that and now let's write const disable editing to be another arrow function which is very simply just going to set is editing to be false great and now i want to write const on key down to have an event of keyboard event. And if E dot key is the escape button, we want to disable editing. So this is just one of the things we're going to do to kind of copy that feel of using Trello and feeling like it's actually Trello, right? We're not just going to visually copy it. I also wanna copy all the neat trick it has. Like when you click on escape, the entire thing closes, right? So we're gonna keep track of details like those. And now we can actually use a little use event listener from use hooks. If you remember, which we installed in the beginning of the tutorial, we needed it for use local storage. So let's go ahead and use it even further, right? If we have it, let's use this use event listener here to listen to key down. and call the on key down And let also add use on click outside again from use hooks TS So use on click outside is gonna listen to form ref, and then it's just gonna do disable editing if we click outside the form ref. Great. And now let's go ahead and let's actually assign this button to do something. So give it an on click option to call the enable editing like that. And now let's go ahead and let's write if is editing. In that case, I want to go ahead and I want to create that form. So return again a list wrapper. And inside, let's render the form element. Let's give this form element a ref of form ref. Let's give it a class name. of w-full padding of three rounded medium bg white space y4 and shadow md like that inside of the form let's write our form input component from add slash components form form input so make sure you input this and let me just move it here above uh like that great and now let's go inside this form input and let's give it an ref of input ref let's go ahead and give it an id of title class name is going to be text small px2 py1 h7 font medium border transparent and hover, border, input, focus, border, input, and transition. So we're practically going to match what this button looks like. So I want to make a very smooth transition when it turns into an input. I don't want the UI to jump around too much. So that's why I'm kind of using these weird values, because when I developed this, I just did a bunch of trial and error, you know, with CSS to see what looks the best. So if you're confused and you think, oh, well, I would never guess this class name. So yeah, you wouldn't guess this class names. This is what you would do in trial and error. That's what mostly styling is for me. So I just want to clear that up in case you think I somehow know this up front. I don't know. And now let's give it a placeholder here. enter list title and let's give it well that's all we can give it for now I think so let's go ahead and try if I click here there we go you can see how it turns into a nice little input and when I click outside it goes back well it goes back to being a button perfect so now I want to create a little button below it. So let's go ahead. And inside of this form, well, I actually want to do another thing. I want to go ahead and put a hidden input here. So a hidden value. And let's go ahead and give it a value of params board ID. And I didn't add params. So let's go ahead and let's get our params, use params from next slash navigation, make sure you import that. And now let's go here and just get params board ID like that and give it a name of board ID. So this way we could have technically also fetched this inside of our on submit function or we can add it to a input so then we can work purely with form data. It's however you want it to be. And now below this, but still inside of this form element, create a div with a class name of flex items center and gap X one. And go ahead and render the form submit from add slash components form form submit. So So make sure you add this import as well. And inside of here, write add list. And below that, add a button component, which you can import from components UI button. So just make sure you do that as well. Great. Where are we? We are here. And inside of this button, render the X icon from Lucid React as I added right here next to my plus icon. Great. And let's give this X a class name of H5 and W5. And let's give this button a class name of actually no class name, but let's give it an on click to be disable editing. And let's go ahead and give it a size of small and a variant of ghost. And let me close this. All right. And let's try that out now. When I go here, when I click, there we go. You can see how it immediately focuses on the input. We can click on add list or we can click here. We can also kind of click outside and then it closes. We can click the escape and it closes as well. Perfect. So what we have to create now is an actual action, which is going to, is an actual server action, which is going to be called when, well, when we click submit. So let's go ahead and let's copy one of the existing actions, which we have. So I'm going to actually close everything for now. Let's go inside of the actions folder and I want to copy the update board. Let's go ahead and copy it and let's rename it to create list like this. And let's go inside of schema.ts first. So we're definitely going to require the title and instead of requiring an ID, we're going to require board ID. So the title rules can actually stay the same. It's required that a list has a title. And let's just put a minimum length of three. This, of course, doesn't matter. For example, I'm pretty sure all lists should allow a lower than three. For example, if you want your list to be named QA, that's just two letters. But you know, if you want to test out validation, you can leave it at three. It's purely your choice. And let's rename this to create list. All right. So just make sure you have validation for title and validation for board ID. So that's what we expect for the user to enter. And now inside of types.ts, let's rename this from update board to create list. And let's use it here as our input type. Now go back inside of index.ts here. And let's go ahead and slowly modify this. So first we have this schema error. Instead of update board, it is create list. And we have to use it all the way to the bottom here in create safe action. So change this to create list and rename the action to be create list as well. And now let's go to the beginning of the action and see if we have to modify anything. So the validation can actually stay exactly the same. We don't need to modify that. And now from our data, we're actually going to get the title and board ID. And we're not going to be creating a new board, we're going to be creating a new list. So let's prepare the let list instead. And then in the try block, we are assigning to that new list constant and calling awaitdbboard.list and create. This means we're not going to be using the where clause at all. Instead, we're going to be passing the data only. We're going to pass in, well, the title. We're going to pass in the board ID. But we're actually missing a couple of stuff, right? Because I also have to pass in the order, which now I can only hard code. So let's go ahead and do the following. First thing I'm going to do is attempt to fetch the board, which where this list is supposedly to be created. So const board await DB board find unique where ID is board ID and organization ID of the current user matches. And now if we don't have a board, in that case, let's return an error board not found. So this will prevent any outsiders from trying to create a new list inside of your organization's board. And well, in general, check if by the time you attempted to create this list, maybe the board was deleted. So this is going to prevent that from happening. Now that we have the board, what we can do is we can attempt to fetch the last list inside of that board so that we can properly assign the newest order of this list. So let's write const last list to be await db list find first where board ID is board ID, order by is order descending, my apologies, and select. Whoa. We just need the order. So we just want to hear about what is the last list in our board. And then when we have our last list, we can generate a constant new order to say if we have the last list, in that case, let's do last list dot order plus one. Otherwise, one like that. And then we can copy this new order and paste it inside of here. Perfect. and now let's go inside of the error and instead of fail to update let's do fail to create and instead of revalidate path id let's use the board id which we extract from the data and let's return data which is a list and now yeah we actually have to modify our type because we are expecting board I believe so let's just revisit our types.ts yes so let's go ahead and And instead of importing board, let's import list from Prisma Client and put the list as the second argument in our action state. And now, as you can see, we have no errors inside of our safe action handler. Perfect. So now let go ahead and let find our app platform dashboard board components list form And let go ahead and let import everything we need So we going to need use action from Hooks use action And we're going to need create list from actions create list. So make sure you have use action and create list. And let's go all the way to the top here. And actually, I don't want to do it here. I want to do it after these disable editing, right? So I want them to be in the scope first. And then let's extract execute and field errors from use action. Let's pass in create list. And now let's go ahead and let's actually add some existing callbacks, right? So on success, we get the data. and let's just do toast, which we can import from Sonar. So just make sure you do that. I'm going to move it to the top. All right, so we have that. And let's do toast.success, open backticks, and let's write list, open annotations, and let's write list data.title created. So we immediately show that we fetched and, well, have the name of the new list. And then let's do disable editing. And what I want to do then is also call router.refresh. So let's just go ahead and do const router to be user router from next navigation. So make sure you import user router from next navigation. And then in here, let's do router.refresh. So we refetch all of our server components. And on error, let's fetch the error. And do toast error and the error itself. Great. And now that we have that, let's go ahead and let's create our onSubmit function. So const onSubmit accepts the form data, which is form data. And in here, we have the title, which is form data get title as string. and we also have the board ID because we placed it in this hidden input here, right? And let's go ahead and let's do execute title and board ID like that. And then let's use this on submit to be an action of this board right here. and let's also go ahead and give this form input errors of field errors. And did we extract the field errors? We did. So make sure you extract field errors from use action. And let's try it out now. So I'm going to refresh here. And first, let's try something too short. There we go. We have an error that title is too short. And now let's try something like this. And there we go. It says that list has been created. Perfect. We cannot see it here because we don't do any iteration over any fields, right? But what we can do is we can visit our Prisma Studio. So let me do that. And it's Prisma Studio. That's going to open it here. And let's go ahead and see. And in my list collections, it looks like I have one. And as you can see, it has the correct title. It has the correct order since it's the first one. And it has a relation with the board exactly as we wanted. Perfect. Great, great job. So what we're going to do next, we're going to create, well, a kind of loop over our lists and actually render them out here. Great, great job. So now let's go ahead and let's actually iterate over our lists here. So we don't only have this button, but we also have our newly created lists. So in order to do that, let's go ahead and let's visit our actual board page. So go ahead inside of the app, platform, dashboard, and you should have the board here. And inside of board ID, we have page.tsx. And in here, we actually load the lists and all of the lists cards, but we don't actually go over them anywhere. So go inside the list container right here, which we created. And let's go ahead and let's store this data inside of a state. So const data, set data, or actually we can call it ordered data and set ordered data and go and write use state data. So the reason I'm doing this is because when we implement drag and drop, we're going to need to have a kind of a source of truth, which we are going to modify locally before we do it in the database. we could directly do it in the database and skip this local part but then it would kind of have a bad user experience because with drag and drop it's very important that we have a working optimistic mutation usually optimistic mutation is only used you know for specific cases where you kind of want to improve the experience like when you like something but it's not the end of the world if you don't do optimistic updates when you like something, right? But when it comes to drag and drop, it's a really bad experience to have to wait for a second. And then, you know, your content switches back to where you dragged it from. And then after a second, it only updates. So this way, we're going to use optimistic update. And we're going to immediately add a use effect here. so that when the data updates, we're going to go ahead and change set ordered data again and pass in the new data. So this is going to ensure those optimistic updates. And when later we implement the actual drag and drop, we can directly mutate the data using set ordered data. Great. And now that we have that, let's go ahead inside of this ordered list. And just before we do the list form, let's go ahead and let's iterate over data, actually ordered data dot map. And now we are working with an individual list. And let's also pass in the index while we are here. So go ahead and open this and return a list item, which is going to have a key, which is list dot ID. index, which is list.id as well. Sorry, index is going to be index, my apologies. And we're also going to have the actual data, which is going to be the entire list. Great. And if you save, you're going to get an error because we currently don't have the list item. So go inside of underscore components where we have the list container and create a new file, list-item.tsx. And go ahead and mark this as use client and export const list item here and return a div saying list item. And go back to the list container and now you can import the list item from dot slash list item. And when you save, you should be having a list item here like this. So now let's go ahead and let's just quickly give this ordered list a class name of flex gap x three and h full. And now as you can see, the list item has moved next to this button to add a list, which is exactly what we want. Now let's create an interface for the list item so we don't have this props errors here. so interface list item props is going to accept the actual data which is list with cards from types so let me just go ahead oh my god okay list with cards and we're going to have an index which is a number and we're going to use the index later when we implement drag and drop and now let's assign those props. So list item props. And we can extract data and the index. Great. So now let's go ahead and let's change this div to be a list item and give it a class name of shrink zero, H dash full and W 272 pixels and select none. Great. And now inside of here, I want to go ahead and just write a simple div element and give it a class name of w-full rounded md bg dash and go ahead and write hashtag 1f1f242f4 shadow md and padding bottom of two. Great and now inside Well, for now, we can just add a list header component like this. And if you save, you're going to get an error for that as well. So let's go back inside of the components and create a list-header.tsx like that. Mark it as use client and export const list header and return a div saying list header. and go back to the list item and then you can import the list header from dot slash list header and when you save you should no longer be having an error and I made a small mistake here in the list item component so the background should be one f and then it should be directly sorry, it should be F1, F2, F4 like this. And there we go. So now you should be seeing a white-ish color like this. So not exactly white, but just a slight gradient on it. Perfect. And now we can go inside of our list header component here. And let's give this div a class name of padding top of 2, px of 2 as well, text small. font semi-bold flex justify between items start and gap x2. Great and now inside instead of rendering list header let's go ahead and open up a new div which is gonna write list header here and give it a class name of w-full text small px-2.5 py-1 h-7 font-medium and border-transport.\nparent like that. Great. And now let's go ahead and let's actually create an interface for the list header. So interface list header props is going to accept a data which is a list like that and we can import the list from Prisma client. And let's just assign those here. So list header props. Let's extract the list. And then we can go ahead, sorry, data, not list. And then we can go ahead and actually render data.title here. And we forgot to pass that. So go back to list item and pass data from here. And there we go. You can see how now if I attempt to create a new list, it should appear right here. There we go. You can see how it's right next to this one. And if I do it again, it will appear here. Perfect. So that is working now. What I want to do now is I want to implement an ability to rename lists. So when we click on the title, I want it to switch to an input. So inside of the list header component, let me just expand this. So go back inside of the list header component. And let's go ahead and let's import the things we need, like use state, use ref, element ref from react. And the first thing I want to do is I want to store the title inside of its own state so I can optimistically update that as well. So const title, set title is going to be useStateData.title. And then let's replace this data.title to use the state version of that. Great, besides this state, we're obviously going to have the isEditing and setIsEditing from useState. And by default is going to be false. And now let's go ahead and let's add formRef to be useRef with elementRef of form and give it a null for the default value. Copy and paste that and replace this one to be the input ref. And the element ref for that is input. And then let's go ahead and let's create const enable editing. The set is editing to be true and then set timeout. So set timeout is obviously not the best solution for this. It's kind of like a hack, but it gets the job done for now. you can explore flush sync if you're interested in that but the react docs mention that it should only be used as a last resort in all of the attempts that i've tried using it it worked perfectly of course it can probably vary depending if a device is slower but i think for a tutorial it's it's just enough and i mean there is nothing no important functionality inside of this set timeout. It just focuses on the input to, you know, save the user from clicking twice. So let's focus on the input ref using the current and focus and input ref current select. I actually think we can only like, we actually don't have to do both of them. We're going to try it out now. I think we only actually need select. Great. We're going to try that in a minute. Let's now just continue and let's write the disable editing. So const disable editing. Set is editing is going to be false. Great. And now let's go ahead and let's just add the use event listener from use hooks. So make sure you import that. And let me just align those. So use event listener from use hooks. And I'm going to go ahead and pass in the key down element and on key down function. Now let's define const on key down to accept the event, which is a type of keyboard event. And then inside, if e.key is escape, in that case, we're going to submit this request. So form ref dot current question mark request submit. That's what we're going to do. Great. And now let's go ahead and let's modify this a bit. So inside of this div, let's go ahead and add if is editing. In that case, just return a paragraph which says form otherwise and then wrap this entire div here like that. And now let's just go ahead and let's give this div a on click to be enable editing like that and let me just indent this all right and already now when I try and click on this it should change to the text form like that perfect and what we're gonna do now is we're gonna actually well make the input here but we're also gonna try and add the necessary padding and stuff so that when we click it doesn't get like this little jump, right? I want it to stay the same size as the original card. So let's just refresh this so everything is in the original title form. And let's add the actual native HTML form element here. And let's go ahead and give it a class name of flex-1 and px2pixels. And let's write an input inside and let's go ahead and make it a hidden input with an id of id name of id and value data dot id let's copy the hidden input and let's change this one to be board id and name board id and the value is data dot board id like that and then let's add the form input component from add slash components form form input. So make sure you have this input right here, which I've separated. And inside of here, go ahead and give it a ref of input ref, give it an on blur. Well, for now, let's just make it an empty function. Give it an ID of title, a placeholder of enter list title. Whoa. and a default value of title from the state. So let's try that out now. When I refresh here and click here, there we go. You can see how it turns into an input. And now we're just going to go ahead and kind of style it so it flushes with the original, well, the card, right? And it kind of doesn't jump as much with this outline thing. So let's try and do that. We're going to give it a couple of class names here. So focus in the form input and let's give it a class name of text small. PX is going to be seven pixels. PY is going to be one. H is going to be seven. Font is going to be medium. Border is going to be transparent. On hover, border is going to be input. On focus, border is going to be input, transition and truncate. And let's also add BG transparent and focus BG white like that. So let's try that out now when I refresh. And when I click here, there we go. You can see how it looks kind of flush. Perfect. And what I want to do now is, well, the actual server action, which is going to trigger the submit. so let's go ahead and copy an existing server action so let me just collapse everything here go inside of actions and let's copy update board and let's rename it to update list and then first let's go inside of schema.ts so what do we expect we definitely expect the title and the rules for the title can stay the same we expect the id of the list but we also expect a board ID so we know where to update this. Great. And now let's go ahead and let's modify the types from here. Sorry, the types inside of update list. And we will not be expecting the board to return. We'll be expecting the list to be returned. And it looks like we forgot to rename the schema. So it's not going to be update board, it's going to be update list. And then we have to modify that from here and here as well. So let me just copy this like that. And now let's go ahead and inside of index here, change this to update list as well. Go all the way down, change it to here. And instead of update board is going to be update list. Perfect. So the authorization can stay the same. And from the data, we're going to extract the title, the ID, and also the board ID. And instead of updating the board, we are updating the list. So let's assign the list here. And we're doing awaitedb.list.update, where ID is ID, board ID is board ID, and board has the matching organization ID. And we are just passing in the title here. Perfect. And we're going to change this to, well, this can stay the same, failed to update. And we're going to make sure that the path we are revalidating is board ID like that. And in the end, let's just return the list. Perfect. And now let's head back inside of our app folder, platform, dashboard, board, components. And we have list header here. And let's go ahead and actually add this stuff. So after disable editing, I'm going to add use action, which we can import from add slash hooks, use action. And update list from actions, update list. So let me just show you that. I added the use action and update list. All right. Make sure you have those two. And now here, let's go ahead and let's extract the execute. Execute like that. And let's go ahead. Oh, I misspelled it again. Execute. All right. Fourth try. And now let's go ahead and add on success message here and a data. And in here, we can just display a nice message using our Toast, which you can import from Sonar, like this. Let me just move it to the top here And let call Toast here open backticks renamed to open annotations and let render the new name like that And then let set the new title so we optimistically update it to data.title here. And let's disable editing. In case we have an error, let's get that error and let's toast error error. Great and now let's go ahead and let's write the const handle submit here which accepts the form data which is form data and let's get the title. So form data dot get title as string. Let's copy this two more times. The second one is going to be the ID and the third one is going to be board ID. And let's go ahead and let's do if title is equal to data that's title, we can just disable editing, right? No need to do an API request here. And then let's do execute. And let's pass in the title ID and the board ID. And now let's also add the const on blur here to call form current request submit and it's actually form ref. All right and now let's go ahead and let's assign all of these things. So our form needs to have, let me just collapse these items. So our form needs to have a ref or form ref. It needs to have an action of handle submit. And I think that should be it. Our main input here has the ref, but we also need to pass the on blur here. And I think that should be it. Let's also just add a little button element here. which is going to be a type of submit and hidden. All right, and let's try that out. I'm going to refresh this. I'm going to go and change the title, and I can press enter, and there we go. It's updating, and it's changed, and we have a success message, and when I refresh, the title has been saved. Let's try another one, and this time I will not press enter. Instead, I'm just going to blur, and it still works, and let's try if I click escape. Same thing. It updates. Perfect. So you have finished renaming and updating the list. Now we're going to go ahead and create options tag so we can copy the list, but also delete it if needed. Great, great job. So now that we finished our list header, I want to go ahead and add a little options here on the right side so that when we click here, it opens a little popover and allows us to, you know, add a new card to this list, delete this list, or copy this list. In order to do that, let's make sure we are in the list header component, which we just wrapped up. And after this if else clause all the way here at the bottom, let's add list options component. Of course, if you save, you're going to get an error because it doesn't exist. But let's already prepare and let's give it a data of data like that. And let's also give it on add card. And for now, let's just make this an empty function. We don't have the form to add a card. So there's nothing we can do here. But I just want to make sure we don't forget about this functionality. And now inside of this underscore components folder, create a new file list options.dsx. Go ahead and mark this as use client and export const list options and return a div list options. And now let's quickly create an interface list options props. And let's define the data to be list from Prisma client and on add card. Let's just make it an empty void. And now in here, let's go ahead and assign those props to list options props. And let's destructure the data and on add card. Great. Let's go back to the list header. And now we can import this list options from dot slash list options like this. And you should no longer be having any errors. And now in your card on the right side, you should have the text which says list options. And we're now going to modify it. So it's actually, well, a drop down menu. So first thing I want to do is I want to go ahead and import everything we need from add slash components UI popover. So let's go ahead and import the popover itself. Let's import the popover content, the popover trigger and the popover close. And now inside of here, I want to replace the div with the popover component. And inside, I want to add a popover trigger. and I want to render a button component from add slash components UI button. So just make sure you add this as well. And let's mark this as child. And let's give this button a class name of h-auto, w-auto and padding two and the variant of ghost. And now let's go ahead and let's add more horizontal from lucid react so just make sure you have this import from lucid react more horizontal and let's give it a class name of h-4 and w-4 and now you should have a nice little button here which will open the popover content which we are going to create in a minute so below the popover trigger let's add the popover content here and let's give it a class name of px0 padding top three and padding bottom of three. Let's give it a side of bottom and a line of start. Great. Inside of the popover content now, let's add a div which is going to say list actions. And let's give this div a class name of text small, font medium, text center, text neutral 600 and padding bottom of four. And now you can already try and click on one of these and you should have a nice list actions popover. So now we're going to go ahead and actually add some items here. So before we do that, let's add our popover close here. And let's go ahead and add a button inside and let's render the X from Lucid React. So make sure you add the X here. All right, Let's give this X a class name of H-4 and W-4. And let's give this button a class name of H-Auto, W-Auto, padding to top, sorry, absolute, top to, right to, and text-neutral-600 and the variant of ghost. And let's also just go ahead and give this a prop as child to this popover close. And now our popover should be having a close button here, which works. Great. And now we're ready to actually render some actions inside. So below this popover close, add a button component and write add card like this. And now let's go ahead and give this an on click on add card, which is, as you remember, an empty function for now. And let's write a class name here, rounded-none, w-full, h-auto, padding to px5, justify, start, font, normal, text, small. And give it a variant of ghost. And let's check that out now. And there we go. We have a nice button which says add a card. Perfect. So besides add a card, we're going to have two more options. One is to copy a list and the other one is to delete a list. So just below this button, let's actually open a little form element here. So there are obviously multiple ways we can do this. We can directly add a button, right? And then we can use our use action hook and just call the execute on click. Or we can use the form, right? So these server actions have enabled us to do a lot more. So let's go ahead and give this an input inside, which is a hidden, has a name of ID, an ID of ID and value of data ID. And let's copy and paste it. And let's replace this one to have a name of board ID and this one board ID as well. and value is going to be data.boardID. And inside, we're just going to render the form submit, which we imported from add slash components form, form submit. And inside, let's render copy list like this. And now what we have to do is give this form submit a variant of ghost. and we can just actually copy the class name from this button, which has add a card. So you can just paste it here. And let's try that out now. So when I open, there we go. We now have a copy list, which is going to trigger a form. If you click now, it's just gonna refresh the entire page. But later we're gonna add an action to the form, which is going to properly execute what it needs to execute. And since we are using the form submit option, this is automatically going to be disabled while it is loading. perfect and now let's go ahead and let's add a little separator after we end this form so separator which you can import from s slash components ui separator so just confirm that this is your import and not the radix one and let's go ahead and just copy and paste the entire form like this and just paste it below the separator and we can keep the id and the board id And this one is not going to say copy list, but delete this list. Like that. And let's try it out now. And when we click here, as you can see, we have options that add a card, copy the list or delete the list. What we have to do now is create these two actions copy list and delete this list So the easier one of the two options to do first is the delete list especially because we already have a function to delete the board So let's go ahead and let's copy the server action for deleting the board. So we have delete board. Go ahead and copy it and let's rename it to delete list. and first let's go inside of the schema to define everything we need for it. So we need the ID but we also need the board ID. So make sure you add that and let's rename this to delete list. Now let's go inside of the types and let's change this to delete list as well and let's change this to be zinfer type of delete list and the same is for our return type. We are expecting a list and not a board. Now we can go back inside of index.ds for the delete list. And first let's fix this wrong import. So now it is delete list. And we can copy that and paste it all the way here at the bottom. And change this from delete board to be delete list. Great. And now let's go ahead and modify this. So this can of course stay the same. And from the data we now destructure the ID and board ID. And we are working with list, not board. So let's define let list here. And let's assign it here. And let's go ahead and attempt to delete a list. So db.list.delete, where we have the matching ID, we have the matching board ID, and also the board relation has the matching organization ID as our current user. So no foul play can be done here. We can leave the fail to delete here. And let's change this to revalidate the board and the board ID. And instead of redirecting, let's return data list. Just like that. Perfect. And we can remove the redirect import from here. Now that we have this finished, let's go ahead and let's actually use it inside of our list options. So let's find list options. As you can see, it's located in platform. dashboard, board, board ID underscore components, we have list options here. And let's go ahead and let's import everything we need here. So we need the import use action from hooks use action. And we also need the delete list from actions delete list. So make sure you have use action and delete list here. Now let's go ahead outside of the return function and let's call the execute. and let's remap it to execute delete because we're gonna have multiple executes here in this component. Let's call the use action here. Let's pass in the delete list and let's add some callbacks. So on success, we're gonna get the data and let's go ahead and call toast, which we can import from Sonor. So make sure you have the toast, which I will add here. There we go. So let's call post.success and let's go ahead and open back text and write list, data.title, deleted, like that. And let's add on error. So in case we have a server error here, let's do post.error and log that error. Perfect. And now let's go ahead and let's create a function const on delete to get the form data, which is a type of form data. Let's get the ID to be form data get ID as string, copy and paste this and replace it with board ID for the second one. And then execute delete ID and board ID. great and now let's go ahead and let's add this on delete as the form action for our last form here which says delete this list so in this form give it an option action on delete and let's now try it out here so I'm going to refresh I'll pick this last list I will click delete this list there we go it's pending and it says list has been deleted perfect one thing that can happen though is the that this popover stays open after we delete something. I've seen it happen a couple of times and it will certainly be visible once we do the copy list. So I already wanna prepare for that by creating a ref for the close button and then programmatically calling it. So let's go ahead and add const close ref to be use ref from React, element ref, again, from React button and give it default value of null. So let me just confirm that you have this imports, use ref and element ref from React, and I will import them right here. So element ref and use ref. Now let's go ahead and assign the close ref here to the popover close component where we provided the as child prop and we have the button with the X icon inside. And let's go ahead and give it a ref, close ref. And then inside of our onSuccess right here, we can call the closeRef.current?click. And right now, I think nothing should change. Everything should still work as we expect. But we just ensure that that popover always gets closed. Great. What I want to do next is create the server action for copying a specific list. so we can just copy this delete list server action which we just created so let's go into actions delete list and let's copy it and let's rename it to copy list like that let's first resolve the schema which we need so that actually stays exactly the same but let's just rename it to copy list like that now let's go inside of our types and let's fix the schema so it is copy list and let's properly assign it for our input type here. The return type is correct because we are gonna be expecting a list. And now let's go inside of the index.ts here and let's change the copy list here as well. Go all the way to the bottom and place it here and rename the function to copy list. Great, and now let's go ahead and actually copy the list. So this can of course stay the same. We are also extracting the ID and board ID. We are defining the list here. And now we're just going to do things slightly different in the try block. So first, I want to find the exact list we are trying to copy. So const list to copy is await db list find unique where ID is matching, board ID is matching, and the board relation has the matching organization ID as our current user. outside of the where object let's also add include cards true because when we copy a list we also want to copy all the cards right so let's go ahead and check if there is no list to copy in that case return an error list not found because we cannot proceed further and then let's go ahead and let's see which one is the last list in this board because when we copy a list we need to give it a proper order position. So let's write const last list to be await db list find first, where we have a matching board ID, order by the order property in this sending mode. And let's just select the order. That is all we need. And make sure you put order true. great and now let's write const new order to be last list question mark last list dot order plus one or just one and now we can finally create a new list so list is equal to await db list create data board id is list to copy dot board id title is going to be open back this the original title from list to copy dot title but we're going to append a little copy annotation so we know it's copied and order is going to be the new order and then we're going to also create all of the cards So let's go ahead and open the cards object, create many here and pass in the data to be list to copy.cards.map. Get the individual card from here. Open an immediate object and return the title to be card.title. Let's also add a description to be card.description and order to be card.order. Great. And as you can see, the object fully matches what we expect. And let's also return include cards true at the end. Great. And now let's go ahead. And inside of this, instead of failed to delete, we're going to say failed to copy. Perfect. And the revalid path is correct. And the return data is correct as well. Great. Now let's go back inside of our list options component where we just added the execute to delete and we can just copy and paste the entire thing here. And this time let's rename the execute to be execute copy. And let's use the copy list from at slash actions copy list, which we just created. So I'm just gonna move it here alongside the delete list. So make sure you have the copy list imported. And now let's go ahead and let's write list data.title copied. And we can leave the close ref to be closed. On error should yield on error. That is all right. And now as we did with on delete, let's copy and paste it here. And let's just call on copy this time. We also extract the ID and board ID. And let's just call the proper execution. So execute copy. Great. And now let's go ahead and assign this on copy to this first form where we have the copy list text. So give it an action on copy. Perfect. And now let's try this out. So I'm going to refresh my page. I will create an original list here. And then I going to go ahead and click copy list And let see if that is working And it is working Perfect Exactly what we wanted Great, great job. What we're going to do next is we're going to create a form to create cards or tasks in an individual list. And then we're finally going to go and learn how to reorder these using drag and drop. so now let's go ahead and let's create the functionality to create cards inside of lists so for that i want to go back inside of the list item component so inside of this underscore components in the board id folder find the list item and inside of here first thing i want to do is I want to define the is editing and set is editing to be from use state with a default value of false and make sure you import use state from react. Then let's go ahead and let's add const text area form, sorry, ref to be use ref from react as well as element ref from react and give it a type of text area. And let's give it a default value of null. So just make sure you have element ref and use ref imported. And let me just separate those two like that. And now let's create our functions. So const disable editing is very simply going to turn the set is editing to false. And now let's go ahead and define const enable editing, which is going to be set is editing true and also set timeout here. And now let's just focus on the text area ref. So text area ref current focus like this. Great. And now inside of our list header, we can now add on add card here to be enable editing. Because remember, we have another action inside of our list options, which is used to add a new card. So that's why we are defining this states inside of the list item rather than a new component, which we are going to create here, which will be called card form. So now we have to add this props inside of the list header. So let's go ahead and define on add card to be a void like this. And then let's use that prop. Let's also extract it here from the props alongside data. And let's find the list options where we, if I remember correctly, passed an empty function for add a card. Well, now it's going to be add a card. There we go. So we can go back inside of the list item now. And now outside, sorry, inside of this div, just below the list header, let's add a card form, which we don't yet have. So when we save, we're going to get an error. And let's already prepare here by giving it a ref of text area ref. Let's give it is editing prop to be is editing, enable editing to be enable editing, disable editing to be disable editing. And let's also pass in list ID, which is going to be data.id. like that great and now let's go inside of here and create a new component card-form.psx let's mark it as use client and export const card form and return a div card form and now let's just quickly create the interface card form props which is going to accept the list ID, which is a string, enable editing, which is just a void, disable editing, which is exactly the same, and is editing, which is a boolean. And let's go ahead and assign those props, so card form props, and let's extract list ID, enable editing, disable editing, and is editing here. let's go back inside of the list item and now we can import card form from dot slash card form the same way we did with a list header here and you should no longer be having any errors and here underneath the header you should now have a text which says card form so let's go ahead and modify the card form so it actually looks like something and one thing that we forgot to do is the ref forwarding here. So let's go ahead and change this a bit. So I'm going to remove this prop assignment here. And instead, I'm going to mark this entire thing as forward ref, which we can import from React. And let's open another brackets here. And let's end the brackets here at the bottom. And then let's open pointy brackets for the forward ref. And let's give it HTML text area element and comma, let's give them card form props. And now let's go ahead and inside of this, well, to get rid of these errors, first thing let's do is card form dot display name to be card form, like that. And now alongside this props here, add a comma and exclude the ref itself, like that. Perfect. So now what I wanna do is render, well a button here to say add a card so let's give this div a class name of padding top and pay x2 let's open a button from add slash components ui button so let me just separate those imports here inside of the button let's write add a card let's go ahead and close the button and let's also add a plus icon from lucid react so make sure you have the plus icon from lucid react let's give this plus icon a class name of h-4 w-4 and margin right off too let's give this button on click to be enable editing let's give it a class name of h-auto px2 py to 1.5 w full justify start text muted foreground and text small let's give it a size of small and a variant of ghost like that and there we go now we have a nice little button here which says add a card and it actually switches the state from is editing false to is editing true but right now we haven't created any functionality when that happens so what I want to do next is I want to create a reusable component called a form text area so let's go ahead and do that I'm gonna go inside I'm going to close everything, in fact, and I will go inside of my components folder. Let me just find it here and create a new file. Actually, let's go inside of the form and create form-textarea.bsx. Let's mark this as use client. And let's go ahead and write an interface form textarea props, which is an ID, an optional a label, a placeholder, a required prop, a disabled prop, errors, which is going to be a record, string, string array, or undefined. Let's go ahead and give it a class name, which is a string, on blur, which is going to be an optional void, and on click, which is going to be an optional void as well. and on key down, which is going to be an optional keyboard event handler from React. So make sure you import that, which is going to work on HTML text area element or undefined. And let's give it a default value, which is an optional string. So a lot of props for this one. And now let's go ahead and write export const form text area to be forward ref, which you can import from React as well. And let's immediately add form text. And let's actually call it form text area with a lowercase a like this. So form text area dot display name is going to be form text area. Now let's go inside the forward ref. Let's go ahead and open parentheses. And let's go here and let's give it an HTML text area element and form text area props like that. So basically, this two, let me go ahead and try and collapse those or perhaps I can do it. So basically, these two elements, HTML text area element and form text area props, which we created. Now go ahead and open parentheses again. and immediately let's go ahead and destructure the id label placeholder required disabled errors on blur on click and let's not make a typo in the on blur here besides on click we're going to have on key down class name and default value now outside of this props let's extract the ref and then before we end this parenthesis open an arrow function like this and go ahead and return a div and that should get rid of your errors so make sure you do forward ref assign the proper type to it open parenthesis and then open another parenthesis which represent the props and inside you can immediately destructure the id label placeholder all of that stuff and after that add a comma and add a ref and then open an arrow function and just make sure you have an additional parenthesis here to close the forward ref which we opened great let's give this div a class name of space y2 and w dash full and now let's add a new div with a class name space y1 and w dash full as well and inside if we have the label we're going to go ahead and render the label from dot dot slash UI label. And I'm just going to replace that to use slash components. Let me separate it. All right. Let's go ahead and close the label here. And inside, let's render the label like this. And let's also give this an else to be null. Let's go ahead and give this label an HTML4 element to be ID plus name to be text extra small font semi-bold and text neutral.\n700, like that. And now let's go ahead inside of the chat CN, sorry, inside of the terminal. And I'm just going to go ahead and add npx chat CN UI latest ad text area like this. Let's wait a second for this to install. Perfect. And now just below this label here, go ahead and enter the text area from dot dot slash UI text area, which again, I'm going to replace to use add slash components like this. And inside of this text area, let's go ahead and assign all the props we need. So on key down is going to be on key down. On blur is going to be on blur. On click is going to be on click. Ref is going to be a ref, which we forward. Then we're going to have required prop, which is required. We're going to have the placeholder, which is placeholder. We're going to have name, which is ID. We're going to have ID, which is ID. We're going to have the disabled prop, which is going to be the disabled prop. And we're going to have class name, which is going to be dynamic. So go ahead and import CN from at slash lib slash utils. And inside of here, go ahead and open parenthesis. And first, let's write the default classes, which are going to be resize-none, focus-visible ring-0, focus-visible ring-offset-0 again. Then we're going to have a separate ring-0, focus ring-0 and outline-none. And we're going to have shadow small like that. And now add a comma and paste in the class name prop like that. And let's also add area dash describe the Y to be open backticks the ID dash error. So we have the accessibility setup and let's give it a default value of default value. Perfect. And outside of this div, go ahead and render the form errors, which you can import from dot slash form errors. So we already have that component like this. and let's go ahead and let's pass in the id to be id and errors to be errors great and now one more thing that I want to do is I want to use the use form status hook so it's disabled while the form is pending so extract pending from use form status which you can import from react dom Let me just move it here to the top like that. And then let's use this pending here in the disabled prop. So we're going to use either pending or disabled like that. Perfect. So now that we have our form text area set up, we can head back inside of our card form component, which we started creating where we added this add a card button and a plus sign. So that's again located in app, platform, dashboard, board, board id components card dash form right here so now let's go ahead and modify this so that if is editing in that case go ahead and return a form element which is going to have a class name of m-1 pa-0.5 px-1 and space y4 and inside render the form text area from add slash components form text area which we just created and let's go ahead and let's give it an id of title let's give it an on key down for now to just be an empty arrow function. Let's give it a ref of ref, which we are forwarding. Let's give it a placeholder of enter a title for this card. And let's go ahead and let's give it a class name. Well, we actually, I don't think we need to do this class name. Instead, let's just pass in the errors to be errors. And we don't have the errors yet. So how about we don't give them just yet. And looks like we have a missing on click property. So let's go back inside the form text area. And let's just ensure that on click is optional. So just add a little question mark here, my apologies. And there we go. Now you should have no errors here. Perfect. Below this form text area, add an input, which is going to be hidden. the id of this input is going to be a list id the name is going to be list id and the value is going to be list id which we have in the props here and now what i want to do is go below this input and open up a div with the class name of flex items center and gap x1 and let's go ahead and add a form submit option, which you can import from as slash components form, form submit as I did right here. And let's write add card. And below that, go ahead and add a button component, which we already have imported, if I'm not mistaken. That's right, we do. All right. And inside of this button component, render the X from Lucid React. So just make sure you have X alongside plus in your lucid react imports here and give it a class name of h-5 and w-5 and go ahead and give this button and on click disable editing a size of small and a variant of ghost like this and let's see what we come up with here so now when i click on add a card There we go. I should have a beautiful text area here, which says enter a title for this card. And we can close it from here as well. And when we open, it is immediately focused. Perfect. So what we have to do now is we have to create an action, which is going to create the card. So as always, let's copy an existing server action for this. So for example, let's do create list and let's paste it here. Let's rename it to create card. Let's go inside of the schema first and let's rename the schema to create the card like this. And what we expect inside of the create card here is the title, which can, well, the rules can stay the same. We expect the board ID, but we also expect the list ID like this. Now let's go inside of types.ds and let's change this to create card from the schema. And let's assign that to our input type. And our return type should work with card now. So replace that as well. And then go inside of index.ds and let's do the same thing. So create card from the schema and we scroll all the way down. create safe action now uses create card and the function name should be create card. Great. And now inside of this handler, let's see what we have to do. So the authorization can stay the same. From the data, we destructure the title, the board ID, but also the list ID. And we are no longer working with the list. Instead, we are working with the card. So inside of the try block, let's go ahead and remove everything here. So make sure the try block is empty. And let's go ahead and first let's attempt to fetch the list where we are trying to insert this card. So const list is await db list find unique where id is list id and board uses the same organization id as the currently logged in user. If there is no list, return an error, which says list not found. Great. So that is our protection. And now let's go ahead and let's get the last card inside of this list. So we know what order to give this new card, even if it is the first one, we need to do that check. So const last card, wait, DB card, find first, where we have a matching list ID and order by order descending and select order true. And now let's define the new order to be last card. If we have done, it's going to be last card order plus one. Otherwise, it's the first card. So we can just put the order to be one. And now finally, let's define that the card is await db card create. Let's give it the data in which where we will define the title, the list ID, and the order to be new order. Perfect. So that is it. The error can stay the same. The revalidation is correct. And the return returns the card and not the list. And that should be it. Perfect. We can now go back inside of our card form component. So inside of the board ID underscore components, we have card form. It's alongside all of those list header, list items, all of those stuff we just worked on. So we just finished this button, which triggers the is editing and changes it to a form. So now let's go ahead and let's import everything we need to trigger the action. So we need the use action hook from hooks use action and we need the create card from actions create card. And let's see if that's going to be it. I would also like to import use ref from react as well as element ref and let's also import keyboard event handler like that. Great. And oh, I already started this React. So yeah, let's not have two imports. Let's just move the forward ref to be here. So no need to have multiple imports of exactly the same thing. In fact, I think it will throw an error. Okay, and I think this should be enough for now. Now let's go ahead and just also add use params from next slash navigation like that. And let's go ahead here inside of the card form. And let's add const params to be use params, which we imported const form ref to be use ref element ref of form By default it going to be null and let write const use action create a card And let go ahead and execute and extract execute and field errors. Great. And now let's write const on key down, which is event keyboard event. If event dot key is escape, in that case, we're going to disable editing. All right, so we have that done. Now let's go ahead and let's add the use on click outside, which you can import from use hooks DS. I'm just going to move it right here. So import use on click outside and also import use event listener here. So we're just doing some user experience features now, right? When they click escape, when they blur, when they click enter, when they click escape, all of those things. So inside of the use on click outside, we're going to pass in the form ref and we're going to trigger disable editing. then we're going to write use event listener here which we imported to listen to key down listener and then we're going to trigger the on key down and now let's create const on text area key down to be a type of keyboard event handler which handles the HTML text area element. It accepts the event prop and opens an arrow function. And let's check if event.key is enter. And if we did not press the shift key at the same time, in that case, prevent default. And instead, let's go ahead and request submit for the form. So we're going to consider if a user presses enter inside of the text area, which by default just opens a new line, we're going to submit this form because that's what Trello does, except if they are holding shift. In that case, they're going to go into a new line. And finally, let's create const on submit to be form data, which is a type of form data. Let's extract the title to be form data gets title as string. Let's copy this two times. Let's replace the second one to be a list ID and the third one to be board ID. Let's go ahead and let's call the execute prop and let's give it a title list ID and board ID. Perfect. Let's go ahead and let's assign the ref to this form to be form ref and let's give it an action on submit. Great. And now we also have to pass this on text area key down inside of the form text area. So go inside of the if is editing clause, find the form text area and replace this empty arrow function with on text area key down. And let's also give it errors to be field errors, which we now have because we added this function execute and field errors. And now let's go ahead and let's import toast from Sonar package. And let's go ahead and add some callbacks. So on success is going to get the data. And we can call toast.success, open backticks, card, open annotations, data.title, created like this. And let's do form ref dot current reset. So it clears the form. And let's add on error here to get the error and cause toast error with the contents of the error. Perfect. So I think this should be it now. Since we don't have any UI to display the cards, I'm going to go ahead and run npx Prisma Studio here. So it opens on localhost 5555. So let me just prepare that here. I'm going to refresh this. I'm going to paste the Prisma Studio here. And let me focus on the card collection here. Right now, as you can see, there are no rows inside of my table. So I have opened the card rows. And let's go ahead and try this out. So I'm going to go and write test. I'm going to click add. and it looks like something is not working as it should. Let's go ahead and debug why. So if I, let's see, already when I open, there seems to be an error. All right, let's see why it is not working. So first thing I'm gonna do is I'm gonna confirm that this onSubmit is actually having all the values. is a title, list ID and board ID. So let's try that out now. So I'm going to open the console here. And let me just focus. All right. Let's write test here. And when we click test, oh, the board ID is null. Oh, all right. So we forgot to pass the board ID. That's where the function is not working here. So let's go ahead and let's pass the board ID. So we have to do that, I believe. I believe we can actually do it inside of the on submit here because yeah, we can use the params here. So we added this use params, but we're not using it, but we needed to extract the current board ID. So we can either add another hidden input field or we can use the params.board ID here directly and also write it as a string like this. And let's see if that's going to improve this when I write test here and click add a card. There we go, card test has been created. Let's try one more time and there we go. The form is cleared and I have a notification that card has been created. Let's check my Prisma Studio now and I should have two records and I do and they have the correct order. They have no description and they have a relation with the list. Great, great job. So you finished creating cards. What we have to do next is we have to iterate over the cards and actually show them in individual list. So rendering the cards is actually quite simple because we already have them. Because if you remember our page.tsx, we also include the cards when we load the lists. So inside of the list container here, when we pass the data to the list item, we already have access to all the cards. So let's go just above this card form here. And let's add an ordered list element. So this is the list item component, make sure you are in list item here. And just below the list header and above the card form, go ahead and add an ordered list. And let's give it a class name, which is going to be dynamic. So import CN from at slash lib slash utils. And let's give it some default classes, which are going to be MX1, PX1, PY0.5, flex, flex-call, and gapY2. And then add a comma, And let's add a dynamic class, which is going to be if data.cards.length is zero. In that case, we're going to have is larger than zero. In that case, we're going to add a little space from the top. Otherwise, we're not going to add any space from the top. Perfect. And now let's go inside of this ordered list. And let's do data.cards.map. Let's get the individual card and the index. and let's go ahead and render the card item component, which we don't yet have, but we're gonna create it in a second. Let's pass in the index, which is an index, key, which is card.id, and data, which is the card itself. Perfect. So inside of this very same, so let me close everything but this. All right. So inside of this folder where we have list header, list item, basically everything, and our newly created card form. Now let's create cardItem.tsx. Let's go ahead and let's mark this as useClient and let's export const cardItem here and return a div cardItem. And now let's just quickly create an interface cardItem props to accept the data, which is the card from Prisma Client and an index, which is a number. and now we can go ahead and destructure this props and add them here so data and index like that and let's go back inside of our list item and now we can import the card item from dot slash card item as I did right here and when you save you should be having no errors and now let's just go ahead and style this so instead of rendering card item we're going to render data.title and let's go ahead and give this a class name of truncate border2 border transparent hover border black py2 px3 text small bg white rounded md and shadow sm and if i take a look now there we go we have our cards here perfect and if I create a new one here there we go it is immediately visible in my list perfect what we have to do next is finally implement our drag and drop so we're gonna do that before we do opening a card in a model right we're gonna do that later and just one more thing that I want to do before we wrap up is give this div a roll of a button like that so now when you try you can see that you have a like a clickable element right and you're going to be able to drag it or you're going to be able to click on it right now none of those works but what's cool is that we revalidate this path and we do router.refresh so the server components are all up to date and we have the newest data here. Perfect. Great, great job. So now that we have all the elements we need we are ready to install the drag and drop package and implement the functionality So head inside of your terminal and let go ahead and run npm install at hello slash dnd. So this is the same as react-beautiful-dnd, but the old one has been deprecated and this one is up to date. Great. So make sure you have your project running. And let's go ahead inside of our list container component, which is in the platform, dashboard, board components. And in here we have the list container. And let's go ahead and let's import everything you need from the DND. So I'm going to import drag drop context and droppable from at hello pangia DND. like that. And now let's go ahead and let's drag this entire thing inside of drag drop context. Like that. And let's go ahead and let's add an on drag end, which for now is just going to be an empty arrow function. Now let's go ahead and wrap this ordered list element inside of a droppable component. So droppable here, wrap the entire ordered list inside of that, and go ahead and give it a droppable ID of lists, and give it a type of list, and direction of horizontal. And now go inside and destructure the provided prop, and wrap the ordered list again inside of this new element. And now that we have the provided prop, we can go ahead and pass it to the ordered list here. So let's go ahead above the class name and pass in provided.droppableProps. So spread them like I did and give it a ref of provided.innerRef like this. And then go ahead above the list form and render provided.placeholder like that. Now that we implemented this drag and drop context and droppable in the list container, we have to do the same thing for the list item. So let's go inside of this component, list item, and let's import everything we need from drag and drop. So that's gonna be draggable and droppable from DND like this. and let's go ahead and let's wrap the entire component inside of draggable. So draggable all the way to the end of the list element and let's give it a draggable id of data.id and an index of index. And now we have to destructure the provided as we did. So open curly brackets, open normal brackets, get the provided and wrap the entire list inside of that element. Go ahead and indent this a bit. And now let's go ahead and pass everything we need to the list item here. So go ahead and spread provided dot draggable props and ref is going to be provided dot inner ref like this. And now inside of this div, which wraps the list header, let's go ahead and give it another property, which is going to be a spread of provided.draghandle props. So when the user grabs on this div, they're going to activate the drag and drop. Perfect. And now what we have to do is go below this list header here and wrap the ordered list of cards into droppable. So just go ahead and wrap that. Let's go ahead and give Droppable the droppable ID of data.id and let's give it a type of card. And then we have to destructure the element provided and wrap the entire ordered list inside of that. now that we have the provided right here let's give this ordered list a ref of provided dot inner ref let's just not misspell inner ref and let's go ahead and spread provided dot droppable props and now just at the end of this ordered list go ahead and render provided dot placeholder like that and i think that already we should be able to at least visually see the drag and drop as you can see we can drag and drop of course it's not working because when it finishes the on drag end we don't actually do anything and one more thing that we have to do to enable the cards from being draggable and droppable because right now if I grab on the card, you can see that the entire list gets dragged. We have to go inside of the individual card item, and let's go ahead and again import draggable from DND. So import draggable from DND like this, and we have to wrap the entire div inside of draggable. So let's go ahead and do that. Let's indent the element. let's give the draggable id to be data.id and an index of index and then let's go ahead and destructure the provided element and wrap the entire div inside of that and then we can give this div a spread of provided draggable props and provided drag handle props like that. And let's also give it a ref of provided inner ref like this. And now I believe we should be able to drag the cards as well. There we go. We can now drag cards or we can grab on the list and then we can grab list. But again, it's not working right now. You can see that it resets its position after we let go. That's because we haven't done anything inside of our on drag end. But as you can see, still, it works. If I try to rename something, I can click on the options here. So all of those things are working. Perfect. And now let's actually implement the functionality first to locally or optimistically mutate the state of orders. And then we're going to do the request on the backend. So we have to go back inside of the list container. So let me just expand this. And let's go inside of list container.tsx inside of this board ID components here. And now we have to modify this on drag function. Before we do that, I just want to create a generic reorder function, which we are going to reuse. So let's do function reorder, which is accepting a generic T, has a list as a prop, which is an array of the generic. It has a start index, which is a number and an end index, which is a number as well. Let's go ahead and open this function. Let's create the result, which is going to be an array from the list we provide. And then let's destructure the removed from result splice start index and one. And now let's do result dot splice and index zero and removed and return the result. So this is going to reorder them by indexes. And now we have to create a very big function on drag end. So we're going to add comments inside. So it's easier to follow exactly what it does. So let's go ahead and let's create const on drag end function. And let's replace that with this empty function here. So now on drag end inside of our drag drop context should be handling this. So first, let's go ahead and extract everything we need from our prop result, which let's give it a type of any. Let's go ahead and let's destructure the destination. Let's destructure the source and the type. First, we're going to check if we can even do this. So if it happens that we don't have destination, we can already return the function. There is nothing else we can do with this. Now let's go ahead and let's write a comment, dropped in the same position, or maybe if dropped in the same position. So if a user picks up a card or a list and drops them in the same position, we don't have to do anything. And we can check that with an if clause, which is going to say if destination.droppableId is equal to source droppableId. And if destination.index is equal to source.index. In that case, we can break the function, meaning that there's nothing we have to do if the user picks up an item and drops them in the very same place. And now we're going to create what happens if user is moving a list. So let's write a comment, user moves a list. Let's write if type is a list so we know that because we gave this droppable a type of list the same way that in a list item we have a droppable element right here with the type of card so we know what exactly we're dropping so go back inside of the list container and if the user moves the list we're going to do the following. First, we're going to reorder the items using our generic reorder function. Inside, we're going to pass the ordered data, which we have from right here. So that's our initial data. We're passing the ordered data, then the source.index, and then destination.index. And then let's do .map. Let's get the item and the index. and let go ahead and return an immediate object where we spread the existing item but change the order to the new order using the index And all we have to do then is set ordered data to be the items like that And I'm going to write a to-do comment, well, trigger server action. So we have to create a server action, which is also going to update this on the backend. For now, we're only going to do the optimistic mutation here on the front end. So I think we can already try it out. If I drag and drop the original, there we go. The original now stays exactly where we place it. Of course, if we refresh, it goes back to its original position. That's because we don't do the server action for this yet. Great, so now we have to do the same thing, but for the card, right? So let's go outside of this if here and write a comment, user moves a card. In that case, let's do if type is, card, and let me try and expand the code even more, all right? So if type is card, let's go ahead and just create new ordered data to be a copy of the existing ordered data so we can mutate on this. And let's go ahead and first get the source and destination list. So const source list is new data, new order data dot find. And we search through all the lists. We get the list ID and we compare it to source droppable ID. So let me zoom out so you can see that in one line. We get the source list. Sorry, we attempt to get the store list, source list using the new order data. And we use the find method to get the list. And we compare the existing list ID with source droppable ID. and now let's go ahead and create the destination list so const desk list is going to be again new ordered data dot find we get the individual list and we compare the list id this time with destination dot droppable id like that so let me zoom out so you can see both of those so source list is comparing the list id from the new order data with the drop with the source and the destination list is with destination droppable ID. All of those props you can find, you can see that we have the droppable ID lists here and the list item we have different droppable IDs to be the data.id. All right and now let's go ahead and let's check in case that source list or destination list wasn't found inside of this iteration using find. So if we don't have the source list or if we don't have the destination list. There's nothing we can do. So let's just break the function. And now we have to handle the cases if the cards property doesn't exist on a specific list, right? If the list is empty, it can happen that it doesn't have cards. So check if cards exists on the source list. We're going to do if there is no source list.cards. In that case, let's go ahead and manually add them. So source list dot cards is an empty array. Now let's go ahead and check if cards exists on the destination list. So in here, if the exclamation point destination list dot cards manually add cards to be an empty array like that. Great. So now we can work with that. And now we have to handle the case if the user moves the card within the same list and if they move the card in a different list because we have to modify their list ID in that case so we can safely send that to the backend. So first let's do moving the card in the same list like this. So we can do that by checking if source droppable ID is identical to destination droppable ID. Let's go ahead and let's reorder our cards using the generic reorder function. So const reordered cards are reorder function. And let's pass in the source list.cards, source.index, and destination.index. And now let's change the order of each card. reordered cards dot for each we have the car and the index and let's write card dot order to be the new index and let's just not misspell index all right and then let's assign that to the source list cards so source list cards is now reordered cards with the new indexes and now let's do set ordered data to be the new ordered data like that. And let's go ahead and add a comment here to do. And we're going to write, well, trigger server action. So we can actually save this. So now, if I'm not mistaken, we should be able to locally keep the state of cards. There we go. So SDF is now on top and I can move it all the way to the back and it doesn't reset. One to three is here. I can move it all the way to the back and it doesn't reset. But if I refresh, of course, it goes back because we have to do the server action. Great. So now we have this and now we have to handle the case if the user moves the card to another list. So let's go ahead and add a little comment here. user moves the card to another list. And let's open an else statement here, because this is the end of our if clause, as you can see right here. So meaning that if the droppable ID is different from the destination's droppable ID, it means that we are using a new list to drop this in. So let's go ahead and let's remove card from the source list. So const moved card is going to be source list.cards.splice source.index and one like that and then let's go ahead and assign the new list id to the new card or actually moved card so we're going to do that using moved card.list ID is going to be destination dot droppable ID. And now let's add the card to the destination list. So we successfully just changed the card ID, cards list ID. And now let's use the destination list dot cards dot splice so we can insert it in between the destination index and moved card like that and now we have to change the order of the cards as well because remember when we drop it inside another list we can also reorder the entire list we can put it on top or in the middle or on the bottom so let's write source list dot cards dot for each get the card the index and card.order is the index. Whoops, let me just go. I somehow switched my components. So back in the list container, this is where we were. Source list.cards for each and apply the new index as the card order. Great. And now we have to update the order for each card in the destination list. so let's go ahead and do destination list.cards for each, get the card and the index again, and let's just write card.order is the new index, and now we can do set ordered data to be new ordered data, and let's write to do trigger server action and now this should be locally working as well so let's try it out if i drag one two three in the middle of this it should stay and it does if i move this one as the first in this one it works as well perfect so you can try with as many combinations as you want. You can try in between the same list and everything seems to be working. Perfect. So we are officially ready to now do this on the back end as well because locally it works. Again if you're having any problems you can always visit my original github and look at the exact code from here. Perfect. So now we have to create two server actions. One is to update the card order and the other one is to update the list order. So let's go ahead and do that. So as always, let's copy an existing server action. So I'm gonna close everything going to actions and I'm going to copy the create list and let's rename it to update list order like this. And first let's go ahead and modify the schema. So let's change this to update list order. And it's no longer going to be having a title. So it needs a word ID and it accepts the item, which is z.array of z.object. And inside we expect the ID to be z.string, the title to be z.string as well. We expect the order to be z.number, the created add to be z.date, and the updated add to be z.date as well. All right, now let's go inside of types and let's use the update list order schema as our input type. And instead of a single list, we're going to be expecting an array of lists at the end. So just make sure you add an array here. And now let's go inside of the index right here and let's modify the update list order here as well. Let's move it all the way down to update list order and let's change this to update list order. Like that. Perfect. Now let's go inside of the actual handler. The authentication can stay the same and let's go ahead and modify this. So instead of title, we are now extracting the items and it's not going to be list, it's going to be lists like this. Let's go ahead and clear everything in\nside of our try function. So make sure the try function is empty. And let's go ahead and create a transaction. So const transaction is items.map, get the individual list from here. And let's do db.list.update, where id is list.id and board matches the organization id. and let's pass in the data to be order list.order like that so I just want to bring it to your attention that I did not add any curly brackets here so this goes directly after the arrow function like this I just moved it to a new line so I directly returned this function so make sure you don't put any curly brackets otherwise I think you have to return this function great and now let's go ahead and write lists to be await vb dot dollar sign transaction and passing the transaction inside like that and in the error let's go ahead and let's say fail to reorder and then we're going to go ahead and return the lists there we go and now we can use this inside of our list container So let's go back inside of the list container, which where we just wrote this big on drag and function. And let's go ahead and import the use action from hooks use action. And let's also import update list order from actions update list order. And now inside of here, I'm going to go ahead and I'm going to get that, well, action. So const use action update list order and let's get the execute. So execute is going to be execute update list order. So we know exactly which one of those it is. Let's go ahead and open the callbacks. And on success, we are not going to work with data. Instead, we're just going to call the toast from Sonar. So just make sure you add this import. And we're going to say toast.success list reordered. And if we have an error, we're going to go ahead and log that error in a toast. And now we can use the execute list order once we finish our list reordering. So go inside of on drag end. And in here, we have a clause. if user moves a list. And in here we have set order data for this new items. And then we have a comment to do trigger server action. Let's remove the comment. Let's call execute update list order. Let's pass in the items and the board ID like this. And now we should be able to move the original in the place of this one and it should be saved in the database. So if I drag and drop it like this, there we go. List has been reordered. And if I refresh, the original stays in this place where we move it. Let's try that one more time. List has been reordered. If I refresh, the original stays in the same place. Perfect. So now we have to create the same thing, but for updating the card order. So let's go ahead and copy the existing update list order because it's the closest to what we need. So I'm just going to close this stuff. I'm just going to leave the list container open so we can quickly move back to it. Let's find the update list order, copy it and paste it inside of the actions and rename it to update card order. And inside, focus on the schema first like this. So let's rename this to update card order like that. And we expect the board, we don't expect the board ID. We expect the list ID like this. And then let's go ahead and confirm the object. So we need an ID title order, but we also need the list ID, which is Z dot string like this. So let's go ahead now inside of types and let's import update card order and paste it here. And we are no longer working with lists instead of we're working with cards. So import the card and place it here. So it's going to be an array of cards in our return type. And now let's go inside of the index and let's change this schema to be import card order. Let's go all the way down and change this to import card order as well. And the function should be update card order as well. perfect. So now let's go ahead and modify this index here. So this authorization can stay the same and we're no longer extracting the board ID. Instead, we are extracting the list ID and the let is going to be updated cards like this. So let's go ahead and let's remove everything inside of the try block and let's go ahead and write db.card.update where id is card.id oh my apologies so we have to run a transaction actually so const transaction is items.map and then we have the individual card and again don't open the curly brackets you know just immediately start writing a function db card update where id is card.id list is board organizational id so we ensure no foul place can be done and then we pass in the data which is the new order from card.order and we pass in the list ID, which is card list ID like that. Perfect. So we have our transaction and now let's go ahead and write const updated cards to be await DB, sorry, not const, but we just modify the updated cards from here. Await DB dot open the transaction and pass in the transaction inside. Great. And let's go ahead. Okay, this can stay the same and return the data to be updated cards. And I just realized that we might actually need the board ID from here as well. So let's go back inside of our schema here and let's request board ID to be z.string as well. And now let's go here inside of the index and let's destructure the board ID from it as well. And let's go ahead. And yeah, this can stay the same. Perfect. It actually seems to me like we don't need this list ID at all. So let's remove the list ID and let's also remove it from the schema. So we only work with board ID here. All right. And now let's go back inside of the list container. And in the same way, we created the execute update list order, let's create the update card order. So I'm going to go ahead and I'm going to import update card order from update card order, which we just created. So let's just ensure that we did that properly. The name is update card order, correct. Great. So we have the update card order and we can go ahead and we can copy this use action here. So let's just paste it below and let's give the alias to be execute update card order and let's use the update card order like that and let's give the success message to be card reordered and now let's use the execute card order instead of the well list order perfect so let's go ahead and find where we have to do that. So we handled the case if the user moves the list. Now we are here where the user moves a card. So let's go ahead and find the first to do here. So moving the card in the same list. Let's go ahead and change this to do trigger server action to call the execute update card order. And let's pass in the board ID. Do we have the board ID in here? Let me just confirm. We have the board ID in the props. All right, so we can use that. So you can just pass in the board ID, we have it in the props. And let's give it items to be reordered cards. And that should be working. So this is a case if user moves the card in the same list. Let's see if that saves in the database now. So I'm going to move one to three to the bottom of the list. And let's see if we get a success message there we go card reordered and if I refresh one two three is still at the same place perfect but if I try this for example well there's no success message because we don't handle that case so if I refresh it's going to go back to here perfect so reordering in between the same list seems to be working just fine now we have to handle the case if the user drags it in the other one. And we don't have to create any new server action for that one. We can instead just go ahead and find the case. It's right here below it. So where we wrote the execute update card order, we have a comment user moves the card to another list. So scroll here and we have a to do trigger the server action. So let's add execute update card order. Let's give it the board ID to be board ID and let's pass in the new items to be destination list cards like that. Perfect. Let's try it out for the moment of truth now. I'm going to drag and drop one, two, three in the middle of this board. I should be having a notification any second. The card is in the middle. If I refresh, the card stays in the middle and let's try it with this one, moving it to the top let's wait there we go we have a notification i refresh and it's still here let's try if i can now move it in between this seems to be working as well i refresh and it is still here let try with a new card so new card let see if it going to be created It is right here I will immediately move it here and that seems to be working as well I can refresh. Let's try and rotate this items. That seems to be working as well. Let's move the new card to the top here and that seems to be working. Perfect. So I think we just did some good Q&A here. we handled all the cases. Perfect. What we have to do now is that when we click on a specific item or card, it opens a model with more information about it. Great, great job. So now that we've wrapped up our drag and drop, which is working flawlessly, let's go ahead and implement the functionality that when we click on a card, we open a model, which then displays the content of the card. And from then we can rename it, we can add the description and we can also do some actions here. So let's go ahead inside of our hooks folder here and I wanna copy and paste the use mobile sidebar and let's rename it to use card model. So I copied the use mobile sidebar and rename it to this and let's change this mobile sidebar to be a card model like this. So we have the card model store, we have the use card model and card model store here in the create types. And I want to modify it a bit by adding an optional ID here. And whenever we open it, we're going to pass in the ID of the card we want to open. And then let's explicitly add the ID to be undefined in the initial state. And in on open, let's extract the ID and let's go ahead and set the ID. And when we close, let's reset the ID to be undefined. Great. And now what I want to do is I want to actually create the card model. In order to do that, we're going to need to have the dialog components from ShatCN. So let's go ahead and run npx chassian ui latest ad dialogue like this. And let's wait a second for all of this to install. And let's do npm run dev again and just refresh the localhost if you shove down your app. Now let's go inside of the component and create a new folder called models. And inside, let's go ahead and create a new folder card model. and inside create an index.tsx because we're gonna have multiple components inside of this models, of course. So let's go ahead and let's mark this as use client and let's export const card model here. And what I wanna do is I wanna return a dialog from components UI dialog. So just make sure you don't accidentally use from the radix. and then let's render the dialog content from components UI dialog. And inside, I'm just going to say I am a model like this. And let me just add the semicolons here. And now let's connect it to that useCardModel hook. So const useCardModel is useCardModel. And let's actually change this to be cardModel. And now let's go ahead. actually let's not do it like this let's individually get each one so let's do the id use card model state and we pick the state.id let's get the is open to be use card model get the state and do state is open and let's do const on close to be use card model get the state and state on close. And now we can apply those props to the dialog itself. So open is open and on open change is going to trigger on close like this. Now let's create a model provider. So inside of the components folder, create a new folder called providers like that. And inside create a new file model-provider.tsx. Let's mark it as use client and let's export const model provider here and let's return a fragment and inside we're going to add our card model from models card model like this and I'm just going to change this to use components and now I want to go ahead and protect it from hydration errors. So let's give this a use state with the default value of false. And let's go ahead and extract is mounted and set is mounted. And then in here, I'm calling a use effect, which is going to set the is mounted to true. And then I'm adding an if clause that if we are not mounted we just return no. So this ensures that this component is and everything inside is only rendered on the client because use effect can only be rendered on the client so unless this is mounted has been turned to true by the initial mount here it will not be rendered and thus it will not be creating any inconsistencies when it comes to server and the client thus preventing hydration errors. Now let's assign this model provider to our platform layout. So go in the app folder platform layout.tsx and just below the toaster go ahead and add the model provider from components providers model provider. Let me just align my imports a bit. There we go. So just make sure you have that. Now we have to go back inside of card item component. So inside of platform, dashboard, board, board ID components, you have a little card item component. And let's go ahead and use our card model hook. So const card model is use card model. And we can import use card model from at hooks use card model. and let's go ahead and give this button an on click which is an arrow function which calls card model on open and passes in the data id so we know exactly which well card to load so when i click on one now there we go you can see how we have a nice little model here perfect but it seems like this background is just a bit too light for me. So what I want to do is I want to go inside of the dialog itself. Let me close everything and let's go inside of components UI dialog right here. And I want to find the dialog overlay right here. And in here we use BG background 80. I want to use BG black like that. And I think that should look just a bit better when I click on something. There we go. It has a black background now and our model is opened. Perfect. And now what I want to create is a component which is going to display the title of the model of the card. But before we can actually display any information, we have to fetch the information from somewhere. Up until now, we only fetched information using the server component and directly accessing the database. But due to the nature of this drag and drop component, which requires the items to be client components, we're going to have to create an API route. And to call that API route, we're going to be using React 10 stack query. So let's go ahead and let me just close everything here. and let's go inside of our terminal and let's do npm install at 10 stack dash slash react dash query like this. Let's wait for a second for this to install. Let's do npm run dev again and let's go back inside of our providers in our global components here and create a new file query provider.tsx. Let's mark this as use client. And let's go ahead and import use state from react. And let's import query client. And let's import query client provider from add down stack react query and then export const query provider which has children and go ahead and give the children a type of react.react node and inside of here let's assign the query client in a state and let's make it be the new query client so let's just the new query client and then let's just return the query client provider. Let's render the children inside and let's pass in the client to be query client like that. Now let's assign this new query provider back inside of our platform layout. So inside of the app folder, we have the platform and layout right here. And let's go ahead and let's wrap everything inside of our query provider from components providers query provider so make sure you do it from here and including the children as well so what this this is not going to turn everything inside into client components just because we've wrapped this as client because there is a way to render server components like a bunch of our children are inside of client components and you do that by passing them as children and that is not going to change the fact that the children are server components just in case you had that doubt. So let's just refresh the localhost here to confirm that all of this is still working to confirm that our server components are still server components and as you can see they are because this was loaded directly from the database. Perfect. And now what I want to do is I want to create an API route to fetch a specific card. So let's go ahead and let's go inside of the app folder and create a new folder API. And then inside create another folder called cards And inside of that create a new folder with a dynamic ID card ID And instead of page since this is an API we create route.ds. So now let's go ahead and let's export asynchronous function get. Let's get the request, which is a type of request, and we can destructure the params. So let's give them a type of params and get the card ID, which is a string. Just confirm that your card ID exactly matches the folder name card ID. Otherwise, it's not going to be visible here. Go ahead and open a try and catch block and we can resolve the error first. So let's just do return new next response from next slash server. And let's pass in internal error and a status of 500. Inside of the try block, first let's attempt to get the user ID and the organization ID from the out handler. So make sure you import clerk from Next.js. If we don't have user ID or if we don't have organization ID, you can return new next response unauthorized with a status of 401. one. And now let's go ahead and let's import our database from at slash libDB. And in here, let's go and fetch the card. So const card is awaitedDB card find unique, where ID is brands card ID list has a board which has the matching organization ID. So only members of the board can access this and let's include a list but the only thing we're going to need from the list where this card is is the title so write select title true so no need for any additional information and now let's just write return next response dot json card great so now we have our API request ready. Now let's create our reusable fetcher. So I'm going to close everything, go inside of the lib folder and create a new file fetcher.ts. And in here, export const fetcher, which takes in the URL, which is a string, uses the native fetch and passes in the URL, and then transforms the result to JSON. So it's readable for us like this in one line. Great. And now we can go back inside of our components models card model right here. And let's go ahead and let's import the use query from tan stack query. So import use query from tan stack query like this. and let's write const use query and let's define what we expect from it which is card with list which we have defined in our at slash types so let me show you that you should have card with list which is a card from prisma client and it includes a list and let's go ahead now and let's actually define the query key to be card and id and let's do the query function to be a void which calls our fetcher from at slash lib fetcher and passes in backtick slash api slash cards and then pass in the individual id like that and then inside of your const you're gonna have data which we're going to save as card data like this. Perfect. And now let's go ahead inside of here and let's render data, sorry, card data question mark title like this. And let's try it out. So I'm going to refresh this. And when I click on new card, there we go. It says new card. If I click on one two three it shows one two three and you can debug this if something's not working by going inside of your network tab here and let's go to slash cards is oh let's try again all right and you just type in card here and let's click on a card and there we go you can see the exact id that i'm passing and the exact result that i received back perfect so now we can go ahead and we can finally create our header component from here. So let's go ahead and do that. So I want to go and open the browser here where I have my card model and inside I want to create a new file which is going to be named header.dsx and let's go ahead and mark this as use client and let's export const header and let's return a div saying header. Let's go back inside of the index component here and what I want to do is I want to render the header component and I want to pass in the data to the card data and let's import the header component from dot slash header so this one right here and let me just join this imports because they are of the same nature like that. And now let's create the types for the header so we can accept this data. So inside of the header, create an interface header props, which accepts the data, which is card with list, which we have inside of our types. Let's go ahead and give them header props here and destructure the data like that. Perfect. So what I want to do now is I want to go ahead and render this but I kind of want it to be an input by default so we're going to see what I have in mind so let's give it a class name of flex items start gap x three margin bottom of six and w four and now I want to go ahead and add a layout icon from lucid react so just make sure you import that and it is a self-closing tag and let's go ahead and give it a class name of h-5 w-5 margin top 1 and text neutral 700 and now let's create a div here with a class name of w full and let's open up a native form element and let's render the form input from components form form input like this. And inside of it, we have to give it an ID of title. We have to give it a ref of input ref, which we don't yet have. So let's not do that just yet. And let's go ahead and give it, well, first thing I want to do is I want to store the title inside of use state here. So give it the data.title like that. And make sure you import use state from React because we're going to do some optimistic update on that as well. So now we can give this a default value of the title from the state. And let's go ahead and give it a class name of font, semi-bold, text, extra-large, px1, text, neutral, 700, bg, transparent, border, transparent. relative minus left minus 1.5 w sorry width of 95 percent and on focus visible I wanted to have a background of white on focus visible I also wanted to have a border input a margin bottom of 0.5 and truncate like that perfect so now as you can see when I click on a specific card So let's just refresh so we ignore this. Okay, let's try it like this. Let's use data question mark title like that. Or I actually think now, yeah, now nothing's going to be displayed here. All right, let's just continue developing and we will see the results in a moment. So in order to fix that little bug, let's go ahead and let's create a skeleton here. So header.skeleton is a function, header.skeleton, which returns a div with a class name of flex, items start, gap x3, and margin bottom of 6. And inside, we render the skeleton, which we can import from add slash components UI skeleton, as I did right here. and let's go ahead and let's give this skeleton a class name of h-6 w-6 margin top 1 and bg neutral 200 like that and then below that let's open up a div and inside let's render another skeleton. Let's give it a class name of h-24, actually width of 24, height of 6, margin bottom of 1, and bg-neutral 200. Copy and paste this below, and let's give this one a width of 12, a height of 4, and it doesn't need any margin bottom. And now we can use this header skeleton it's always going to have it. And now go back to the index where we have an error because the data is possibly undefined. So let's go ahead and do the following. If we don't have card data, in that case, we're gonna go ahead and render header.skeleton like that. Otherwise, we're gonna go ahead and render the actual header like this. So let's try that out. Now I'm gonna refresh here. And when I click, you can see how for a second have a nice little loading skeleton and then it loads the exact card which I expect. Perfect. Let's head back inside of our header component and let's give it all the necessary stuff it needs to successfully update this. So I'm going to go ahead and I'm going to add the query client from use query client which we are going to use to refresh the data once we successfully update it using the server action. So let me move that to the global imports at the top. We also going to need the params So use params which we can get from next navigation as I did right here Besides this we also going to need to create a ref So let write const input ref to be use ref from react, get the element ref from react as well and give it a type of input. And inside, give it a default value of null. So just make sure that from react, you have the element ref, use ref, and use state. Great. So now we have those. And let's go ahead and just create one more, which is going to be on blur. So const on blur is going to call the input ref dot current dot form request submit. All right. And now let's go ahead and let's give this form input what it needs. So ref is going to be input ref on blur is going to be on blur. And we already gave it the default value. So that's good. And now let's just go ahead and let's create const on submit here with form data, which is a type of form data. and let's console.log formdata.get title. So now, and of course, let's assign this to be the action of this form. So if we try this out already, I'm gonna open my inspect element here. And if I try and change this to something and press enter, there we go. You can see the exact form field, which is going to be submitted to the server action, which we are yet to create. Before we do that, let's go to the end of this form function and let's create a paragraph, which is going to render in list, open a span element, data list title like this. And let's go ahead and give this paragraph a class name of text small and text muted foreground. And let's give this span element a class name of underlined like that. Let's try it out now. I want to refresh and click here there we go you can see it says new card and then where my list is at and you can see how flush this input is it's barely noticeable there are almost no you know flickering and stuff when you click and when you blur perfect so now let's go ahead and let's actually create that server action so let's copy an existing server action, I'm going to go ahead and find update board. That's the closest one. And let's rename it to update card like that. Let's go inside of schema. So we tell it exactly what we expect. So let's change this to be update card. And alongside title, we're also going to need a board ID, which is going to be z.string. We're also going to need a description, which is z.optional. Z dot string. And then we're going to go ahead and give it a required error of description is required. We're going to give it an invalid type error of description is required as well. And let's also give it a minimum value of three, just so we can see those errors. And a message of description is too short. All right. And so we have the board ID, the description, the title, and we have the ID of the card we are trying to update. Perfect. Now let's go inside of the types and let's get the update card from here and let's add it to the type of. Great. And now let's go ahead inside of the index. Let's change the schema to use the update card. Let's go all the way down and add the update card and change the function name to update card as well and now inside we have to modify the handler so this can stay the same and in here we're going to be extracting the following so we will extract the id the board id and the rest of the values we're going to keep in a constant values which we will spread like this and we're not going to be working with a board but with a card so change this left to be a card and then in here let's go ahead and let's simply change the card to be awaitdbcard.update where id matches the id and the list has a board which has the matching organization id and then in the title we're just gonna go ahead and spread the values like that perfect and we can leave this to be failed to update and let's use the board ID here and in the data let's return the card and it seems like I forgot to update my types here yes so go back inside of the types and change from using the board to using an individual card as our return type great and now let's go back inside of the header component inside of models card model so go inside of header here and let's go ahead and let's import the use action from hooks use action and let's import update card from actions update card and now let's go ahead and actually well create this hook right so right here I'm going to call the execute and I'm going to call the use action I'm passing create sorry update card here like that and then inside of this on submit let's go ahead and change this to be a constant title and let's do execute and let's pass in the title but I believe that it also needs a couple of more things here so let's fetch the board id to be params board id as string remember we have the params from use params here. So make sure you have that. All right. And let's pass in the board ID and the ID is going to be data.ib. Like that. Perfect. And let's change this to be as string. And let's also prevent updates if the title matches the current data.title. So we can then just break the function instead. Perfect. So that should now be working. How about we try it out? One more thing that I just want to do is add the callbacks, right? So first, let me just go ahead and let's import the Sonar, sorry, toast from Sonar. And then inside of the use action, let's go ahead and add on success, let's get the data from here. And first thing I want to do is re-invalidate the query. So query client dot invalidate queries. And let's go ahead and pass in the query key to be card and data dot ID like that. And then let's call post dot success and open backticks, renamed to open annotations and render data.title. And set title to be the new data.title. And let's add on error here, error. And inside, well, we can just call the toast.error and pass in the error. So let's try it out now. I'm going to go here. Choose a card. change the name press enter and there we go rename to change the name and it's immediately reflected here and i can also click just outside and then does the exact same thing perfect so now we're going to go ahead and create some additional actions like changing the description copying the card or deleting the card and then we're going to go ahead and learn how to create activity logs. So now I want to create the description component. So let's go ahead and just prepare for that. It's going to be very similar as our header component is. And the first thing I want to do, of course, is just create a little skeleton for it so we can render it conditionally. So go inside of the card model and create a new file description, psx. Let's go ahead and mark this as use client. Let's export const description. We can immediately create an interface for it. Description props. And you already know that we accept the data, which is a type of card with list. Now let's go ahead and assign those props here. Description props. And let's destructure the data. and for now let's just return a div rendering data title or perhaps it would be smarter to render the description even though we don't actually have the description since it is optional so this will be the first time the user can actually add a description and now let's go ahead and let's just create a proper skeleton for this one so I'm going to write description dot skeleton is equal to a function, which is a description skeleton. And let's go ahead and return a div. Let's give this div a class name of flex items start gap x3 and w pull. Inside of this div, let's render our skeleton from add slash components UI skeleton. I will just align it like that. let's give this a class name of h-6 w-6 bg-neutral 200 and now let's create another div here with a class name of w-full let's go ahead and copy the skeleton and put it inside of here and let's modify its class name a bit so I'm going to leave the bg-neutral 200 but I'm going to add the width to be 24, the height to be 6, and margin bottom 2. Let's copy and paste this one. And let's also leave the BG neutral. And let's add W full here. Let's give it a height of 78 pixels. And that should be it. Let's just save this now. And now let's go back inside of the index here. And inside the dialog content, let's create a proper grid, which is going to render this description. So the reason I want to create a\ngrid, because our layout is going to look like this. On this side, we're going to have the description, and then we're going to have the activity log down here. But here in the corner, we're going to have actions to delete this card or to copy it. And on mobile mode, they're going to collapse underneath each other. So for that, I think it's best that we use the grid functionality. So let's go outside of this conditional, which renders the header, and let's add that grid. So give it a class name of grid, grid calls one on mobile device. So they go underneath each other. And on medium device is going to be grid calls four. And on medium devices, they're also going to have a gap of four between another. And now let's give this description, right? Column, a larger call span. So I wanted to take three out of four possible places, which we define here on the, well, medium mode, right? So call span three. And then inside, let's render a div, which is going to use the full width of that column. And now inside, I'm just going to, oh, and let's also add space Y6, because we're going to have multiple items inside. But for now, let's do the same thing with it above. So if we don't have card data, in that case, let's go ahead and render description from dot slash description, which we just created. So just make sure you have all the necessary exports here so you can import it properly. So we're going to render description dot skeleton. Otherwise, we're going to render the description and we're going to pass in the data, which is card data. And now I think we should already be seeing a nice little loading skeleton here. Let's try this again. There we go. You can see how for a second we have a nice little space for the description here. So let's go ahead and style that now inside of this description component right here. So let's give this top div a class name or flex items center. Actually, let's make it items start gap x three and w full. and just as I wrote this I think I remembered when I was watching the previous part of the tutorial that in my header component I made a typo yes I saw this in the video so if you made the same typo I don't think you did but just make sure this says items start right so let's go back in the description now so flex item start gap x3 and full width and now let's add an icon align left from lucid react so just make sure you have this import it is a self-closing tag and let's give it a class name of h5w5 margin top of 0.5 and text neutral 700 below this icon let's open a div which takes in the full width and inside let's add a little paragraph which is going to be the label description. And let's go ahead and give it a class name of font bold, actually semi bold might be better. And text neutral 700 and margin bottom of two. So let me just try that out to see how it looks. There we go. It looks quite nice. So this first skeleton is obviously representing that icon and the description text. And now we're going to add that big box here at the bottom, which is actually going to be the description. So this is going to be conditional. It's going to be a text area if we are editing it. Otherwise, it's going to be a plain div. So for now, let's just make it a div. And then later, we're going to make it conditional. So inside of this div, let's give it a roll of buttons so that it indicates that the user can click on it. And let's give it a class name of min height of 78 pixels, BG neutral 200, text small, font medium, py3, px 3.5, and the rounded medium. And inside, let's render the data description, or since it's possible that we don't have one, let's add a placeholder, add a more detailed description. All right. And as you can see, this is how it looks now. So we have a nice little button that we can click here. And right here, we're going to have our actions, right? So let's go ahead and now we'll create the functionality to enable editing, right? For that, we need to add all of those things like enable editing and refs. So let's go ahead and prepare all of that here. So first things first is editing and set is editing. they're going to come from use state and by default they are false so make sure you import use state from react and while we are here let's also import use ref and element ref now let's go ahead and let's create the text area ref and the form ref so const text area ref is use ref with the type of element ref which uses the text area and by default let's give it null and let's copy and paste this and now create a form ref which uses the form element all right and now I just want to add the query client and the params so const query client is use query client from tan stack react query so make sure you have this import I will just order them by length all right and we're also going to have the params so So const params is use params from next navigation. So let me just add this as well. Great. So we have everything we need here. And now we can go ahead and create our enable editing function. So const enable editing is an arrow function, which set is editing to true and set timeout. And then we're going to focus on the text area ref using the current focus. Now let's go ahead and let's create the disable editing. And that is very simply going to set editing pulse. Great. And now let's create our own key down. So const on key down is going to take in the event, which is the keyboard event. And if event key is escape. in that case let's go ahead and disable the editing so a little bit of good user experience on our side and now let's go ahead and let's add the use event listener from use hooks ts and while we are here let's also import use on click outside from use hooks so add both of those use event listener is going to be listening on the key down event and it's going to call on key down. And now let's add use on click outside of the form ref. And that is going to trigger the disable editing. Great. And let's go ahead and create a const on submit here, which takes in the form data, which is a type of form data. And inside of here, let's go ahead and let's destructure the description. So form data, get description as string. And let's also get the board ID to be params board ID as string. Great. And let's add a comment to do execute, because we didn't add that yet. Perfect. And now we can go ahead and modify this div to render conditionally. So let's wrap this inside of curly brackets. And let's write if is editing. Let's go ahead and let's just render a return of a paragraph. let's just make it like this oh actually this represents the return my apologies so inside let's render a paragraph which says editing and then let's wrap this in the else clause the entire div uh which renders uh this like that uh actually like this let's just see so it should be just this div and close the else clause. And you can remove this data, the description here like that. So two divs here, and one div here, and we can indent this, I believe. Yes. All right. So now let's give this div an on click, enable editing. Great. And now let's go ahead and change this, not be a paragraph, but instead be a form component. And let's go ahead and give it a ref of form ref. Let's give it a class name of space y2. Now inside, let's render the form text area component, which we created recently. So from s slash components form form text area, and let's give it all the necessary props. So we need the ID, which is a description. We need a class name, which is W full and margin top of two. We need a placeholder, which is add a more detailed description. We need errors. Well, we're going to have them later. And let's give it the default value of data.description or undefined. And outside of the form text area, create a div, which is going to hold our submit button and our cancel button. So give it a class name of flex items center and gap X2. Let's render the form submit component. yes, form submit component from add slash components form submit. So make sure you add this import as well. And inside, let's just write save. And now let's go ahead and render a regular button here, which we can import from add slash components UI button like I did here. And inside of here, we're going to render cancel. And now let's go ahead and give this all the necessary types. So type is going to be button, on click is going to be disable editing, size is going to be small, and variant is going to be ghost. And now I think when I click in here, there we go, we have a nice little description to write in here. Perfect. So now let's go ahead and actually create the execution for that. And we don't have to create any new server action, we can reuse our existing action called update card Because in the schema you can see that we already prepared for accepting the optional description inside and in the index we just spread whatever values the user passes us so that way we cover both the title editing and the description editing so I'm going to go here and I will import use action from hooks use action and I'm going to import update card from actions update card. And now let's go ahead and I'm going to go right here and I'm going to execute this. I'm going to extract execute from use action. I'm going to pass in the update card component. And let's go ahead and let's get the on success here. And let's call the toast from sonar. So let's move this owner to the top here. And let's go ahead and where is it? It's right here. So we can also use the data here. So data.success. And we're just going to open back this. And we're going to write card, open annotations, data title, updated. So we don't need to render the entire new description because it can be pretty large. and inside of here I also want to call the query client and I want to invalidate the queries which is the card and data.id and let's add the on error which takes in the error and that is just going to well display that error in a toast and let's also extract the field errors from here. And then we can use that to pass in this form text area as errors. Great. All right. So now I just think we need to add some refs here. So let's actually we need to add the action here. So action to this form is going to be the on submit, right? I think that's what we call it right here. And now let's use the execute. And let's pass in the description and the board ID. Let's see if I did this correctly. So we have the execute like that. And something seems to not be working. Oh, I think it's because of this board ID. It seems to be missing. It seems to be missing the title or it's missing the ID. Yes. ID, which is data.id like that. but I still think we have to modify the schema because in our action, yes, in schema, we always require the title. So let's make the title optional as well. So go inside of your schema of update card and just modify the title can be optional like that and just wrap the entire thing in brackets and add a little comma at the end. Great, like this. And now we have no errors inside of here. Great. And I think we did not assign this text area ref anywhere. So let's also give that to the form text area here. Ref, text area ref. And let's try it out now. So I'm just going to refresh everything. I'm going to go in a random component here. I will add a original description. I'm going to click save. And it seems like it is working. Perfect. And let's go ahead and refresh now to see if that's true. There we go. It stays right here. The only thing I want to change is after I save, I want to disable the editing. So let's just go ahead and do that. So I'm going to go right here and call disable editing. And I think that's going to be enough to trigger it back to the original. So I'm going to modify it again. I'm going to click save. And there we go. It moves back to this. Perfect. So we finished the description now. Now I want to create my actions and my actions skeleton. So let's go back to our index model. And outside of this div, which represents this column, we're going to create a new one. And actually, we don't have to create a div at all. Instead, we're just going to render the actions here, right? So just leave a little space here and let's go inside the card model and create actions.tsx. Let's mark it as use client and let's export const actions here. And the actions are going to have its own div. And don't forget to return this. Actions. And what I want to do first is I want to create the skeleton for my actions. So actions.skeleton is equal to function actions.skeleton. And let's return a div with a class name space y2 and margin top of 2. And in here, let's render the skeleton component, which we can import from add components ui skeleton. Let's go ahead and give it a class name of width 20, height 4, bg neutral 200. Let's copy this two more times. The second one is going to have a pool width and a height of 8. And the same is going to be for the one below. Now let's go back inside of the index right here. And now we can conditionally render that. So if we don't have card data in that case, well, let's do it like this. So if we don't have card data, we're going to render actions from .slash actions.skeleton. so make sure you import the actions in the same way with the description and the header so just make sure you have all the necessary exports in here all right and otherwise we're going to render actions but we're going to pass in the data which is card data we don't have the types defined for that yet but I think it should be enough and you can see when I expand they go to the side here so that's exactly what I want. And when I refresh, you can see how I have a nice skeleton for them on the side. Perfect. So now let's create the interface and let's wrap up the actions. So the interface, I think you already know what it's going to be. Interface actions, props, data, card with list from types. All right. And let's go ahead and assign those props, actions, props, data. And let's just make it properly spelled. There we go. And now let's go ahead and style this UI. So our first div is going to have a class name of space Y2 and margin top of two. And then inside, we're going to have a paragraph, which will say actions. So let's just give it a class name of text extra small and font semi bold. And then below that, we're going to have two button elements. So just make sure you import the button from here. And the first one is going to say copy, and it's going to have a copy icon from Lucid React above the text, right? So before the text, make sure you import copy from Lucid React. And while we are here, let's also import the trash icon from lucid react and let's go ahead and give this copy a class name of h4 w4 and margin right of two and now let's give this button a variant of well we have to create a new variant because whichever variant we actually use for this model simply won't look good so before we do that let's go inside of our button component so inside of the components folder ui button after transparent go ahead and create gray. And let's give it a BG of neutral 200. Text secondary foreground, hover BG neutral 300, like this. So if you misspelled it or something, or if it's not working, you can always visit my GitHub and just take a look at it here. And now let's give it a gray variant like that. And as you can see, we now don't have any errors. And let's give it a class name of w full and justify start. But I also want to give it a new size. And let's actually go ahead back inside of the button. So we practice how to modify this size as well. So go back inside of your button. After you've added this gray variant, go inside of the size here and create an inline size. And let's give it an H auto PX of two PY of 1.5 and text small. Great. And now let's go inside the actions and give it the size of inline. Great. So that is our button now. And we can copy and paste the button one below another and change this one to be delete and use the trash icon, which we already imported. So let's try that out now. When I click here, there we go. Actions, copy and delete. Perfect. So now we actually have to create the execution methods of those actions. So we can actually kind of reuse both of those actions, right? Because they're not going to be very different from another. And let's just quickly visit. Okay, so I added text small to the inline. Okay, everything is fine. So we can close everything and let's go inside of our actions. And let's, for example, copy how we can do the copy list. Why not? And let's change it to copy card. Let's go inside of the schema and let's rename it to copy card like this. And what's everything we need inside of this? So we need the ID and we need the board ID. I think that is fine enough. Now let's go inside of our types and let's change this to copy card like that and change this to be a board, sorry, card and returns a card. And inside of the index here, let's also change the copy card from this schema all the way down here and change this to copy card as well. and now the authorization can stay the same. We are extracting I believe yeah ID and board ID That all we need but we going to be working with a card So just change this And now let remove everything inside of the try block So it's completely empty. And let's go ahead and first get the card to copy. So await DB card find unique where ID matches. And the list board has the same organization ID. If there is no card to copy, we can just return an error, card not found. Great. Now that we have our card, let's find the last card in this list. So const last card await db card bind first where list ID is card to copy list ID. So we didn't need to manually pass the list ID because we can fetch it using this existing card we want to copy. And let's order by order descending. And all we need to select is the order itself. and now let's write the new order. If we have the last card, it is last card.order plus one, otherwise it's just one. And let's write card, we await db card create, and let's use the data here, let's give it a title to be open back text, and we're gonna copy the original card to copy the title, and we're going to give it a little tag copy at the end. Let's give it a description to be card to copy dot description. So that can stay exactly the same order, new order, and list ID is going to be card to copy dot list ID. Great. And now we are ready to return that card right here. Perfect. And while we are here, I think we can already go ahead and create our server action for deleting a card. So let's just copy and paste this and change it to delete card. Let's go inside of the schema here and change this to delete card as well. I believe we only need the board ID and the ID as we did in the previous one. So let's immediately go inside of our types and let me just change. Oh yeah, I had a typo, delete card inside of the schema. And let's give this here, this here. Perfect. Go inside of the index. Let's use the delete card schema. Let's go all the way down, change it to delete card and the function rename to delete card as well. Perfect. So now I believe this will be even easier. So we can just, we can remove everything inside of the try block for the delete card, because all we need to do is card is await DB card delete where ID is matching and the list board has the matching organization ID. That's all we need. Perfect. And let's change the error to be failed to delete. And we pass in the card. Perfect. So we now have our two new actions and now we are ready to go back inside of our actions. Do I have an error here? I believe it's just, let me just, I'm going to, I just pressed command shift B and reload my window to see if this will go away. There we go. It's no longer red. Okay. No errors. So let's go inside of components, models, actions right here. And let's import everything we need. So we need the use action from hooks, use action. We need the copy card from actions, copy card. And we also need the delete card from actions delete card. Like that. Perfect. So let's go ahead and add those here. So const use action copy card. And let's write it execute to be execute copy card. Let's copy and paste this. And this one is going to be execute delete hard and it's going to use the delete hard action. Let's go ahead. And I believe we need some params here. So let's import use params from next navigation. And let's add the params here. So use params. and let's create const on copy to be an arrow function, which is going to get the board ID from params board ID as string. And let's go ahead and call the execute copy card, pass in the ID to be data.id and board ID. and we can copy and paste this function and rename it to on delete and this is going to call execute delete card like that and now what I want to extract from here is also going to be well the is loading right so is loading for this one and is loading for this one as well. And let's change it. Yes, so is loading copy, and this one is going to be is loading delete. And let's now assign those actions to our buttons. So the first one is for copy. So on click on this button is going to use on copy and disabled if is loading copy. And now let's copy these two attributes and let's place them here. So this one is going to be on delete. And is loading delete. Great. I just want to add some callbacks now. And also when we delete a card, I want to close the model, right? So let's go ahead and let's import our card model from use card model. So you can import that from Hook's use card model. And let's also import toast from Sonar so we can display a success message. So first, let's do the copy card. So on success here, I want to go ahead and log toast success. And let's write card open annotations data dot title. Hop it. And I believe, yeah, we can close the model on copy as well. So card model dot on close. I think that would kind of make sense on error. Let's get the error. and let's do toast.error. And now let me just copy these two actions here and just open an object here and paste them here. So this is going to be card data title deleted. And it's also going to close everything. And I think that should be it. Let's try it out. So I'm just going to refresh just in case. I will get this one, two, three. First, I'm going to try to copy. There we go. It's disabled. and one, two, three, copy. Now let's try delete and it's deleted. Perfect. So we can now delete our cards. We can copy our cards and we can also add the description. Let's actually try with description. So I have this and if I click copy, there we go. The description copies as well. Perfect. So what we're going to do next is we're finally going to implement our activity tab here with audit logs, and then we're going to implement Stripe. Great, great job. So now that we have all the functionality finished in our board, I want to go ahead and create audit logs, which are basically logs of activity, which will be, well, created every time we do some kind of change, like renaming a card, copying a card, which is essentially creating a card, right? Deleting a card, editing the description, all of those things will have its own activity and then you're going to be able to see that. So just like in JIRA or original Trello you're going to be able to see exactly what happened with a specific card. And of course we're also going to add that activity right here in this tab which says activity which is currently a 404 page. and before we do that I just quickly want to expand this model right here because I have a feeling it's just a bit too small so let's see how we can do that. I want to go inside of components UI dialog right here and we already modified this BG black here but let's take a look right here in the dialog content. And in here, you can find the max with LG. How about we change that to 3XL instead? And now I believe that our model, there we go, is slightly bigger. So we have more room for this activity right here. Perfect. So where I want to go now, what I want to do now is first, let's shut down the app because we're going to be modifying, not ports terminal, because we're going to be modifying our schema. So shut down your app and let's go ahead inside of Prisma schema dot Prisma right here. And all the way at the bottom, let's add a new model, model audit log. But in order to use that, first, we're going to have to create a couple of enums. So first, let's create an enum action. So we're going to have the possibility of logging create actions, delete actions. Actually, let's write update first. So update actions and delete actions. And we're also going to have an entity type, which is going to be connected to each of those actions, which are board, list, and card. Great. And now let's go back inside of the audit log here. And let's give it an ID of string ID and a default value of UUID. Now let's give it an organization ID, which is a string. Let's give it an action, which is a type of action, which we just defined above. Entity ID, which is just going to be a string entity type, which is going to be a type of entity type enum which we defined above the same as action We also gonna have the user ID which created that we going to have the user image string db text and username string db text as well and we going to have the created ads so we can order them by latest with the default value of now and we're going to have have the updated at, which is also a date time with the value at updated at. And I'm just going to go ahead and align all of these things. I just like them to be aligned. You don't have to do this, of course, but you know, I think prettier does this for you, but I'm not using prettier in my tutorials because I don't want you to miss out on any changes in the code. Great. So now that we have this, we have to push it in the database. So let's go ahead inside of the terminal here and let's run npxprisma db push. So this is going to add this new model inside of our database. And now we also have to do npxprisma generate. So we locally have this. And again, make sure you've shut down your app. So now run it again, because if you kept it running, well, there's a chance that it wouldn't be registered. And then you're going to have some weird errors until you restart your app. And now I want to go inside of the lib here and I want to create a new file, create audit log.ts. So we're going to have a reusable function whenever we want to create a log. And let's go ahead and import out and current user from at clerk next JS. And let's import action and entity type from at Prisma client. And let me just change this to be out like that. Let's import database from, well, I'm going to change it to lib like this. And let's create an interface props to accept the entity ID, which is a string, entity type, which is entity type, entity title, which is a string, and action, which is an action. And now let's go ahead and let's create this function. So export const create audit log is going to be an asynchronous action, which calls the props. And now let's open a try and catch block. So in case this fails, we don't want to break the entire function, right? We just want it to live in its own scope. So console log, let's write audit log error, and let's log that error here. And now in here, first let's extract the organization ID from out. And now let's extract the entire user from await current user. So both of these are imported from clerknext.js. And let's go ahead and check if we don't have the user or if we don't have organization ID. In that case, throw new error, user not found. And now let's go ahead and let's extract entity ID, entity type, entity title, and action from props. and let's do await db audit log, which we now have create and give it a data of organization ID, entity ID, entity type, entity title. Did I misspell that? Entity title. Looks like something is wrong here. Did I forgot to add it to my schema? Let's go inside of Prisma schema here. Yes, I forgot. So add entity title here, which is also a type of string like that. And now what we have to do is shut down our app since we modified the schema, run npxprisma generate again. So that's going to fix it here. As you can see, now we no longer have that entity title error. And then we also have to do npxprisma db push to synchronize it with the actual database on PlanetScale or wherever you're running your database. And then npm run dev. And everything should be fine. Great. Besides entity title, we also need to pass the action. We need to pass the user ID, which is user.id. We need to pass in the user image, which is going to be user question mark image URL. And we're also going to need the username, which is going to be user first name plus empty space user last name. Like that. So that's it. That is our audit log action. So now let's go ahead and add it to a very simple action, like for example, creating a card. so in order to prepare to see whether this is working or not I'm gonna go ahead and also open my prisma studio so npx prisma studio make sure you have this running and let's just paste it in here so I have no audit logs you can see it says zero here so I'm gonna click here and focus and that. So there are no rows in this table. And now let's go ahead and let's close this and let's go into a very simple action, create card. And let's go inside of index right here. And let's go all the way to the bottom here where we assign the awaited DB card create to the card constant. And below that, we're going to add await create audit log, which you can import from s slash lib create audit log as I did right here. I'm just going to move it here. And let's go ahead and give it everything we need. So create audit log. Entity ID is going to be card ID. We're going to have entity title, which is going to be card title. We're going to have entity type, which is going to be entity type, which you can import from at Prisma slash client. So entity type dot card. And we're going to have an action, which is going to be action, which you can again import from Prisma Client dot create like that. So let me just show you my imports. So I also added this action and entity type from Prisma Client and create safe action from ask slash lib create safe action. Perfect. So let's try out if this is working. I'm going to refresh this in my Prisma Studio. I have no audit logs right now. So let's just wait for this to okay. And let's create a new card test. Let's click create. It's creating and there we go. The card has been created. Let's check my Prisma Studio here. I will refresh the audit log and there we go. I have the organization ID. The action is create. I have the entity ID. I have the entity type card and the entity title matches my card and also have the user ID, user image, username, everything I need. Perfect. So now we are ready to create an API route so we can fetch that activity inside of a card model and display it here. So let's go back inside of our API folder. I'm going to close everything here. Go inside of app API, inside of card ID and create a new folder logs. And inside, create a new route.ts. Let's export asynchronous function get here. Let's get the request, which is a type of request. Let's extract the params, which are a type of params, which have the card ID, which is a string. Just confirm, of course, that this card ID matches the card ID folder. Let's open try and catch block. Let's log the error here. And let's just return new next response, internal error. with a status of 500. Now let's, oh, we have to import next response from next slash server. So make sure you do that. And in here, we're going to go ahead and extract the user ID and the organization ID from the out library from clerk next JS. And while we are here, let's also import the database from s slash lib DB. And now let's go ahead and check if we don't have the user ID or if we don't have the organization ID return new next response unauthorized with a status of 401. And now let's get const audit logs. We await db audit log find many. And let's go ahead and write where the organization ID matches. entity ID is params card ID and entity type is entity type which you can import from Prisma client entity type dot card like that and let's order by created at descending and for the card model we're just going to take three so only the latest three are going to be visible there but in the activity page, you're going to be able to see all of them. Great. Now let's go back inside of the card model. So components models card model inside of the index here. And in here, we already fetch the card data. So we can copy and paste this function and replace this to be audit logs data. And let's change this query key to be card dash logs. Let's change the fetcher to go to slash API slash cards slash ID and then slash logs and change the expected output to be audit log from Prisma client and add a little array at the end. So I imported audit log from here. Great. And now that we have this audit logs, it's time for us to render them. So inside of here, let's create a new component called activity.tsx. Let's mark it as use client and let's export const activity and let's return a div saying activity here. Now what I want to do is create a little skeleton for it. So let's write activity.skeleton to be function activity.skeleton.\nand let's return a div inside, which is going to have a class name of flex items start gap x3 and w full. It's going to have a skeleton, which you can import from s slash components ui skeleton, and go ahead and give this skeleton a class name of vg neutral 200. And let's also go ahead and give it a height of six and a width of six. Now let's go ahead and create a div with a class name of W full and inside we're gonna have two more skeletons like this. Let's go ahead and give this one a width of 24 and a height of 6 and let's give the bottom one a width of full and height of 10 like that. And let's give this upper one a margin bottom of 2 as well. Perfect. So let me just save that. And now let's go back inside of our index here. And here we render the card data. So let's copy and paste this below. And let's change this to use the audit logs data. And it's going to render the activity from dot slash activity and activity here. And we're going to pass in the items here to be audit log data like that. So let's just go ahead and quickly create an interface for the activity here. So interface activity props is going to have items which are a type of audit log from Prisma client. Perfect. And I think we should not be having any error here. Let's see. We seem to be having errors. Oh, we didn't assign that. Yes. So audit activity props and extract the items. Great. So now when you click on a card, let me just refresh. When you click on a card, there we go. You have this lower loading state right here. And it seems something is happening with our request. Let's check that out. So right here, audit logs data, API cards ID logs. And let's check my API route. So app API card ID logs, export asynchronous function get. Oh, I forgot to return the audit logs. So let's go back inside of the route for logs here. And after we fetch the logs, let's return next response, JSON audit logs like that. All right. And now let's refresh this and let's pick another one. And there we go. Now you can see how it stops loading after a certain time. Now let's go back inside of our activity component inside of our card model here. And let's go ahead and give it some styles. So class name is going to be flex items start gap x3 and width of full. We're going to render the activity icon from Lucid React and make sure you import it as activity icon. Or if you can't do that because they have the activity icon, but you can see it clashes with the name of our component. So you can always remap it using as activity icon, or you can just simply import the activity icon, but just in case they change it in the future or something. And now let's give this activity icon a class name of flex items start gap x3. Sorry, no, I copied one from above. So height of five, width of five, margin top of 0.5, text neutral 700. let's create a div with a class name of width full. Let's create a paragraph which says activity. And let's go ahead and give this paragraph a class name of font, semi-bold, text, neutral, 700, and margin bottom of two. And then let's create an ordered list here with a class name of margin top two and space Y four. And in here, let's iterate over our items like this. and let's just create a paragraph item, and this should be item, of course. So item dot, let's just say entity title for now, just so we see something here. Let's try it out. So not all of our cards are gonna have activity, just the recent one which we created. And there we go. You can see we have some existing activity here because it knows that this is the name of our component, sorry of our cart and now let's create a reusable activity item component before we do that i just quickly want to create a lib which is going to be called generate log message so we don't have to write our message ourselves every time so inside of the lib folder create a new file generate log message.ts and let's go ahead and import action from prisma client and audit log from Prisma Client as a type. And now export const generate log message is gonna be a log, audit log. Let's go ahead and extract the action entity title and entity type from log. And let's do a switch on the action in case it is action.create. we're going to return backticks and this should be without column here we're going to return created entity title sorry entity type to lowercase and then we're going to open annotations and render the entity title so if it is the create action, we're going to say created a card and then the name of the card like that. And let's go ahead and just copy this actions. So let me just indent them properly. Like that. So the second action is going to be update. And we're here we're going to say that we updated. And the last one is going to be delete. And in here we're going to say deleted. And then the last one is going to be the default case. And we're just going to say that it's an unknown action because, well, we don't know exactly what happened if for any reason we don't have that. Great. So now that we have the generate log message, let's go inside of our components and let's create a new file activity item dot TSX. So let me just close this. So alongside our hint and our logo here. Let's go ahead and let's export const activity item. Like this. Let's go ahead and create a type for it. So interface activity item props is going to have the data audit log from Prisma client. And now let's also import the generate log message from lib generate log message. And let's also go inside of our terminal here and let's run npx chatcn-ui at latest at avatar because we're going to need to display the image of the person that created this specific log, right? So, okay, that's fine. And let's also import that. So import avatar from .slashui avatar and avatar image. And I'm just going to replace that to use components. and now let's assign the props so activity item props let's extract the data from here and let's go inside and let's return a list element and let's give it a class name of flex items center and gap x2 and let's write an avatar here which we have imported let's give it a class name of h-8 and w-8 and let's render the avatar image so we have both of those imported from here right and let's give this one a source of data user image and now below that let's create a div with a class name of flex flex dash call and space dash y 0.5 and inside open up a paragraph and let's give this paragraph a class name of text small and text not test text small and text muted foreground and inside let's open up a span and render data user name and give this span a class name of font, semi bold, lowercase and text neutral 700. And then just outside of this span here, add a space and run generate log message and paste in the data like that. And then just outside of here, we're going to add a new paragraph, which is going to well say when this was created. And for that, we're going to need to install npm iDateFNS. So just make sure you install DateFNS so we can work with dates now. So let's go to the top here and let's import format from DateFNS. And now here, we're going to go ahead and render format new date data.createdAt. and we're going to format it in a type of MMMD, YYYY and then open this small annotations at and then we place the time HMM8 like that and give this paragraph a class name of text extra small text muted foreground like that. Perfect. Now let's go back inside of our activity inside of the card model here and instead let's render the activity item from components activity item so make sure you import that and let's go ahead let's give it a key of item.id and let's give it data of item like that and let's try that out now when I click on test there we go it says that I created test and now what we have to do well for every new card that we create that going to be true so it going to be having the new audit log here And now we have to add audit logs for all the other actions inside of our projects So just go inside of the actions and we going to go inside of each of those and add where we can. So let's start with the copy card, go inside of the index here and in here where we assign the new card, go ahead and await DB, sorry, await create audit log from at slash lib create audit log and give it an entity title to be card.title. Go ahead and give it entity ID to be card.id. Entity type to be entity type from Prisma.client. So make sure you import that dot card. And we're going to have action to be action again from Prisma client dot create like that. So let me show you my imports. I imported the create audit log here. And I imported Prisma client action and entity type. And we're going to do that for all of those. So I'm actually just going to copy this so I can easily do it. So you can copy create audit log as well. After a copy card, let's go into the side of copy list. And in here where we assign our list. Let's go here. And let's import create audit log from lib create audit log. Let's use the list title. Let's use the list ID and import the entity type from Prisma client and the action from the card. Great. And let me just align my imports here. Great. All right. And now let's go into the next one. So that's going to be our create board. So go inside of the index here. After we create the board, go ahead and create a new audit log. Use the board title and board ID, import the entity type and import action. Great. Now let's go inside and let's change the entity type, of course, yes, I think I forgot that in the list as well. So change the entity type here to be word, right? And go inside of the list and change the entity type to be list as well. So that's important. All right. And the copy card one is correct. Now let's go inside of create list index right here. And we do the same thing. So find the list right here. And let's import create audit log. we are using the list and let's import the entity type let's import the action so the action is correct but the entity type needs to be list all right now let's go inside of delete board go inside of index here and after we delete it go ahead and import create audit log from lib slash create audit log. We are using this board and entity type is going to be board and our action. So we make sure you import both entity type and the action is going to be board. Sorry, delete. All right. Now let's go and fix the delete card. So all the way to the bottom here, Let's add create audit log. Let's use the card. The entity type is card. Make sure you import the entity type. And the action delete is correct because we delete here as well. Now let's go into the delete list. And we're doing the same thing. So find where we deleted the list in the try block. Let's import create audit log. Let's use the list information. We import the entity type. We import the action. The entity type is list, but the action is correct. Now let's go inside of the update board index here. And let's paste that here as well. So after we update the board, import create audit log, use the new board. The entity type is board and the action is update. Great. and let's go make sure you save those files of course go inside of update card now and it's going to be pretty similar here so create audit log we're going to use the card import the entity type which is a type of card and import the action which is still update and go inside of we're going to skip the update card order and update list order and instead we're going to go just for the update list here. So when we change the title of the list, let's go ahead and create audit log here. And let's use the list information. And let's use the entity type from Prisma client and let's use the action. And that is the same action. Perfect. So now whatever we do, we have, of course, our audit logs. Again, just make sure that you add the necessary imports from Prisma client and create audit log. Great. But what happens? So right now, you can see when I try, let's try it out. So when I edit something here, it should give me a new, oh yes, we forgot, we need to revalidate this as well. So I want to go back inside of the card model. So go inside of components, models, card model here, and we have the header, right? Let's go inside of the header first. And in here, we do some query client invalidate queries. So let's just copy this. And let's also reinvalidate the card log, if that is the correct one. So it's card logs. So reinvalidate the card logs. And now, let me just refresh, pick a random one, change the title. And now, it should reinvalidate with the new logs. And they're right here. And they will also work. It will not work for the description. So we have to do the same thing in the description. So copy the invalidate queries, go inside the description here. And let's see, where is our own success? It's right here. So after we re-invalidate the card, also re-invalidate the card logs. So let me try that out now. When I add a new description, there we go. It says that we updated the card. and if you're wondering around this fact that sometimes it's empty well this should never happen right it's because we created these cards before we implemented audit logs so after you reset your database right when you're going into production or whatever you're gonna see always see some activity here or you can use prisma migrate reset perfect so we finished the activity here. And let's just try for copy, for example, when I click copy here, it also has the created card exactly what we want. And now we finally are able to create the activity tab here, which is going to be quite easy because we have a lot of reusable components here. So let's go. Let me just expand this. Let's go inside of the app folder, platform, dashboard, organization, organization ID, and in here, create a new folder called activity, like that. And inside of activity, create a new file, page.vsx. And let's go ahead and write const activity page. Let's return a div activity page. And don't forget, you always have to export default when it comes to pages and layouts. so activity page like that and let's try and click on activity now and there we go no longer we have a 404 error if you're still seeing a 404 error confirm that you have named the activity correctly and then you have to confirm inside of your nav item here in the routes that you're in this routes constant that your activity also doesn't have a typo here. So just some possible corporates that could happen. Great. And now inside of this page, let's go ahead and let's give this a class name with full. Let's go ahead and let's enter the info component from dot dot slash components info. So we have it right here, the info component like that. Let's add the separator from components UI separator. Let's give it a class name of my2. And then let's go ahead and let's render the activity list, which we don't yet have, but we're going to create in a second. So go ahead, inside of activity here, create a new folder, underscore components, and inside create activity dash list dot dsx like that. It's going to be a server component, so no need to put use client on the top. Activity list is going to be an asynchronous function. And let's extract the organization ID from out, from clerknext.js. If we don't have the organization ID, in that case, we can redirect the user using next navigation to select org. So make sure you import redirect from next navigation. And while we are here, let's also import the database. Let's import the activity item from components activity item. And let's import the skeleton from components UI skeleton. Now let's go ahead and let's fetch all the activity logs for this organization. So audit logs are await, DB, audit log, find many. And let's go ahead and write where, organization ID. That's all we need. and then let's go ahead and let's return an ordered list here and give it a class name of space y4 and margin top of four. Let's add a paragraph no activity found inside this organization and this is only going to be visible if there are no items. So we can do that using CSS using the hidden by default but only if it is the last element inside of this ordered list it's going to be visible using the block. And text extra small, text center, and text muted foreground. And inside of here, let's do audit logs.map. Let's get the individual log in here, and let's render the activity item. Let's give it a key of log ID and data of log itself. Great. And now, Now let just go ahead and create the skeleton for this one So activity list is activity list skeleton function activity list skeleton And let's go ahead and let's return an ordered list. And let's give it a skeleton here. And let's write class name width of 80% and the height of 14. and let's give this ordered list a class name of space Y4 and margin a top of four. And now let's just copy it a couple of times. So let's give this one a 50% width. Let's give this one a 70% width. This one an 80% and last one a 75%. Like that. So make sure you have this skeleton. Now go back to the page.tsx right here. And let's go ahead and do the following. Let's import suspense from React. let's wrap the activity list inside of suspense here. Let's import the activity list from dot slash components activity list. So let me just separate my imports just a bit. And now let's go ahead and give it a fallback of activity list dot skeleton. Like that, there we go. Now we have the activity page. So you can see in here, we shouldn't have any activity. And now let's try out and create a new board for example. So I'm going to create a random board here and I'm going to go back to the activity here and there we go. It says that I created a board. Perfect. So let's try and renaming the board now and let's go ahead and let's add a list here. And besides the list, let's also go ahead and create a card. All of those things. Let's copy a card, for example, and let's delete a card. So all of those things. Let's go back now to the activity. And there we go. Created the card, created the board, created the card, updated the board, created a list. We have everything we need in here. And just one more thing that I want to do. And this thing, I think, yeah, it should be like this activity list.skeleton or not. Oh, I have a mistake here. skeleton. Yes, and I think, yeah, we need to render it like this. Great. And now just go inside of the activity list and also add order by created at descending state is in proper order. Otherwise, it doesn't give us too much information there. So we created the board, we updated the board, created the list, created a card, created a copy card, and then deleted a card. Perfect. And you can see how we have a loading state for our activity. Great, great job. All that's left is to create the payments. All right, so now that we've finished our activity, it's time to get to the last part of this tutorial, which is implementing board limits and a subscription. So right now we have this text which says the five are remaining, but it is hard coded and it's not actually enforcing anything. So let's go ahead and change that first. I want to go back inside of my Prisma schema right here. And let's go after our model audit log. Let's create a model organization limit, short org limit. Let's give it an ID and a default value of UUID. Now let's go ahead and give it an org ID, which is a string. Let's give it a count, which is an integer, and by default, it's going to be zero. And let's also ensure that this organization ID is unique for every organization limit we want to create. And after that, let's add the created at field, which is going to be a date time and a default of now. And we're also going to have the updated at with a date time and a default, sorry, updated at. and let me just align all of these things. Let's try it like this. And now let's go ahead and let's push this into the database. So just make sure you have the ID. Make sure you have the organization ID, which should be unique. Make sure you have account, which is an integer with a default value of zero. Let's go ahead and let's do the following. npx well first as always shut down the app right that's important and then let's do npx prisma generate so it's locally available and then npx prisma db push like this and i actually just want to clear out the entire database so let's do that as well npx prisma migrate reset like this and confirm that you want to reset the entire database because i want to enforce this count of our boards and after you do the reset, you have to run db push again. So I should have done that first, my apologies, but nevertheless, let's go ahead and proceed with this. So now that we have the organization limit, I wanna go ahead and I wanna create a constant call boards. So let's go inside of our constants folder where we already have the images and let's create a new file, boards.ds And let's export const max free boards to be five. So from here, you will be able to change it if you want for your application. Great. And now I want to create a helper called organization limit from where we're going to fetch all the necessary information about how many boards there is left, how many, how to increase a board and to check if we can even create a new one for free. So go inside of the lib folder and create a new file org limit.ts. And let's go ahead and let's import the out from clerk next JS. Let's go ahead and import the database. And I'm just going to change the import to use add slash lib. And let's go ahead and import max free boards from constants boards. And first, let's write a function so we can easily increment the available count. So whenever we create a new board, we have to run this function. So export const increment available count. That is going to be an asynchronous function. Let's go ahead and extract the organization ID from our auth helper. If we don't have the organization ID, we can just break the function. and let's write const organization limit to be await db.organizationlimit.find unique where we have the organization ID and I actually think it will be better if we threw an error here so throw new error unauthorized so we want to we want to break the server action if we are not having any organization ID, right? We need to enforce this. And now that we fetched this organization limit, let's check if we have it at all. So if we have the organization limit, in that case, let's do await db organization limit update, where we have a matching organization ID, and data is going to be count the current organization limit, which we fetched just here, dot count plus one. So we increase the current value by one. But if this is the first time they have created a board, then this object, this model does not exist. So we have to create it. Await db org limit dot create data, passing the matching organization ID and count is fixed to one because it's the first board they created. Great. And now we have to create the same thing, but to decrease the amount. So let's go ahead and copy this function like that. And let's rename it to decrease available count. And let's go ahead and this stays the same. And then we check if we have the organization limit and then we update it. And we could technically do just minus one, but that could take us into a negative number. So instead, we're going to check if the current organization limit count is larger than zero, then we're going to do organization limit dot count minus one. Otherwise, we're going to go back to zero. So this way we ensure that there's no way the new count can be lower than zero. Otherwise, it's going to be a count of one. And now let's go ahead and create a helper method to help us check whether the user can create a new free board. So export const has available count is going to be an asynchronous function where we're going to fetch the organization ID again. If there is no organization ID, throw new error, unauthorized. and then let's go ahead and let's fetch the organization limit to be await db organization limit find unique where we have a matching organization id and then we're going to write if there is no organization limit or if organization limit dot count is smaller than max free boards. So if they created less than five boards, we return true, meaning that it's okay for them to create a new one. Else, we're going to return false. And let's go ahead and create the last helper, which is just going to be a helper to actually tell us the number of free boards they've used up. So export const get available count is again an asynchronous function. which again uses the organization ID using the out helper. And this time, if we don't have the organization ID, we're just going to return zero because this is going to be used in our UI helpers, right? So let's go ahead and fetch const, get organization limit to be awaited DB, organization limit find unique, where we have a matching organization ID like that. if we don't have the, let's rename this to organization limit. If we don't have the organization limit, we're going to return zero. Otherwise we going to return the organization limit dot count Great And now that we have this we can go back inside of our board list So let go inside of our board list component So that inside of the app folder platform dashboard organization, organization ID components. And in here, we have the board list. Great. And let's go ahead and let's do the following. So I want to import max free boards from the constants and I want to import get available count from at slash lib org limit like that. And now what I'm going to do is after I do this fetching of the boards here, I'm going to get the available count. So const available count is going to be await get available count like that. And then let's go all the way to the bottom here where we write five remaining. And instead of writing that, let's go ahead and let's do the following. Let's make this dynamic and let's write max free boards like that minus the available count remaining. Like that, perfect. So let's refresh our local host and let's actually make sure that it is running first. npm run dev. So let's refresh this now and let's wait a second for this to reinitialize. And it says that we have five remaining. Great, so it's working because right now what we are doing is we are calculating 5 minus 0 because take a look at our get available count. Well, first it returns 0 if there is no organization ID, but also if there was no organization limit found in the database, it means that we haven't even created the first board for anything for us to calculate. So we are reducing 5 by 0, which in turn is 5. Now we have to add one of our helpers here, which is going to be this one, increment the available count inside of our server action where we create the board. So let's go inside, let me close this and let's go inside of actions, create board right here. And after we successfully create our board, let's go ahead and write await, increment available count and make sure you import increment available count from ad slash lib org limit like that. But I also want to import another thing from here, which is has available count. So make sure you have the increment available count and has available count from our newly created org limit util. And now we're going to go ahead and use this has available count right after we check for authentication, let's go ahead and write const can create to be has available count. But let's also make sure to await this, otherwise it's going to be a promise. And if we cannot create, meaning that we surpassed our available count, we're going to return an error saying you have reached your limit of free boards. Please upgrade to create more. Great. And let's try that out now. So I believe it should already be working. Let's just confirm that we properly added this await increment available count. We did. Great. And let's try it out. So I'm going to refresh this now. I'm going to create my first board and I'm going to click create. And once I'm redirected to the board and I go back, there we go. Now it says for the remaining. Let's try and fill this up now. So I'm going to create another one here. Let's go back. Now it says three remaining. Great. Let's create another one and let's go back to confirm two remaining. Great. We are getting close to getting our error. There we go. One remaining. And let's go ahead and let's get the last one here. One remaining. There we go, zero remaining. And now let's go ahead and attempt to create. And there we go, we have an error. You have reached the limits of free boards. Please upgrade to create more. And what I wanna create now is that if the user deletes one of their free boards, we want to decrease the count, right? So let's go ahead and do that. So the only thing we're gonna need is that decrease available count. So let's find the delete board action. right here. And let's go ahead and let's import decrease available count from lib.org limit here. And let's go all the way down here after we delete the board and await decrease available count. And let's try that out now. So let me just confirm and refresh here. So it says zero remaining. And I'm will get redirected back. And now again, it says one remaining. And if I try, I should not be getting any errors. And I am not great. So our front end and back end is fully in sync. And we can reverse that action. So now we found a way to block the user from creating more than five boards. And now we have to find a way to enable them to create more if they add a Stripe subscription. So for that, we have to head back inside of our Prisma schema. So let's close everything here. Let's go inside of Prisma schema.prisma and let's create a our last model organization subscription. So model or subscription for short, it's going to have an ID, which is a string and a type of ID with the default value of UUID. We're going to have an organization ID, which is also a string and unique for every subscription. And we're going to have the Stripe customer ID, which is going to be a string, optional, unique. And we're also going to map it to a specific value so we can access it differently. So give it a name, Stripe underscore customer underscore ID. So exactly what we have here, but in a different case like that. So let me just separate this so we can see them more clearly. And let me just move this here. All right. Besides this, we're going to have the Stripe subscription ID, which is also an optional string. It's also unique. And let's map the name to be Stripe underscore subscription underscore ID. And now let's go ahead and create Stripe price ID, which is going to be a optional string as well. And it's not going to be unique, but it's going to have a name of stripe underscore price underscore ID. And last one is stripe current period end. Date, time, optional. And let's map the name to be stripe underscore current underscore period underscore end. Like that. Perfect. So make sure you have all of those. Of course, you can always visit my GitHub to see the exact code. as you can see, I made a typo here. So it should be period. So just double check that you don't have any typos you can always confirm with my source code. And now what we have to do is push this to the database as well. So let's go ahead and shut down our application and run npxprisma a db push. Let's ensure that it's up to date with our planet scale or wherever you're hosting your MySQL. And then let's do npx Prisma generate so we can locally access this. Perfect. Now let's go ahead and let's run npm install stripe while we are here. Great. And now we don't have to run the app just yet because we're going to be creating a lot of libs now. So the first lib I want to create is the Stripe lib. So let's go inside of lib and create a new file stripe.ts. Let's import stripe from stripe. Let's export const stripe to be new stripe. And then we're going to give it a process.environment.stripe underscore API underscore key and put an exclamation point at the end. So we're going to have to add this in a moment. And now let's add some options here. So API version It can be different if you're watching this in the future, but you can just let TypeScript autocomplete it for you. So you can see for me, it's 2023, 10, 16. For you, it might be something different. For example, my previous tutorials don't use this exact version, right? But it will always be matching to your package which you install. So if you have the newest package, which we just did in a second, when you do this, you can see that you have an autocomplete like this. And I'm gonna show you another way to confirm which is the newest version once we get to the Stripe dashboard. So just don't worry. For now, you can just write it like this. If you have an error, I believe if you type something else, you can see that it has an error. So if you wrote everything correctly, you're going to have no errors. And let's write TypeScript true. Great. And now what I want to create is I want to create a lib to check our subscription, right? To confirm that we have an active subscription. So let's go ahead inside the lib folder and create a new file. Check. Actually, let's just call it subscription.ts. And in here, let's go ahead and let's import the out from clerk next.js. Let's import the database. And I'm going to change this to slash lib. And let's write a constant const day in milliseconds is going to be 86400000, like that. And I close that, my apologies. All right. And now let's export const check subscription to be an asynchronous function. And let's extract the organization ID from our out service. If there is no organization ID, we're just going to say false, meaning we don't have any subscription here. So we are not ever going to say true optimistically, right? So if we are not able to fetch the organization ID, we're just going to say, okay, no subscription for you. And now let's attempt to fetch the subscription. So const org subscription.com.\nis awaitdb.org.subscription.findunique, where we have a matching organization ID. And let's select Stripe subscription ID to be true, Stripe current period end to be true, Stripe customer ID to be true, and Stripe price ID to be true as well. And now let's write, if we don't have the subscription, return false. Otherwise, let's go ahead and check if it's valid. So just because we have it, that's not enough. We need to check if it's not expired. So const is valid. It's going to check if organizationSubscription.StripePriceID exists and if organization subscription.stripe current period end question mark dot get time. And let's just add an exclamation point at the end here. So we're going to compare the time of the period end plus one extra day, right? And it's larger than date dot now. And let me just zoom this out so you can see it in one line. So stripe current period end, we get the time and we add a buffer of one day, right? And we compare if even that is larger than the current date, then it means that it is valid. Great. So just ensure that you wrote that. And of course, we have to return is valid. And let's turn it into a Boolean by adding double exclamation points at the end. So please don't make one, right? Make sure you put double. This will turn it into a, for sure a Boolean. Great. So we have this done and now we have to create our Stripe API key and our web hook and our actions to actually trigger the checkout. So let's go ahead and let's, so we just wrapped up this subscription, right, is valid. Now let's go ahead and let's head on stripe.com and go ahead and just log in. And you should be seeing this dashboard. And what I want you to do is to create a new, well, store, right? So you can either use this top left corner and scroll all the way down to where it says new account. Or if this is your first time using Stripe, I think this will be open for you by default. So let's call this Trello tutorial. And let's click create an account right here. and now we will be able to copy our API key. So click on developers right here in the knob bar and here we have the API keys tab and in here click reveal the secret key and click again to copy that secret key. And now we have to put that right here in our Stripe API key. So let's go inside of our .environment file and let's add stripe underscore API underscore key and paste the key inside. And double check that your stripe API key matches exactly what you've written in the environment file. So now what I want to create is the user interface for an actual, you know, a popover model which tells the user, all right, you need to upgrade now. So let's go ahead and do that. So I'm going to go inside of my hooks and I will copy the use card model and I'm going to paste it here and I will rename it to use pro model. And now let's rename this to pro model in all instances. We can remove the ID from the store. We can remove the ID from the props here in the on open. We can remove it here as well and change this from setting any ID. So both the on close and on open and also remove the ID from here. Great. And now let's go ahead and let's create the actual pro model component. So I'm going to go inside of components, models, and just create a new file pro-model.tsx. Let's go ahead and let's mark it as use client. Let's export const pro model here. and let's go ahead and start styling this. So I'm going to create a dialog component from . . . UI dialog and I'm immediately going to add the pro model from use pro model from our hooks right here. And let's go ahead and let's give it a property open on pro model is open and let's give it on open change to be pro model on close. Great. And inside, let's render the dialog content from dot dot slash UI dialog again. And let me just change this to use add slash components. Just ensure you're not accidentally using the Radix version. And inside of here, let's give this a class name of max vmd padding zero and overflow hidden. And now what I want you to do is I want you to add a little image which you can find, well, anywhere really, but you can go inside of my GitHub and just find hero.svg. So let's go ahead and download this image and let's place it inside of our public folder. So let me show you. I prepared my public folder here and I will just drag and drop that inside of the public folder. Great. So let me just close this and let's go back inside of our pro model here. And let's go ahead and add a div here with a class name of aspect, video, relative, flex, items, center, and justify center. And let's go ahead and render the image from next slash image. So make sure you add this import as well. and let's go ahead and give it a source of slash hero.svg, which we just added, an alt of hero, class name of object dash cover and a fill property like that. Perfect. And now I think it's time for us to actually see this model. So let's do the following. Let's go inside of our providers. So in the components, we have the providers and we have the model provider. And just below the card model, add a pro model, which you can import from . . . models pro model or slash components pro model. And I quickly want to go back to our use pro model hook and just give it the default value of true for is open and make sure that you have the app running. And that should be enough for you to finally see the actual pro model. Let's just wait a second, see if that is true. And there we go. You should be seeing, it should be open by default. And I think, yeah, so every time you can close it and just refresh and it should be visible again. So it's visible because in the hook use pro model, we added it to be is open true by default and we added it to model providers right here. So make sure you do that. And also in the actual model itself, make sure you're using the pro model here. and assigned is open here. Great. So now let's continue developing on this one. So outside of this div, let's go ahead and give it a class name. Text neutral 700 MX auto space Y6 and padding of six like that. Let's go ahead and write an H2 element upgrade to Taskify Pro today like that. and let's give this h2 a class name of font-semi-bold and text-excel. Outside of the h2 element, let's create a paragraph which will say explore the best of Taskify and give it a class name of text-extra-small, font-semi-bold and text-neutral-600. and outside of this paragraph, let's create a div with a class name of pl-3 like this and let's create an unordered list with a class name of text sm and list disk and inside create a list element which is going to say unlimited words. Let's also write advanced checklists. So basically, you know, just some promotional text. Admin and security features and more, right? So we know that it's actually only unlimited boards, but after you finish this part of the tutorial, you're going to know how to limit anything else you want in this application. Great. And now outside of this div, I want to go ahead and add a button component. So make sure you import the button component from dot dot slash UI button here, components. And let's go ahead and let's write upgrade here and give it a class name of W full and give it a variant of primary like that. There we go. So now we have our nice little model here. And now let's just change its use pro model here to be closed by default. And now let's go inside of our components form, form popover here. And let's go ahead and add that model. So const pro model here, use pro model. So make sure you import use pro model from hooks use pro model. And then let's go ahead and call it if we have an error inside of this create board function. So pro model on open. And now if I try and create a new board, so let me just refresh. And if I try and create a new board, there we go. You can see that I have a pop-up. Great. Now let's go ahead and create an action that when we click on this upgrade button, we actually get redirected to Stripe checkout. So I want to begin by going quickly inside of my lib utils where I have my cn function and let's add another reusable util called export function absolute URL. Oops. Which takes in the path which is the string and it returns a combination of process underscore public underscore app underscore URL and combines the path So we're going to use this as our Stripe checkout redirect path. So that's why we need it to be exactly where our app is hosted. So now make sure you copy this environment variable, go inside of .environment, dot environment and add it. So HTTP localhost 3000. Just make sure you don't put an extra slash at the end. So it needs to be like this. It doesn't matter what your port is or wherever you're running it. Just make sure it doesn't have the end slash. Great. And now let's go ahead and let's copy one of the existing server actions like copy cart here and let's rename it to stripe redirect would be good. So stripe redirect. Let's go inside of stripe redirect, and let's resolve the schema first. So we're going to call it stripe redirect here. And let's go ahead and change this to be just an empty object, because we don't need to pass anything. And now go to types.ds and change this to stripe redirect as well, and modify it here. And the return type is just going to be a string, right? So we don't need this Prisma client at all. And now let's go inside of the index here and let's import this stripe redirect from schema. Let's go all the way down and let's put it here and let's change this function's name to be stripe redirect. Stripe redirect, lowercase like that. Great. And now let's go ahead and let's actually do this. So I'm going to go ahead actually and how about I remove this entire try and catch block. So all the way from here, I don't even want this. Make sure you only have the authorization here. Now let's get our settings URL. So that's going to be our absolute URL from lib utils, which we just created. I've added it just here. And let's go ahead and pass in the backticks organization slash the current organization ID. Now let's write let the URL, which we expect the return, is just going to be an empty string. And now let's open our try and catch block here. Let's get inside of the try and let's write const organization subscription to be await DB organization subscription find unique where we have the organization ID. And now let's check if we have the organization subscription and if organization subscription has Stripe customer ID, let's go ahead and let's create a Stripe session. So we're going to do that using const Stripe session to be await Stripe.billing portal. Oh, let's also import Stripe from add slash lib Stripe as I did here. So if we already have a subscription, we're going to open the billing portal, sessions create, and pass in the customer to be orgsubscription.stripecustomerID, and the return URL is our settings URL. And now let's just return the URL to be the stripesession.url. So that's if we already have a subscription. And now let's write an else clause. And in here, let's go ahead and create a new Stripe session. So const Stripe session is going to be await stripe.checkout.sessions.create. Success URL is going to be the settings URL. Cancel URL is going to be the settings URL as well. Payment method types is just going to be card. You can, of course, modify this however you want. So I'm just giving you my options here. Mode is going to be subscription. We're going to have a billing address to be auto. The customer email is going to be user email. Do we have the user? We don't have the user. So let's go ahead up here and let's get the user await current user from clerk next JS. So just make sure you import current user from here. And let's also check here if we don't have the user. In that case, also, let's throw an error. And now we can go back here. And let's go ahead and get the user email addresses, the first one in the array, dot email address. Let's write in light items here. Let's write price data. The currency is US dollars. product data has a name of Taskify Pro and description is unlimited words for your organization. And now let's go ahead and give this product a unit amount of 2000 which represents 20 recurring on an interval of month. So monthly interval and let's also give it a quantity of one right here. Perfect. And then outside of this array, extremely important to add the metadata where we're going to pass the organization ID like that. Perfect. And now let's go ahead inside of this if clause and let's write URL is equal to Stripe session dot URL or an empty string like that. Perfect. In the catch, let's go ahead and let's just return error. Something went wrong. Great. And now let's go ahead and let's revalidate the path slash organization, organization ID, which we have. And let's just return data to be the URL. So either the billing portal or the new checkout. Great. Now let's go back inside of our pro model. So inside of components models pro model right here. And let's go ahead and let's add execute and is loading from use action from hooks use action. Let's give it a stripe redirect from actions stripe redirect right here. And let's go ahead and write on success, get the data. And now we have to redirect the user. So window.location.href is now the data, which is going to be our URL. And on error, it's going to have an error. And it's going to call toast from Sonor. So import that.error and pass in the error like that. Perfect. And now let's go ahead and let's write const on click to be an empty arrow function, which calls the execute option like this with empty object inside. And now let's go ahead and let's give this button a disabled prop if is loading and on click to be on click. And great. Let's try that out now. So make sure you are getting an error here. Make sure you filled up your boards. let's click upgrade and we should be redirected to the stripe checkout page and we are great great job but we cannot pay just yet if you try it's not going to work so i just wanted to confirm that we get redirected to the stripe checkout and now we have to create the api webhook so let's go inside of our api folder so we have to do this inside of the api folder because this is not something we are going to call, but something that Stripe is going to call. So we need to provide them with an API endpoint. So let's create an API endpoint called Webhook like this. And in the Webhook, create a new file route.vs. Let's go ahead and import Stripe from Stripe. Let's go ahead and import headers from next headers. Let's go ahead and import next response. from next slash server. And now let's import the database. And finally, let's import our Stripe util from Lib Stripe. Now let's write export asynchronous function post, which has a request, which is a type of request. Let's get the body, which is await request.text. Let's get the signature. since this is going to be called by the Stripe dashboard or SDK, we have to confirm that it is them that's calling that. So let's use the headers, get Stripe-signature as string. Basically, we want to protect this endpoint from PowerPlay. Let's define the event here. and now let's try and get the event using the stripe webhooks.construct event in the combination of our body, the signature which we have and the process.environment.stripe underscore webhook underscore secret. So we don't yet have this but we're going to add it and let's just put an exclamation point here at the end and then a catch here which is going to say return new next response web hook error with a status of 400. And if you want to, you can extract the error from here. Otherwise, if we successfully create in this event, it means that this is then, well, we can continue and actually finish the user's checkout. So this happens after the user checks out. So after they enter their credit card and everything else. So let's get the session from event.data.object as Stripe.Checkout.Session. If event.type is Checkout.Session.Completed, then let's create a subscription. Await.Stripe.Subscriptions.Retrieve. So we retrieve using the session.Subscription as a string. If we don have session metadata organization ID which we just passed inside of the Stripe redirect server action that why it said it very important We are going to be through a new error So next response org ID is required with a status of 400. Because if we don't have that metadata, we don't know for which organization to create a subscription. And finally, await database, org subscription, create data, org ID session metadata.org ID. Stripe subscription ID is subscription.id. Stripe customer ID is going to be subscription.customer as string. Stripe price ID is going to be subscription.items. the first one in the array, dot price dot ID. So let's just check if this is correct. Subscription dot items dot data, then the first one in the array like that. And stripe current period end is going to be a new date, which takes in the subscription dot current period end times 1000. Great. And we handled if the user creates a subscription for the first time. And now we have to handle a different type of event. So let's go outside of this if clause and write if event.type is invoice payment succeeded, that means that they have renewed their subscription. So in here, let's get a subscription again using await stripe subscriptions retrieve session subscription as string. And then let's do await DB organization subscription update where we have a matching Stripe subscription ID to be subscription.id. and let's update the data to the new Stripe price ID, which is subscription.items.data, the first one, dot price.id, and Stripe current period end will be updated to new date with subscription current period end times 1000. Like that. Perfect. So we finished our Stripe webhook and very important, at the end of the webhook, you have to return new next response null with a status of 200. Great. And now we have to find a way to keep this webhook running locally. So let's go ahead inside of the Stripe dashboard here. And in here in the developers tab, you have webhooks. Go ahead and click on test in a local environment. You have to download the Stripe CLI so you can click here if you haven't done that already. Once you have that, continue with the video and go inside of your terminal here and let's go ahead and open a new terminal and let's run Stripe login. And now go ahead and click on this link right here and click open and that will guide you to allow this for your computer. So click allow access. Then you can go back inside of here and in a couple of seconds you should be seeing the completed sign right here. Perfect. So let me just clear this now and now we have to run this command stripe listened forward to and let me show you exactly where we're going to listen. So stripe listen dash dash forward dash to localhost 3000 slash API slash webhook. Don't make a mistake and forget the API because it's going to be weird to debug. I see this happen a lot of times. So let me zoom out so you can see it in one line. Stripe listened forward to localhost 3000 slash API slash webhook and go ahead and press enter. And after you press enter, you're going to be seeing your webhook signing secret right here. So copy everything that is in bold text like this. So make sure you copy this and then go ahead and we have to create this Stripe webhook secret API and environment key. So let's go and do that. I'm going to go inside of my dot environment here and I will add Stripe webhook secret and paste it here. So please make sure that you copy that and confirm that your Stripe webhook secret matches. So Stripe webhook secret needs to be exactly the same. And again, confirm from your terminal, you must not shut this down. This must be open whenever you want to test out Stripe locally. Confirm that you copied from the start of the bold text to the end of the bold text and paste it here and confirm that you don't have any extra white space. Great. And now what we have to do is allow this API endpoint to be visitable without authentication, because we're going to be using that signature event to confirm that it is the, well, the correct party. So let's go inside of our middleware file. We have a middleware.ts here. And besides this being the public route, we also need to add the API webhook. So let's add slash API slash webhook here, like that. And let's try this out now. So I'm going to go ahead here and let's refresh this. And let's do it like this. I want to make sure that you keep your terminal open so you can see the logs from here. Let's attempt to create a new one and now let's click upgrade here and now I'm going to go ahead and just enter some fake information. So basically if this is your first time working with Stripe, you need to enter this exact credit card to get a success otherwise you're going to be getting failure. So this doesn't matter, this doesn't matter, the name doesn't matter, but this is very important. It needs to be like this. You can find more examples in the documentation. And let's click pay and subscribe. And let's see if we made any mistakes or if this is working as it should. And there we go. You can see I have a bunch of 200 events inside of my terminal here, which means that it is working. And let's go ahead and confirm that by I'm going to open a new terminal here and run NPX Prisma Studio. So I just want to confirm that I now have an organization subscription. So let me check that out. And there we go. I have an organization subscription model. Perfect. I have the Stripe custom ID, price ID, and current period end. So we confirm that this is definitely working. Now we have to modify this text here to say unlimited. And we also need to check in our server action to allow the user to create new boards. So first, let's visit our board list component. So I'm going to close everything here. And I'm going back inside of the app, platform, dashboard, organization, organization, ID, components, board list. And besides the available count, let's check if is pro using await, check subscription, which we created when we started developing the Stripe lib. So we have the check subscription here where we compare with the day buffer. And if it is pro, we're going to modify that text. So let's go all the way here. And let's do the following. If it is pro, we're going to say unlimited. Otherwise, we're going to do this check. And now when I save, there we go. This now says unlimited. Perfect. And now we have to modify this. So let me just zoom out so you can see. So is Pro unlimited, otherwise we do that calculation. And now what we have to do is we have to modify our actions, create board right here. So inside of the create board, let's see where it is. So in here we have the can create, now let's add const is Pro await, check subscription, which you can import from lib subscription. So if we cannot create and if we are not pro, only then are we going to throw an error. Otherwise, we can do this freely. And let me also do one more thing. So in here, I don't want to increase the available count if we are pro. So if is not pro, like that. so this will only matter if the user later decides to turn off their subscriptions that they're gonna have some weird values i believe let's try it out it says unlimited let's try it out and there we go it's working amazing amazing job you finished up a stripe subscription now we have to modify that to say pro here in the info component, as well as in this one here. And we also have to create the billing page. So let's go ahead and do that. So let's go and find our info component. Let me close everything here and find the info component. You can find it in the platform dashboard, organization, organization ID components, So just alongside board list. And in here, let's create an interface, info props, is pro boolean. Let's go ahead and assign those props. So info props, and let's extract is pro. And now all we're going to do is change this from being hard-coded to free to is pro is going to say pro, otherwise free, like that. And now we have to find all the places where we are using the info component. So let's find info like this. So first one is in the organization ID page. Let me show you where that is. So it is in the organization ID folder page.vsx right here. And let's go ahead and let's get the ispro from await check subscription from as slash lib subscription and pass in is pro here. Great. And now we have to do the same thing inside of the activity page right here. So let's go ahead and do that. Let's turn this into an asynchronous function. Let's import check subscription from lib subscription and pass is pro as a prop here Great And I believe now there we go It says that we are pro here If I go to activity it says that we are pro here But if I go to another one it says that it is free And here I have five remaining But here I have unlimited because subscription is only per organization And now we have to create our last page billing So let's go back inside of the organization ID folder. And alongside activity and settings, create a new folder called billing. Go ahead and create page.psx here and let's go ahead and do const billing page and let's return a div with a class name of wfull. Let's make sure this is an asynchronous component. Let's check is pro using await check subscription and now let's go ahead And then let's render the info component from . . . components info. Pass in the is pro prop. Like that, let me just separate my imports. Let's make sure we do export default billing page. And now you should be having this page available. So when I click on billing, there we go. It's available. If yours is still 404, confirm that your URL is slash billing. If it's not, find the nav item component and modify. Let me show you nav item component. And in here you have the routes and your billing should be slash billing. Make sure it doesn't have a typo. And of course, make sure that the page, the billing folder doesn't have a typo either and that it is inside of the organization ID folder. Great. Now let's add a separator component from components UI separator. So make sure you have that as well. Let's give it a class name of my2. And now we have to create a subscription button component. So let's just write subscription button component like this. And it's going to accept the is pro argument like that. Let's go ahead inside the billing folder and create the underscore components folder and create a new file subscription dash button dot psx. Let's go ahead and mark this as use client. export const subscription button and return a div for now. Now go back to the billing page and you can import the subscription button from dot slash components subscription button. Let me just align my imports. Now let's modify the interface for the subscription button. so subscription button props is pro is going to be a boolean and let's go ahead and assign that so subscription button props and extract is pro from here and now we're going to render dynamically depending on that so get the button component from components ui button and if is pro we're going to say manage subscription. Otherwise, we're going to say upgrade to pro. And let's go ahead and give this a variant of primary like this. And now let's go ahead and let's extract, execute and is loading from use action from hooks use action. And let's use the stripe redirect from action stripe redirect because remember, stripe redirect can both handle the cases of initial upgrade and also the billing portal. So let's give it a disabled prop of is loading. And let's go ahead and write some callbacks here. So on success, we get the data. And then with the data, we have to handle window.location.href to be the new data. If it is an error, we have to handle the error inside of the toast from Sonar. so just make sure you import the toast from sonar and now let's go ahead and let's write const on click here to be an arrow function which which calls if is pro execute but otherwise is gonna open the pro model so const pro model use pro model which you can import from hooks use pro model. So let's go ahead and write else pro model on open and assign the on click to the on click here. Great. So let's try that out now. In here, it says manage subscription. In the boards here in the settings or in the billing, it says upgrade to pro. So if I click here, I get to upgrade. But if I try here, it's actually not going to work. We're going to get an error. And let me show you why it's very simple to fix. Inside of your terminal, you have a text which says, inside of your main project here, you should be having a text. Where is it? Oh, we're not logging any error. Basically, we have to enable the billing portal. So go inside of the dashboard. Let's find some normal dashboard here. And let's find the billing, or let's search billing portal. Oh, so type in billing portal and go to settings billing customer portal. And in here, you have to activate the test link. So click on this button. And now try this again, manage subscription. And as you can see, no more errors. And instead, I'm redirected to manage my subscription. Amazing, amazing job. You finished the entire tutorial. Great, great job. If we have time, I'm going to show you how to deploy as well. If not, thank you so much for watching the tutorial. So it looks like I have some time to show you how to deploy after all. Let's just fix a couple of bugs that we have. So I noticed while reviewing my last part of the video that inside of lib, inside of the subscription here, I was saying 86,000, but I wrote 84,000. So let's be correct and make it 86. Great. And one more thing that I want to do. So in actions, when we create a board, I make sure that I don't increment if I am on the pro, right? So we don't have some weird numbers. So I think it would be fair to do the same thing when we delete the board. So we've wrapped this in is pro. So let's go ahead and check that here. Const is pro is await check subscription from atlib subscription. Make sure you have this. And then I'm going to go ahead and only do this if I'm not pro. So if it's not pro, only then am I going to decrease the available count. Great. So now let's go ahead and let's actually deploy this. So the first thing we have to do is we have to go inside of our package JSON. And in here, we have to add a post install script. So post install. And we have to run the command Prisma generate. After we've done that, we have to add that to our GitHub. So go into GitHub and click on a plus here and click new repository. And now let's go ahead and let's give this repository a name. So I'm going to give this next 13 Trello like that. I'm going to make it private and I'm going to click create a repository. And now what we are going to do is open up our terminal. so we don't need anything running so you can shut down absolutely everything from here even the stripe portal and the prisma studio and let's go ahead and run git add git commit let's give it a message so as you can see i have my messages in numbers depending on which video part i'm on so this would be for me 25 deployment for you the message can just be you know initial commit or something like that. And now we have to push this. So let's go ahead and use this second option, which says push to an existing repository. So copy these three lines right here. And let's go ahead and add git remote at origin. And let's go ahead and push this. Perfect. And now when I refresh here, you should be seeing the entire project. Great. And now let's go to Vercel and click on add new project right here and find that repository which you just created. For me, it's right here. I'm going to click import. And what we have to do now is add all the environment variables. So we can do that quite easily by going inside of dot environment. And you can just copy absolutely everything from here, select the first field and paste. And there we go. That's gonna fill up everything here. So after we deploy, we're gonna have to change the Stripe webhook secret and next public app URL, but we can only do that after we deploy. So let's go ahead and click deploy. If I get any errors, I'm going to pause the video and we will resolve it together. So my deployments seem to have failed because of this chat CN sheet component that we have, because the property class name does not exist on type dialog portal props. So I googled the error and it looks like two weeks ago there was a comment here which explained that this was fixed for dialogue and alert dialogue but it looks like for the sheet it's only coming in the next release but for now we can do this we can npm install this exact redix underlying package so let's try that I haven't tried it but I hope it's going to work so let's just go ahead and run npm install radixui slash react dialog with this exact version. And let's go ahead and paste that inside. Great. Let's go ahead and run git add, git commit. I'm going to write deployment fix here. And I'm going to push. So make sure you do git push. And that should automatically trigger the next deployment on Vercel. So you can click back on your projects here. And then go ahead and select your project, click on deployments. And there we go. You can see that this fix deployment fix is now building. So I'm going to go ahead and pause the video again and see if we have more problems. Great. So that seems to have fixed my issues. And I can now visit this on my deployed domain right here. There we go. The app is here. And now we have to modify our Stripe webhook environment key because remember, currently we created our Stripe webhook inside of the test environment. So now we have to change that to use the production environment. So let's click on the developers tab right here. Let's click back here on webhooks and here we go. I have local\nlisteners. So now let's click add endpoint for hosted endpoints here. And now we have to go ahead and we have to copy this URL, which we have pasted here and go to slash API slash webhook. So make sure you don't forget the slash API here. And let's go ahead and click select events. And we are using the checkout session completed option. So select that one. And we are using the invoice payment succeeded. So if you're not sure which ones we use, you can visit the web hook itself. And in here, you can see that we check for checkout session completed, and we check out for invoice payment succeeded. So those two are the ones we have to listen to and just click add events. So confirm checkout session completed and invoice payment succeeded and click add an endpoint. And then you have the signing secret here and just click reveal. And go ahead and copy this signing secret. And now we have to go back inside of Vercel here, select your project, go inside of settings, environment variables, and go ahead and find the Stripe webhook secret here, edit it, and paste the new version inside. It's going to be much shorter like this. And we have to edit the next public app URL. If you try and take a look now, you can see it's HTTP localhost 3000, but we have to copy the new deployment URL and just remove the last slash like this and click save. And that is going to be it. And after you change your environment variables, go back to deployments, select the last one, which is working and click redeploy and just confirm the redeployment. and that is it great great job you finished the entire tutorial thank you so much for watching remember to leave a like share and subscribe and see you in the next one",
  "transcript_chars": 554631,
  "transcript_filled_at": "2026-06-06T16:08:20.498007+00:00",
  "transcript_filled_by": "tk-bulk-groq-retry-20260606"
}