Fullstack Trello Clone: Next.js 14, Server Actions, React, Prisma, Stripe, Tailwind, MySQL

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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 tasky 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 copy 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 businesso 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 nov bar and from here you can select the organiz ization 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 this 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 task ify 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 free workspaces can have up to five open boards for unlimited boards upgrade this workspace so let's fill up this boards right here last one is left and as you can see we have a beautiful set of random unlash 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 say 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 fre 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 going to go ahead and pick another beautiful image from unsplash here and call this board premium board and let's go ahead and click click create and you might be wondering what does this big create button do in the nav bar 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 so this is going to be a true businesso 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 p agage 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 next 14 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 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-- apppp at latest and now let's go ahead and give our project a name in my case it's going to be Trello D tutorial like this and let me just explain what this at latest means so at latest this specifies that we're using the highest or the most upto-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 Sint 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 uh 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 ACC 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 alas 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 going to leave this add sign as the default import alas if you don't know what an import alas 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 Das 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 uh 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 uh installed for you and you should have the public folder and of course some configuration files for Tailwind like this for typescript for Sint and some G ignore files right here before we start this project I just want to install one more package inside and let's go inside of 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 have this so ignore that great so excuse me about that and now let's go ahead and write npx shed cn- UI at latest in it like this perfect and now now let's see the options so would you like to use typescript recommended 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 where is our global. CSS file as you can see here it's placeholder to appg global. CSS so we can quickly check that by opening up our sidebar and let's confirm that we have uh our app folder global. CSS looks like that is completely okay perfect so we can just press enter it's asking us whether we want to use a CSS variables select yes for that and now here's the bit of a tricky part it's asking because where is our Tailwind doc config in my case JS located right and it 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 doc config.js we have Tailwind doc config.txt 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 going to happen if you leave it at JS your project is still going to work but you're going to have one unnecessary file great and now we have the question for the import alas for our components if you left the default alas uh the uh 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 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 lip folder we have our utilus 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 now 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 this two packages so the shetan command 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 shat CN UI and if you're wondering what shat CN UI is my apologies for not explaining uh because I use shaten in all of my tutorials so sometimes I assume that people know but still I think it's important if you this is your first time watching shed cnii is a component Library which is very compatible with next 14 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 mpm run Dev like this and as you can see I have nextjs 14.01 running on a local host 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 uh page so we are ready to do some more work let's go inside of the app folder and let's go inside uh of page. TSX right here so everything that you see on this page right here is actually written inside of app folder page. SX 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 going to 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 uh 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 shat CN 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 das1 and when I hover you see exactly what see CS that is perfect so now we have this working we are ready uh to start figuring out how routing Works inside of nex3 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 examle example like this and inside of example create a new file page. DSX 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 something 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're probably going to see me combine the folder name and the well the fact that it is a page so in my tutorial you're 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 then we have page. DSX in the uh first part of the video I explained that everything you saw on the main page is inside of this uh root page page. ESX which is just inside of the app folder so now that we moved I'm sorry created a new page. DSX inside of this example folder that means that we can visit it by going on Local Host 3000 SL example and there we go we have now have our example page right here perfect and now let's go ahead and let's create another folder and let's 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 hardcoded like slash example but what if we want to Route by a specific user ID for example your user clicks on some other users'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 users folder create another folder and inside with 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 hardcoded like slash example or slash users it's going to be dynamic right and inside give it a 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 uh 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 slash1 2 3 and look at this it's working even though we have not defined this one two three 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. vsx because that can be very useful right we you probably going to want to then uh 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 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. DSX 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 doid and there we go now we have a text which says ID 1 to 3 and if I change my URL to 456 for example the ID changes as well so if you're wondering where did this params come from right we 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 perams and in the future is also going to be search perams 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 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 going to go ahead and write con test page I'm going to go ahead and I'm going to return a div which says test page and I'm going to 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 you is not possible in the URL you can see that they still get a404 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 head and uh create a new folder inside of here which is going to be called I don't know let's say uh something like that and let's go ahead and create a new file inside page. DSX like this and let's do const something page and let's return a div something page and Export the 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. DSX which we have inside of the test folder like this let's just remove it perfect so make sure you just have the parenthesis test folder and then something inside and now let's go ahead and create another folder called uh other like this it doesn't really matter we're just playing around now and create a new file page. DSX 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 one cool thing that you can also do inside there is a reserved file inside of the next framework just like page. DSX but it is called layout. DSX and let's go ahead and create that file inside of this test folder so create a new file layout. DSX like this and as you can see we have an error here which says that the default expert is not a react component don't get scared by this error that is because just like page. TSX layout. TSX requires an default export but we have not written anything yet so let's do that now I'm going to write const uh 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 layer 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'm going to change this to slash something and again and it only says layout well that's 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 uh in next you don't have to import react right uh 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 name space 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/ 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 D r-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/ example this one is not red so that's what layouts are so useful for because they can create well layouts for specific uh routes and when combined with this organizational folders which initially we just thought are being used for you know uh uh 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 Navar with the 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 night bar so that's why these organizational routes are so important to understand because we're going to be working aot lot with those routes and in here in these 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 uh is that I want to give this uh 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 global. CSS file so go inside of the app folder global. CSS and just below this Tailwind directives also don't worry about this warnings so they are 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 comma body comma colum root and just add height 100% like this and once you save that file you can see how now our layout. TSX H full and BG row 500 has taken on a completely different effect perfect and here 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 nav bar like this and there we go this is what these routes are so useful for I'm just going to add an little HR element here so it separates this two there we go as you can see this is how we're going to use organizational routes inside of our layout file we're 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 uh folder structure here so that's what I wanted you to understand uh 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 going to delete all of these files and we are slowly going to repeat exactly what I just did but with a real world example because we're going to be building a real world project so it's I hope going to be more clear then if it's not already and you can of course always visit the official next4 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 we can go back to Local Host 3000 where we have hello Trello in a nice blue c and one more type of route that I want to show you is the API route so in the older versions of nextjs when there was a Pages router uh we had to write an API folder nowadays we don't have to do that in my tutorial I'm still going to 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 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 uh next response and make sure you import next response from next SLS server. Json and I'm going to 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 uh Slash users now you can see that I have a little extension which pries my Json output but you should see the same thing basically an object uh 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 nextjs 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 uh functions like post functions like patch right so that's why we don't do uh default exports in this one because they can have a lot of them in one if you try and write export default uh 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 Local Host 3000 so make sure that you're are on Local Host 3000 and not on any other route and go ahead and remove p page. vsx 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 parenthesis and write marketing and inside create a new file page. DSX 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. DSX 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. vsx is without changing its name because well we cannot change its name it needs to be page. DSX 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 create uh 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 uh sorry it's 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 uh with structuring your project 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 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 uh of children to be react. react node and let's go ahead and simply return a div and render the children inside and Export 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 full so it takes the full screen height and BG slate 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 PT 40 padding bottom of 20 and BG slate 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 enough bar 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 we 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 goal indicates that I want it to go one below another great and let's go ahead and create another div here with the class name of flex items Center justify Center again and flex call 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 CN UI 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 biggy perfect and now we can render this metal 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 uh a class name of height six with six and margin right 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 uh margin bottom of four Flex items Center border Shadow small padding four so we add some space to it BG amember 100 text amember 100 sorry text Ember 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 task ify 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 3 Exel but on medium devices we're going to give it a text of 6 Exel 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 going to write a class name to be text 3 Excel the same it was above and on medium devices it's going to be text 6 Excel so same as above everything we're also going to go ahead and give it a BG gradient to WR like that from and now we're going to write a color I'm not sure how to pronounce this so F 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 uh structure here perfect so now we have that now let's go outside of this div which is wrapping our task management our task if I help steam move and this little gradient B 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 higher 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 task device 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 four Max width is going to be uh extra small but on medium devices Max width is going to be 2 Excel let's Center the text using text Center and MX outo 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 uh all that I want to do is import a component called button from shaten 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 Shad cn- 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 the UI folder we have button. DSX so this is what's different uh with chaten 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 shat CN UI you able to visit the code and you can modify it how ever you want we're not going to do too much modifying I'm quite satisfied with what chatan offers by default but if you're interested you can always change the the focus visible you can change the opacity when it's disabled you can change the variants 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 at/ components UI button like this let me show you how that looks so right here at the top I've imported button from s/ components UI button and this little 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 uh outside manually right using this dot do 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 Alas 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 alest Imports and and then uh 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 tasky for free and it's missing an hre 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 six let's give it a size of large and let's 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're 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 uh 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 Cal Sans font or Cal open sense font I'm not sure what's the name and it is from a project called call.com is a competitor to um calendly if I'm correct and they've created this amazing font for us to use you can Google cal font 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 we're this is just styling no important functionality is being done here but I want to show you how to uh import local fonts using nextjs so in order to do that we first have to import something called local font from next SL font SL looc like this and then go ahead and write const heading font to be local font F like that and go ahead and give it a source of dot do SL do do SLU fonts font. wf2 like that and now we have our font perfect so now we have to add that font uh inside of one of this divs right here so let me just go back to my local host here uh 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 I actually want to 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 uh relative input here is correct perfect so now 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 shat CN so with this CN we can now com combine the existing class names which we have and then append this initi 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 of parenthesis 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. class name 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 popins from next SL font SLG gooogle like this perfect now we have the popins font and let's initialize it in a similar way so I'm going to go ahead and write const text font to be popins we have to give it subsets which is just going to be an option Latin and we also want to give it weight uh 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 it 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. 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 uh uh navbar component our footer component and also one reusable component called l go because we're going to use it in the navbar in the footer and we're also going to use it in the dashboard of our tasky 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 logo ion.com website where you can find a bunch shop free logos just make sure not to actually use them for your business you can use them for your demo project 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 uh that it's called logo. SVG perfect so I'm going to close everything and I'm going to go uh back here and let's go inside of the components folder so I'm going to keep the UI folder for all the shats and 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 Lo logo. DSX make sure it's outside of the UI folder like that and in here let's go ahead and write export con logo like this and let's go ahead and return a link component from next link let's give it an HRA of just an empty slash let's go ahead and give it a div and let's add an image from next SL image and in here the source is going to be/ 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 uh below that and let's add a paragraph here which is going to have uh which is just going to say tasky 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 X2 hidden by default but on medium devices it's going to be visible so from medium and up meaning on desktop devices 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/ 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 uh imported previously perfect and let's go ahead and do that adding con heading font to be local font let's write a source to be DOA /u/ font V2 great and now let's go ahead and let's wrap this class name inside of curly brackets right let's add our CN util and let's wrap this entire thing inside of parenthesis let's add a comma and then we write heading font do 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 nextjs uh which is a second type of folder which can be used to be outside of the routing system so we 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 uh 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 excluded 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 imported into a page so that's exactly what we're going to do inside of the components folder create a new file navb bar. DSX like this and let's go ahead and let's write con Novar and let's return a div saying Novar 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 uh great and now let's immediately add this inside of our layout. DSX right here so instead of this comment here we're going to add that navbar component from do slore components slnb bar and you can see how I imported this at the top perfect and now you can see a little text here which says Novar perfect let's go inside inside of the Navar 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 maxw 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 uh when I expand to a certain point it stops resizing are you noticing that in the Navar right so that's what this class name uh Max W screen 2 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 2XL 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/ components logo and destructure it like this all right and it seems like we have a little uh error here so let's go inside uh of our logo component so that's located uh inside of components UI sorry inside of components folder 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 redirect into the page that we are already on great let's head back inside of our uh navbar component Here and Now below this logo let's add a new div here with a last name of space x 4 so the items between are spaced evenly on the X AIS let's add MD block let's add MDW Auto let's add Flex items Center justify between and W pull and then let's add our buttons so import button from at/ components UI button and write log in and below that add a new button which is going to say get tasky for free like that perfect so you can see on mobile devices they're going to be uh 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 hre of slash sign Dash uh in like that perfect and then we're going to reuse that link already here uh in this other button like that and this one is going to 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 uh 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 uh a last thing we have to do inside of our marketing page is the footer so let's go ahead uh 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 Navar component and paste it inside of the underscore components folder and rename it to footer like this and let's go ahead uh 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 uh we don't need to have the height of 14 like this we also don't need a border bottom instead we need a 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 Center are not needed either and instead let's write BG slate 100 like that perfect uh 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 uh 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 uh my 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 or you can use it as a guide and now let's go back inside of our layout here and remove this commment which says footer and let's actually import the footer component from slore components uh 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 uh seems a little bit off uh I I have a feeling like our footer is too small right it seems too clutter 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 uh 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 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 uh 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 get IGN 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 task ify 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 PS 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 at 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 cannot change uh you cannot remove this file but what we can of course modify it 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 at/ config site as I did here so it's going to be site config do name like that and let's also give it a template and template is going to be uh well open a pactic like this and write a percentage s then write a pipe and then render the site config do 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 root page like on the marketing page if you take a look at my browser uh tab you can see that it says tasky 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 pipe task ify 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 uh let the title be when we create some additional layouts all right and now let's change the description to be site config do 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 versel 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/ logo.svg and hb/ 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 uh 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 uh 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 Das tutorial uh you can of course change this to task ify 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 uh 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 web 3 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 uh 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 this 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 dogit ignore so this is very important make sure you go inside of dog ignore and just below this environment. loal 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. loal 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 G ignore now why is it important that this is inside of your git ignore 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 uh and it is even more dangerous if you uh make your GitHub repository public that way there's a lot of bots and rpers 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 uh create a lot of damage then so that's why it's important that you add do environment inside your G ignore 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 nextjs option in the quick start and click on this purple button continue in Doc perfect and if you watched my previous tutorials you're probably going to notice that clerk has updated their documentation it looks pretty nice so uh in here make sure that you select the app router option like this uh actually 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 uh 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 nextjs perfect and while that's installing let's see what the next steps are so we have to set 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 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 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 uh 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 at clerk nextjs 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 uh root layout right here but that way uh you are going to make your website um I'm not sure what's the proper way you're either going to lose on on on static r 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 uh 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 uh 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 uh and you you have to cre it in the root of your application so create a new file middleware do DS like that and just paste this code inside great uh let's see what is next let me Zoom this in for you so you can see all right so we just added the middleware and before we add this uh button what I want to do is I want to go ahead and create custom signin 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 sign up inside and then a page. CSX 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 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 uh selection of organization and stuff like that so go ahead and create the sign in uh 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. 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 the 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. DSX perfect and then you can just paste this up and replace this two with sign up perfect now that we have that we have to add some other environment variables so let me just uh go back here uh okay uh 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 so go back inside of your environment uh 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 signin URL is/ signin my sign up URL is/ signup 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 do 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 uh and remember this is not part of the URL this is not part of the URL as well meaning that this route is just Local Host 3000 SL signin 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 uh and do that so I'm going to go ahead inside of my terminal and I'm going to run mpm runev 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 going to refresh this page and nothing should change on this page and as you can see all right so something changed I'm going to 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 middle bar detected that we are not logged in and it redirected me to Local Host 3000 SL sign in and you can see this little component which we are going to uh 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 Al 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 Local Host 3000 and there we go as you can see now we are logged out uh and we can still visit our landing page exactly what we wanted but now when we click on get Tas ify 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 login 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 last thanks to the organizational folders and the fact that we know how to use a 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 uh 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 Flex items Center and justify Center perfect so if you got anywhere near uh to you you probably didn't guess the exact class names right if this is your first time guessing this but if you manag 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 now big is you're going to get it uh 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 creating 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 uh 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 redirected back to this page later in the tutorial this is not going to work exactly like that if we are logged in we are only going to be able to access the dashboard right we're 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 localhost 3000 SL 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 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 nextjs like this import current user from Clerk nextjs and that returns a promise as you can see so it means that we need to wrap this inside of an asynchronous function and avate 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. first name and there we go you can see that it says user is Antonio because I am logged in perfect you can also try another hook called uh out so it's just out from Clerk nextjs 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 uh 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 it out so I'm going to remove all of these things I'm going to remove this I'm going to remove this 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 uh those utils which I just tried instead we have as you can see I have this autoc completion from GitHub co-pilot to give me use out so let's try that so const use out from clerk nextjs 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 D structure 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 uh and remove this we can remove these two hooks and we can remove everything inside and instead instead let's render clerk uh sorry user button from clerk nextjs from the same package right and uh 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 uh 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 Local Host so just go back to Local Host and what I want you to do is go ahead uh 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 want to 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 SL protected I'm redirected to login exactly what we wanted now I'm going to go ahead and log in and now I am a 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 uh 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 uh that our authentication is actually working perfect great great job so far and now we're going to work on uh creating our organizations inviting users uh 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's 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 uh 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 dorg like that and inside create another folder with double uh square brackets and catch all route inside select dorg again and then add a page. DSX so make sure it's inside of this clerk folder so this select or 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 Clerk nextjs 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 uh and do that and now that I'm logged in I'm going to go and manually go to/ select dorg 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 uh you know a name name for example F 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 we remove this personal account so this is going to be primarily a businessto business software so let's go ahead and write hide personal inside of this component and now when you refresh select org as you can see we no longer have uh 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 uh 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 uh 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 uh 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 uh 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 uh another organizational folder besides clerk which is going to be our dashboard like this and inside go ahead and create uh 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 con organization ID page return a div saying organization page like this and Export default organization ID page perfect and as you can see I know longer have that error now because my URL matches SL 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 the folder old 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 parenthesis 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 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 happen happening so that's why we created a specific uh organizational route but at this time just for the dashboard so for now what I want to do is I actually want to show you how to fetch uh some uh um organization information so you can use the out which we already played around with from Clerk nextjs and from here besides the user ID you can also extract the or ID like this so let's go ahead and write organization ation 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 going to remove this for now and let's add the organization switcher component import that from Clerk nextjs 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 uh 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 uh 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 nov bar and add all of those compon 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 uh which are going to be inside of the dashboard uh organizational folder so go inside of this layout and add cons dashboard layout and you already know that we can immediately extract children from this uh sorry let's give them a type so children are a type of react. react node and let's go ahead and return a div and let's render the children and we also have to export default dashboard layout and let's just not misspell that all right and there we go no no more uh 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 nbar component from here but once I save this I'm going to get an error because the enough bar 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 navb bar. DSX like that perfect let's export cons navbar and let's return a div saying Novar 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 N bar from the marketing page right that's the different one we're going to import this closest to us _ components Novar and once you save we have a nice little text nbar here Perfect all right and now let's go ahead uh and let's go inside of this knv bar and let's go ahead and slightly uh style it right so I'm going to give this D A Class name of fixed Z50 top Z w- 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 a class name of flex items Das 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're going to do that later first we have have to implement the desktop sidebar right and inside of here go ahead and open a div uh with a class name hidden MD Flex 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 at/ 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 nav bar 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 uh outside of this div right here which holds our logo just go ahead and import a button from components UI Button as I did uh 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 AO 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 uh 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 uh 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 H 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 you know I don't think it's a big deal you can play around with it uh if you want uh great all right so now that we have that what I want to add outside of this div so just just here in this empty space before we close the nov bar is a new div with a class name of ml D aouto Flex items D Center and GAP X2 and inside of here we're going to render our organizational switcher sorry organization switcher from clerk nextjs so make sure you import organization switcher from clerk nextjs 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 going to go to/ organization ID after leave organization URL we're going to go to/ select dorg like that after uh what's left after select organization URL that's also going to go to SL organizationid 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 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 SL nextjs so import this user button from clerk nextjs right here and go ahead and give it an after sign out URL to be a slash and appearance is going to 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 Navar here perfect but we're not exactly finished now because I can still go to local close 3000 and as you can see I can visit my landing page but I don't want that to happen right uh in fact I always want to be on one specific organization right even if I haven't selected any organization I want in in that case I want to get redirected to that SL select org or 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 you know this is a really cool feature that clerk has this organizations so we get to play around with it a bit so let's go ahead and write after out this is a fun 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. 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 uh visiting the landing page in that case I 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 or 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 pet is going to become make sure you write this inside of back text SL organization SL individual out. organization ID right so we 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 SLS server so just make sure sure you add this little import so return next response uh do redirect or selection like that perfect and now outside of this big if clause which we've just written we have to handle some other uh regular cases which we might have so if we are not authenticated and if we are not on our on a public route we have to explicitly tell what to do and that's going to be very simple we're just going to return redirect to sign in from clerk sln nextjs so just make sure you import redirect to sign in from Clerk nextjs 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 they 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 Al is has the user ID and if we don't have out org idid and if request. next url. paath name is not/ Select dorg 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 or selection is new URL / select dorg and request.url and return next response. 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 copy it directly from my GitHub and now let's try this out so I'm going to go ahead and attempt to go to Local Host 3,000 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 uh go back to Local Host 3000 so I'm allowed to go on the landing page and now I'm going to log in but a completely different account that has never selected any organization let's see what happened then there we go as you can see I'm redirected directly to/ select dorg right and let's try and clean let's 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 bit 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 shat CN sorry in the UI folder so components UI this is the shat 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 B- sky- 700 like this text- primary D foreground hover BG sky- 70090 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 both of these buttons a new variant which is going to be as you can see now we even have the autocomplete 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 for in a type of popover perfect so you have uh started working on organizations your middleware is working and you have completely completed the Al and now it's time for uh to start working on the sidebar here and actually rendering some information about the users's current uh organization great great job all right so now that we have our nav bar 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 nav bar 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. DSX so just make sure that it's not inside of organization ID we're going to have a separate layout for that as well uh 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 nav bar 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 render the nov bar 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 four and Max width is going to be 6 Excel 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 two Excel so if we are on a large monitor then Max resizing is going to be uh W screen XL and MX AO to make sure that uh 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 d0 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 uh 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 its fixed width which we decided right here great and now before we start and build this entire sidebar thing uh 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 going to 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 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 act actively take a look at our uh URL and then uh 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 Con 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 uh 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 going to do is we're going to create a component called organization control but we're going to spell it as or 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 or- control. DSX let's mark this as use client let's go ahead and import use effect from react let's go ahead and import use prams from next SL navigation and let's go ahead and import use organization list from from Clerk nextjs and then export con or control it's going to have nothing in its props and in here we're going to return null because this is purely going to 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 D 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 pam. 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 pam. organization organization ID 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 the org control component you did not misspell pam. organization ID 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 we never defined which are the types uh 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 going to have some issues so now let's test out that thing that I was talking about so I'm going to choose this test organization I'm going to 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 and 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 DTS this 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 shat cn- at latest at 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 two installed perfect and then you can go ahead and run mpm run Dev like that perfect now let's head back inside uh of our organization layout so right here where we 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 uh an error here just make sure you refresh your Local Host every time you shut down the app and rerun it again uh 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 nov bar so why here why not create uh components inside of this folder if that's where we use it well because we're going to reuse it in the nov bar as well in a different component called mobile uh sidebar you can see that I have a comment here for mobile sidebar but we're going to 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 con sidebar and just return a div say 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 doore 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 uh and let's uh start developing this so first I want to create an interface sidebar props which is going to hold the storage key and here 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 uh collapses itself after a rerender right so if we change a specific um Oran 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 SL 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 going to need use organization and use organization list from Clerk nextjs and then let's go ahead and let's import the button from add/ 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 going to open a new one here and write npx shat cn- 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 from radx make sure you always import from at/ components whenever radx is offered in your Imports make you 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 radics and I have imports from at/ components so always make sure you're using the components not radics because you're not going to easily know notice what's wrong right there not going to 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 radics great perfect so now we have this and let's go ahead now uh and let's add all the hooks we're going to need and all the other stuff so first let's uh create a state which is going to be connected to our local storage which is going to keep track of which accordion is is expanded and which one is not so const expanded set expanded and this used local storage works very similar to use state so you can see that we have the expanded set expanded and now we're going to add use local storage 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 this 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 you know Trello project T sidebar state so why am I even using this as a prop right why not just hard code that here well as I said we're going to reuse the sidebar in our uh drawer for mobile for mobile devices and it can get pretty inconsistent if you use the same storage key for both so we're going to going to have something like T 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 uh 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 the structure things uh 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 component so that's what the ls does and let's get the active organization from use organization and while we uh 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 uh 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 nextjs documentation right so this isn't something anyone would expect you to know right you have simply have to take a look at their organization hooks uh documentation and from there you can see exactly uh why we need it and what it does basically it's kind of a imagination 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 uh 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 uh if the current key is expanded Ed 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 uh 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 all 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 head 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 that is loading uh sorry this is not uh this one we write without this uh 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 uh later we're going to actually style it to look something to look like something uh great so let's just go ahead and refresh to see if uh 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 uh and style it later all I want to ensure is that for a second our sidebar is loading great and now let's go ahead and let's actually style uh this div here so first off I want 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 whoops 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 hre of/ select dorg and inside we're going to render our plus icon from Lucid react all of the things basically which we imported uh 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 S ch child prop a type of button a size of Icon a variant of ghost and a class name of ml AO so is push so it is pushed to the right uh great and now let's go ahead uh and see if we can already uh take a look at that 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 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 radic one and let's go ahead and give it a type of multiple so multiple accordion can be active at the same time let's give it a default value of default accordian 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 accordian component so that's why we create this little uh 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 uh 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 1 2 3 true into an array which just going going to hold 1 through three so that's what this default accordian value does and the reason again why we need that is because that is what this uh accordian 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 Y 2 perfect and then inside let's go ahead and let's do user memberships do dat. 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 do uh 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 or organization ID here perfect so now we're going to create a component called nav item which is actually going to render that uh 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 is expanded prop to be if we have this organization ID inside of the expanded uh 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 this 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 D item. TSX perfect so let's mark this as use client as well and let's go ahead and Export con naap 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 slnv item and I'm going to uh 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 you can 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 here so let's just fix that by adding as organization and import organization from /nv 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 uh made exportable like this there we go and now you should no longer have any errors here perfect so let's go ahead uh and let's style this so instead of a div this is actually going to be an accordion item from at/ components UI accordion so let me show you make sure you have this imported again not from radics 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 uh accordion let me just collapse this ones great so accordion trigger and inside let's go ahead and give this un onclick which is an arrow function which calls on expand and uh 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 d700 rounded MD hover BG D neutral Dash uh 500 sl10 transition text- start no dash 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/10 and text Sky 700 100 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 div with a class name of W7 and H7 and relative and inside we're going to render an image component from next SL image so let me show you where I imported next SL 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. 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 uh to the uh next image component but you don't modify the uh 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 uh 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 nextjs to use images from that source so let's go ahead inside of next. config.js here and let's write images remote patterns and let's open an object so protocol is going to be https and host name is going to be image. clerk.com like that great and now what I recommend you do is just restart your application so I'm going to do that mpm runev 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 uh 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 uh what we want but besides rendering the image we're also going to render the actual organization name so outside of the div which is wrapping an image add a span which is to render the organization name and give this a class name uh which is going to be font D 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 uh uh accordion content but in order to do that I first want to create uh 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 it's going to have a class name of H4 h-4 W-4 and Mr of two and it's going to have an HRA and open btic for this one and also let's add a comma here so we don't have that error and the hre is 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 this 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/ activity great and let's go ahead and copy and paste this the third one is going to be settings it's going to use the icon settings and it's going to go to slash settings so this part pretty much always stays the same uh 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 uh from next SL 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 SL router and you can see that there's no error here right but you can no longer use this uh if you're using the app router you have to use next SL navigation so whenever you're using use router if you have some weird errors and nothing is read in the code uh just make sure that your use router is coming from next SL navigation and besides use router let's immediately import use path name and now let's go ahead and add path name use path name great and now let's go ahead and let's add our const on click to accept an H rev which is a string and router. push that very same hre great and now we can finally go inside of uh outside of accordion trigger and render the accordion content again from add/ 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- neutral d700 like that and then let's go ahead and render rout uh routes. map get the individual route and go ahead and render a button component from add/ 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 hre would be better a size is going to be smaller on click is going to be an arrow function which calls the on click and passes in the route hre and class name is going to be dynamic so the default ones are going to be W full F normal justify start pl1 and margin bottom of one and then we're going to have if path name is equal to route. hre then we're going to make it bluish so BG sky 500/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. icon so route. icon don't accidentally do router right I I wrote that a couple of times so route. 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 uh this accordion stays open so the user doesn't have to always manually expand that especially if they're going to have a bunch of workspaces and perhaps organized exactly you know the ones they want to see open and if you try and click on this you're going to 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 you know check the billing you can see we go to slash billing and if you click on the boards itself nothing is going to change but if you click on the boards inside of another organization you can see that it's switches to that organization so take a look at my navb 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 nov bar you can see I left a little to do here to create a mobile sidebar so if you don't have this commment to help you find that first of all go inside of this Novar component and find this logo component and outside of these two divs so just 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 in my previous tutorials some people told me that when I create these mobile sidebars there it's not exactly the best user experience because it kind of stays open after you change route so that's why this time uh 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 um what's the name proper components to do that from shaten but it simply kind of feels like too much work in spaghetti code to do it I don't know uh 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 uh mpm install to St that so make sure you have that installed and we're also going to need a sheet component so let's go ahead uh and let's add npx shat CN UI latest ad sheet like this make sure you have uh that package and this and then you can mpm 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 Das sidebar. TS like that and let's import create from two 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's extract the set and then immediately return an object like that so make sure you wrap it inside of additional parentheses right if you've never seen this syntax let me just briefly explain if you do this this opens uh 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 parenthesis and then an object and this tells uh JavaScript you know okay so this is immediately returning an object and let's add is open to be false let's add unopen 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 so app platform dashboard components navbar and in here I'm removing this comment again so uh 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 DSX let's mark it as use client and Export con mobile sidebar and let's return a div saying mobile sidebar and now we can go back inside of the Novar 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 uh that uh hook so first we're going to get extract uh on open which is going to be use a mobile sidebar we're going to get the state and we're 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 SL 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 uh by to prevent hydration errors especially when working with suant States and uh components like models or sheet components uh so let 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 null so why am I doing this so in next uh JS even if you mark something as used 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 wrong but this is how I understood it and why these hydration errors even happen when using used client components so as far as I understand each component even if it is in Ed client is going to be server side rendered at least the first iteration of it right so what happens when you're using this models and sheets and dis type type of components is that on the server side it has a specific State like closed right and then on the client side it 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 serers side rendered is by using use effect because use effect will not run in that server side rendering iteration so basically what we are 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 uh 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 uh 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 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 um 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 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 this 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 uh from components UI button and inside I want to render the menu icon from Lucid react so make sure you have uh that imported let's give this menu a class name of H4 nw4 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 uh and let's go ahead and give it a variant of of Ghost and the size small great and then below this button let's open up a sheet component from component UI right here so again make sure you don't accidentally import it from radic 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 slidebar 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 a class name of padding two 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 uh 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 so I'm going to go ahead and give this to this one margin right two there we go that that looks just a tiny bit better perfect so you've finished the entire thing you can see you can do anything on mobile uh 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 P 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 uh 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 uh move them away from each other as far as possible using justifi 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 h10 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 a knit 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. skeleton is a function skeleton nav item 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 a another div let me just close this one all right and give it a class name of W10 h10 relative and Shrink Z so this is representing our image component and let's add a scan 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 uh 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 Y 2 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 do 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 so 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 so I'm 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 uh 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/ settings so let's go inside of the uh 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. DSX inside const organ I actually call it settings page and return a div saying set 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. hre and as you can see the hre let's find my uh routes right here the href is/ 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 nextjs 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 we going to add a border to be 1px solid E5 E5 E5 and we're going to 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 its 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 it necessary attributes to make it look more in line and more according to our shaten component 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 are slowly going to abstract them into our own custom hook and our own custom uh util which is going to make those server actions completely type safe and also validated using Zod so the first thing I want you 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 in it 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 UR 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 Planet scale Planet scale offers a completely uh free unlimited uh database but it does require a credit card to prevent abuse of that free tier uh 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 Planet scale so the only important thing is that you use uh some kind of SQL if you want to you can set up your own local Docker MySQL instance or you can Google Planet scale free alternative the reason I'm using Planet scale 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 Planet scale 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 uh how to set up MySQL locally because the only thing we need from planet scale is this database URL that's the only thing we need okay so head to planet scale.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 uh 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 same way going to create our database URL so go ahead and click create password the name does not matter uh if you want to you can copy the username and password and store it somewhere safe uh 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 um again if you forget it it's it's not a big problem you can always generate a new one for the same database um 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 uh 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 uh client so let's go ahead uh we can skip over this we don't need that what we actually need uh is this sorry I I scrolled too far we have to modify our schema Prisma so let's take a look at our schema Prisma to see the differences so I'm going to go inside of Prisma folder schema. Prisma and as you can see we have to change the provider to be MySQL we have to change the url to be uh 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 Planet scale you can do this manually just by looking at what I did Prov provider is my SQL URL is the environment variable 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 Planet scale 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 up 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 of 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 head inside of your terminal here and you have to run the following command MPX Prisma 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 it means that it's added inside of your nod 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 do board you're going to see no errors because it's added low Al 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 npx Prisma DB push and npx Prisma generate make sure that you also restart your application so if you haven't already shut down your application and then just mpm run Dev again but we're not going to do that just yet because the first thing I want to do is do mpm install at Prisma client so this is going to allow us to use Prisma in our application and now we can mpm 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 lid folder create a new file db. TS go ahead and import the Prisma client from at Prisma client and write export con DB to be Global this. 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. environment. node environment is not production so if we are either in development or local or something like that in that case Global this. 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 you know the most primitive way to export our database which uses the new Prisma client so why do we do all of this uh for our development mode or anything that's not production well that's because nextjs uses hot reload and during that hot reload uh new Prisma client will be initialized multiple times and that's going to create a warning inside of your project so what we do is we we do the following so when we Define the database variable we check if we have it stored in global this. 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 Global this. Prisma and then 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 uh 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 want to 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. DSX 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 uh 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 uh if we can see that right here okay it's right here 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 it uh 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 uh 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 Define by adding use client at the top so now this becomes a client component and let's see the difference now so I'm going to 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 used client and add this console log right here refresh your page and open up your uh console uh 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 uh 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 stay the same but you see uh 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 used client at the top uh so if you watched any of my previous tutorials you know that with server components we can do cool stuff but like directly fetching fetching the database right so 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 conso log uh for for now I am triggered and let's add this Asing 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 pressed 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 uh 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 uh if you come from my previous tutorials this is a new thing for you but if you came from purely a uh 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 form data. getet 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/ Le 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's just mark this form data as string there we go and before we run this let's go ahead inside of our terminal here I'm going to open a new one and I'm going to write npx Prisma studio so let me just expand my screen so you can see it in one line npx Prisma studio and press enter and that's going to be running the Prisma Studio which is basically a UI for our database as you can see it notice 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 uh and check out what's going on all right so my apologies I forgot that we have to put a weight here so make sure you put a weight inside of your uh 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 Val 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 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 EX 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 run my app again make sure you have Zod installed and refresh your Local Host 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 create board. THS actually I'm going to use create dashboard. vs like this and let's go ahead and paste that here and let's remove the this use 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 addlib 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 confir 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 uh 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 uh and in fact we can use the button component from components UI but button like that there we go this looks a bit better so let's go ahead and refresh type 1 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 do string like this great now let's go ahead and use 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. pars and then I'm going to write the title to be form data. getet title like that and as you can see now we no longer have to write that as 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 uh our input is a string and that validates this pars 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 SLI db. board. find many and that's actually the only thing we have to do and since we're using a weight 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 do 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 uh of well in inside of your uh create action so what I'm going to do for now is I'm just going to copy uh 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 like this and I'm going to pass in my URL but I'm going to go ahead and remove the Local Host part of it so just SL 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 uh 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 uh created in real time here and updated so now I want to show you how to we learn how to create but how about the 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. DSX and let's go ahead and copy this div which represents our board and inside of this organization ID where we have the page. DSX uh let's go ahead and just create a new file called board. DSX 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. DSX and instead of this div we can now render that board component and let's just make sure that we uh do an export here so export con board and then we're going to be able to import that from do/ 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. tile and ID which is board. 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's give 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 uh because we are we 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 uh our sorry for deleting our um boards so let's go ahead inside of actions and create a new file delete dasboard dots 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. board do 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 do uh. 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 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 going to click delete here and there we go you can see because uh it revalidates the path it's deleted in real time so that's how you can pass in a specific ID uh 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 nextjs documentation is Progressive enhancement which as I understand it allows us to do this 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. DSX where we have uh 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. DSX I'm going to mark it as use client and I'm going to export con form and I'm simply going to render uh 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 SL form the same way you did with/ board and we don't need these two 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 domum 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 St 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 use form State here and now let's go ahead back inside of this create function and modify it slightly because when used in use form state which is going to give us like some p ending State and some errors we have to also modify this but just before we do that we are 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 uh let's see is it a message or invalid I think it's a message I'm just going to say uh 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 errors uh 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 is 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 um this create board pars to use safe pars like that so pars will throw an error and break the application whereas save pars 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 uh not validated fields. 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 field errors 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. 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 SL navigation so make sure you import 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. CSX 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 uh 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 uh and let's 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-all and space Y 2 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 now and inside of this div let's go ahead and iterate our state. errors. tile. 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 uh 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 um how to create uh uh uh errors right right but one thing that you still don't know how to do is how to handle loading States right when 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 uh. CSX or let's call it form input. CSX like that and let's go ahead and do export con form input and let's just return this uh 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 uh inside of a div like this great so now we have to create uh 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 uh 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 or 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 is 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 slash uh form input so make sure you add that import here great and let's pass in the errors which are going to be state. errors like that and I'm just going to pass in uh 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 going to 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 uh status from react Dum so you can see no external libraries here we're 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 uh to be when it's pending and let's actually replace this input to use the input from shaten UI we don't have it so head into the terminal here let me just shut down my Prisma studio and run npx shed TN UI lat 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 uh 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're using the input from shaten uh 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 uh 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. CSX so I'm just creating a random components here and let's do export cons form button and let's just return this items here let's import the button from shat CN 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 do that so we wrap up this thing so we have this board component where we have the delete button and I'm going to create a new one called form delete. TSX so let me just show you there we go form delete. DSX export const form delete and let's just import this button from components UI button and let's go ahead and let's add con pending from use form status from react Dum 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 uh 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 uh 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 this 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 this we have to create a structure for it keep in mind that you we are all probably uh a little spoiled by this amazing libraries like react query uh 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 uh us mutation kind of uh to create our reusable server components and basically there is not going to be a single API endpoint uh 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 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 uh 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 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 going to 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 cover cover 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've prepared something so we're going to uh 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 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 uh an error or for example it can be a specific type from Prisma database we're also going to create a file called schema. CS 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 be able to uh 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 um 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 the fuse effect to I don't know just render a success message or something so we're going to have an unsuccess callback which is going to give us the data uh inside of its params here and that's going to be a type of output which we defined all the way here so it's going to be completely type safe from start to finish on error is going to produce an error from the server and on complete is simply going to be something like finally right so regardless if we succeeded or not uh that's what use sorry that's what on complete is going to be so let's let's go ahead and let's now create this create safe safe action. TS so I'm going to go ahead inside of I'm 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 save action. 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 the 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 uh to that uh 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 uh 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 uh 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 con create safe action is going to be a t input and T output and that's going to have a pram of schema which is z. schema pass inside is going to be T input besides the schema we're also going to have uh 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. safe pars 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 filled errors to work with if this scheme of validation fails using Zod so let's return the field errors you can see how we already have typescript here and we're going to return validation result. error. flatten do 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 do data like that perfect so we finished our save action Handler and now we're going to go ahead and modify the function to actually uh be able to uh well be used by this create save 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 doob and let's render uh 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 uh typ is required as well and then let's add a minimum value of three and let's pass in the invalid type sorry the uh message inside to be title is too short like that perfect so we now created our uh 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 typ 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 save action and finally let's import the actual schema which is create board from do/ 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 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 do/ 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 do/ uh types and let's go ahead and open this Arrow function and first thing that I want to check if I if we have user ID or not from out clerk nextjs so we're all already going to start implementing the authentication inside of this Handler which you can IM imagine as an API route so if we don't have user ID in that case let's just go ahead and return an error and you can see how we have type safe type safety here right because we return uh this return type which can have field errors error or data which is everything we defined inside of this types right here and because we 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 uh 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 have some fielded 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 uh 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 SL lib DB I'm just going to move it here so let's go ahead and give it data to just be title great uh 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 or internal error something like that perfect and now let's go ahead uh 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 Matic SL board SL 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 perfect and then last thing we have to do is export cons create board to be create safe action which I just imported from at/ Li create safe action and in the first argument let's pass in the create board schema so make sure you import that from do/ 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 in uh is going to be a promise which is going to return our title uh well is 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 uh and let's just save this form it doesn't matter if you have an unsaved form just save it 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 call back from react itself and then let's go ahead and let's import action state and field errors from at/ lib SLC 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 call back 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 uh 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 T input T 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 uh 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 used State and that use state is going to have a type of field errors and the generic inside the input great uh and by default let's give it undefined and besides T input it's also sorry besides filled errors give it a pipe of undefined so that's also a type of uh a type of well prop it can work with great so just make sure you have uh it written like that besides filled 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 an undefined by default and cons 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 these fills 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 his loading to be true and then we're going to open a 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 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 Doon error question mark. result. error and we're going to do this same thing but for the data here so options on success result. 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 uh 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 going to have any callbacks because obviously something went wrong out of our reach if we have field errors it means that uh something went wrong with the validation and we don't have a call back for that because well we don't really need one we're simply going to extract the field errors and then we're going to 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 going to add a options call back on error because that's not the type of error which we're going to render under the input field that would be an error which we would 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 going to add a call back on success so we can work with that record like redirecting to a specific ID of that record or simply showing 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 last thing we have to do is actually uh 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 f feel like you've made some mistakes you can always visit my GitHub and take a look at these uh 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 uh in my original source code perfect so what we have to do now is we actually have to use that uh 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 uh create instead what we're going to do is we're going to uh 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. getet 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 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 conso 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'm going to go ahead and I'm going to refresh my app here and I'm going to add test click submit and there we go you can see that my call back 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 uh an error for example well actually let's do the quicker thing which is just entering a very short name like the 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 uh 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 sent 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 uh of of course this is my first time working with server actions and I just kind of experimented with this if you have any uh criticism I will be very happy to hear it especially if it's optimization wise or security wise that would be great uh and you know your general opinions about this hook feel free to leave them in the comments below uh great great job now that we have our abstraction around server actions let's go ahead and let's create some custom comp 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 uh that is going to behave similarly to that one so first things first we're going to mark it as used client because I already know that we're going to use that uh 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 uh 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 uh 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 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 uh 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 uh 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 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 the structured 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. display name to be form input and there we go uh great and now let's go ahead and let's actually uh 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 shatan label component so go ahead and add npx shaten UI at latest AD Label wait for a second for this to uh be added to your project and there we go go back inside and now we can replace this D with a label which we can import again not from radic but from do/ UI label as you see right here but I don't like uh 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 a HTML 4 to be ID so these are going to be some uh 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 let's actually uh render the input component which I just imported from do /ui input but I'm going to replace it with SL 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 D 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 B 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 CN from at/ Li user let's add the CN here let's give it a 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- described by to be id-h error like that great and now what I want to do next is I want to create a reusable components for rendering our errors so let's go ahead and do that we're going to do it uh 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 uh 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 D errors. DSX inside and let's go ahead and let's import X circle from Lucid react so whenever you're working with Lucid react I've notied that you can do both X Circle and you can always do ex Circle 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 con form errors we get the ID and the errors let's give it a d form 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- error it's going to have area- live to be polite and we're going to have a class name of of mt2 text extra small text rows 500 great and now inside let's go ahead and write errors question mark open parenthesis ID map again with a 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 to border border rows 500 BG rows 500/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 right two perfect and now we can go back inside of the form input and we can import the form errors component from do/ form errors and since they are in the same folder I'm not going to replace that with the alas because I think this is good enough uh 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 do/ 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 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/ 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 Local Host 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 submitting you can see that it still has a little uh 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 uh oh and it looks like we are still rendering the hardcoded 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're not using it at all so label instead of the hardcoded 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 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 conso 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 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 it's the error is not clearing because we uh 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 uh there is just this uh errors that that's still showing up but we're going to work with that later uh we're going to see if it actually creates any problems uh or not for us uh great and now I want to do uh 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 uh 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. 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 Dum let's go ahead and and let's import CN from Li utils and let's import the actual button component from do/ UI button or slash components UI button let's create the interface uh form submit props it's going to have children which are type of react react node disabled which is an optional Boolean class name which is a string and variant which can be either default destructive outline secondary ghost link or primary and let's export con form submit here and let me just assign this props so form submit props and let's extract the children let's extract track disabled class 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 class name so in case you have an error with this variant prop it probably means that you have a mismatch between uh what I what you wrote here and what the button actually accepts so just make sure that you don't have any typers here you can always visit my GitHub or 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 uh what variants you have uh 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 uh 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 uh maybe I forgot to export it h components form oh I name 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 uh great great job so you just finished um creating this reusable form components uh what we're going to do next is we're going to create a popover 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 uh 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 save 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 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 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 the 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't need form delete anymore and we also don't need the individual board. CSX so inside of my organization ID you should have the underscore components page uh sorry folder the settings page the layout out 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. DSX and let's go ahead and Mark this as use client and let's create an interface info props and 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 ative info actually export con info sorry all right go back to page. vsx and then you can import the info component from _ 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 nextjs 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 going to go ahead and return a 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 w 60 pixels height of 60 pixels as well and a relative and inside we're going to render an image component from next SL image so make sure you have that imported here it is a self-closing tag so let's go ahead and give it a Field property let's give it a source to be whoops a source to be organization question mark image URL let's give it an ALT to be organization and let's go ahead and give it a class name of rounded MD and object D 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 CL 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 uh 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 B 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 scale component from add/ 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 Y 2 and inside add a new skeleton component with a class name of H h-10 and W2 200 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 W H4 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 800 pixels and now let's go ahead back inside of this is loaded and instead of rendering this we're going to render info. skeleton so save that let's refresh our local host and there we go you can see how now 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 component and just below the info component go ahead and add a little separator we already have that and you can import it from at/ components UI separator again just make sure you don't accidentally import it from radic and just go ahead and give it a class name of my4 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 a class name of bx2 M DPX 4 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. DSX and let's go ahead and add uh expert 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 Bard list for now go back to page and then you can import board list from at/ 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 uh the UI for that so class name for this first VI 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 two 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 right of two 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 it 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 2 on small devices grid calls three on large devices grid calls four and GAP is going to be four and I forgot to added a little space here so this should be joined LG grid calls 4 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 uh 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 Das video relative h- full w- full BG muted rounded small Flex flex-all Gap dy-1 items Center justify Das Center so everything inside is going to be in the middle hover opacity d75 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 uh 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 we're 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 tool tip 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 uh but let's go ahead and create that component now in order to do that first we have to install the tool tip component from shaten so let's go inside of our terminal here and let's Rite npx shaten UI at latest at tool tip wait a second for this to install and after it's done we're going to create a component uh called hint great so it's right here let me just zoom back in and let's go ahead and let's go inside of our components folder and create a new new file hint. TSX let's go ahead uh and let's import everything we need from at/ components UI tool tip so we're going to need the tool tip itself we're going to need the tool tip content the tool tip provider and the trigger so tool tip content tool tip provider and Tool tip trigger now let's create an interface in props children we 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 con 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 tool tip provider inside add a tool tip itself let's give the tool tip a prop called delay duration zero so I don't want any delay with opening the tool tip right I want it to instantly open when the user hovers on it let's add the tool tip trigger to be our children and outside of the trigger go ahead and write tool tip 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 the board list and let's go just below this fan element where we wrote five remaining and add a hint component from add/ components hint so let me just show you where I imported that here at the top so hint from at/ 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 to right -2 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 backck like this and inside uh I want to write three workspaces can have up to five open boards for 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 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 uh 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 uh I don't think we have it we're just going to check now so npx Shad cn- at latest at popover looks like we don't have it so it's installing popover great once that is done go back inside uh of your components folder here inside of the form folder and create a new file form Das popover do TSX so the reason I'm putting this form inside of the uh reusable components folder is because we're going to reuse it both in the nav bar as you can see with this little plus icon here and also in this board list component so that's what why I decided to put it here so let's go ahead and let's mark it as Ed client and now let's go ahead and let's import everything in we need from at 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 uh and let's go ahead and let's import form input which we have from do/ form input and let's import form submit from SL form submit great uh and now what I want to do is I want to go ahead and just uh 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. react node is going to accept a side which is 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 the 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 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 side 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 uh 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 s SL components forms uh sorry form form popover great so go ahead and 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 cont 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 pop over 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 shaten 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 do TSX so you can either find it in the folder or you can just use this form popover component which we are working in hold 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 con popover close to be popover primitive. close and then we have to export it at the end so after popover content add pop over 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 pop over close and inside of here add a button component so I just imported that from data/ UI button but I'm going to change that to SL 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-o w-o padding to absolute top-2 wr-2 and text- neutral 600 and let's also give it a variant of ghost and let's 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 Clos 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 word 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 error from our use action hook which we have imported pass in 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 conso 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 onsubmit is going to work with form data which is a type of form data go ahead and get the title which is form data. getet title as string and let's call the execute function and pass in the title and now let's assign this onsubmit to be this forms action great let's go ahead and refresh this let me open up my uh 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 uh 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 mpm inst sty sonor very simple just like that and then first things first let's go uh inside of our layout component uh right here in the app uh 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 uh I think it's from sonor so let's go import toaster from soner 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 soner so make sure you add this toast import we just learned how to import toaster but this time we're importing the toast from sonor and let's run toast. success and let's write uh board created and for the error let's write those do 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 form picker and that's going to say 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 developers and go ahead and register as a developer after you do that go ahead and go back to unsplash.com 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 to the process of creating a new one so find a button which says new application and you have to agree to all of this uh 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 tr- tututorial and I'm going to go ahead and uh do the same thing for the description and click create an application and as you can see now it redirect Ed us to this uh page where it says that we can apply for production so you can do that of course um if you want to if 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 5,000 requests per hour if you need more than that uh you can uh well get in touch with the theme of course uh 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 lip folder and create a new file unsplash dots and inside of here I'm going to go ahead inside of my terminal first and I'm going to run npm spash npm install unsplash DJs like this so a package from the official unsplash documentation and go ahead and import create API from unsplash DJs and then export con unsplash is going to be create uh API and in here we're going to pass in the access key to be process. environment. nextore Public unsplash 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 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 uh and I actually want you to to keep this open for now just because I want to show you how this is measured right here uh 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 want to go ahead and add this component uh 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 do/ form picker and let's go ahead and give it an ID of image and errors of field errors great so now when you click here you should have a text which says uh form picker just above the board title so we're now slowly going to style it uh so it actually renders the images so we'll go back inside of uh form picker here and let's go ahead and let's import our unsplash library here so import unsplash from at SLB 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 assign the initial images here so const images and set images are used 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 uh and let's create a use effect which is going to fetch the images using the unsplash AP so use effect 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 let's go ahead and let's write con result to be await on slash. photos. getet random and now let's give it a property to only read from specific collection IDs and that's going to be 31799 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 uh 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 unlash 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 h images let's simply Define them as result. response as array record string and any and then let's write set images to be images and I just misspelled images like this um 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 log uh sorry console. error fail to get images from unsplash great and now let's go ahead and let's simp add uh a loading State here so we indicated the user that we loading the images so is loading and set is loading it's going to be used State and by default it's 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 uh 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 loader 2 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 Dash spin and let's go ahead and give it a class name of padding six Flex items Das Center and justify Das Center uh 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 con selected image ID and set selected image ID is going to be used State null and let's also extract the pending state from us use form status because this component is also going to be used exclusively inside of forms so we can do this and then we can uh 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 uh great so now let's go inside of the uh 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 Das call -3 and GAP -2 and margin bottom of two let's go ahead and let's do images. 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 dynamic add CN from add/ li utils like this and let's go ahead and add the cursor Das pointer relative aspect Das video group hover opacity -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 um perfect so now let's go ahead and let's uh 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 D cover and rounded small and let's give it a source of image. url. Thum 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 uh organization image from clerk 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 uh 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 uh that we are hovering over them and that we can click on them but let's take a look at our um requests here so I'm going to refresh and there we go you can see that I already used uh 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 going to go ahead uh well let's 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. THS like that and let's write export default images uh sorry export cons default images like that uh and let's just make it an empty the array for now and now what I want to do is I want to open my inspect element and I want to open my network Tab and in here when I click uh we we should be seeing uh wait let me just try and click all here there we go okay uh so you should be seeing uh 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'm not sure which one it is I think it's 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 uh 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 uh 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 uh GitHub go into constant images. THS and just copy them so this is also going to save you if for any reason you didn't manage to set up the unlash API and this is what we're going to do next so we're going to make sure that we use this uh default images so let's go back inside of our form picker component and now uh let's go ahead and let's import uh 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 uh 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 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 uh I don't know I'm going to go ahead and I'm just going to 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 oblig by API uh unsplash guidelines and they say that we need to hot link to the original Creator so we're going to do it the same way Trella does and that's by creating a little black bar at the bottom uh of each image uh so we can see exactly who created it so let's go ahead uh 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 SL link so just make sure you add uh this import and let's go ahead and let's give it an hre 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 HRA we're also going to have uh 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 Das 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 MBG black sl10 like that perfect so uh 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 uh 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 uh correctly so I'm going to 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 uh it's going to hotlink you to the original creator of that image so this is what we need to do uh to oblige by unsplash API guidelines if you ever want to you know click on this uh apply for production thing you of course have to read the entire guidelines right so you can see everything you need to do here uh 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 uh selected so I think we already have that yeah we have the onclick 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 in Set uh y z h full W full BG black 30 FX items Center and justify Center and inside I just want to render a check icon from Lucid react so make sure you import the check alongside Loader 2 here and let's give it a class name of h-4 W-4 and text- 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 aage let's first see if this is working this on click so conso log image ID selected image let's try that first so I'm going to go ahead inside of my inspect element and when I click here okay it obviously sends the correct ID and and it's definitely sets it in the store here and then in here we check if that ID is matching but for some reason uh 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 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 little check icon in the middle perfect so that is uh what we needed and now I can remove did I remove the console log I did uh 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 uh just by the end of this div and render the form errors component from do/ form errors so just make sure you imported it from do/ 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 uh SE 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 unchange 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 uh 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 uh let me try and find act actual URLs so we have Raw full regular small so these are sizes right so we're going to save a couple of them and we're going to do this in this way so in this input type radio give it a value off and open up back Texs so first is 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. url. Thum 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. your 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 image URLs thumb pipe image Ur is full pipe image URLs uh sorry image links HTML pipe and image username so these pipes are important because in the server action we're going to extract them uh we're going to split this string by pipes and then the 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 uh picker component and just copy the value which I assign to this hidden radio right here great so let me refresh now let's just confirm that nothing uh 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 uh inside of our form data so let's go ahead and let's go back inside of the uh form popover so let's go into form pop over here in here we have the onsubmit and we extract the title so now let's write con image to be form data. getet image as string and let's conso log the image and I'm going to comment out 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 uh 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 form data. getet 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 uh clicks uh 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 Thum URL which is a string and db. text so it can accept a very long uh amount of characters then image full your also a string nb. text then we're going to have image username string nb. text as well and image link HTML to be string db. text as well and while we are here let's also add the created at be date time and default now and let's also add the updated ad to be date time at updated ad 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 uh great and now we have to update our schema and let's go ahead inside of our terminal here and first I want to show you how to reset your entire database so just make sure you're doing this in development you know uh 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 uh which don't have all the necessary data which we need and now we can can write MPX Prisma DB push and then that is going to assign this new schema Prisma to your uh 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're ready to actually save that in the database so now we have to modify our schema a bit but not Prisma schema but the action schema for create board so go inside of schema. Cs 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 uh great and now let's head back inside of index. vs and you can see we already have some errors here because our uh well requirements to create a board have changed so first things first besides user idid 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 dosit 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 usern name then image link HTML actually I think it's it goes link and then user so let's just reverse this two so first link and then user perfect and now let's go ahead and let's write um 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 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 h 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 um 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 uh 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 uh and I think that should be it yes that looks like uh 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 is a bit complicated what we're doing with the images so go ahead and just add a console log here and I just want to want you to copy this array and paste it here and I just want you to confirm that you have all of those 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 uh and make sure you do mpm 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 uh 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 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 uh let's try it out again so I'm going to 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 none of this should be undefined if some 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 conso 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 perfect so this is what we needed to do to finish up our uh 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 we when we successfully uh create a board I want this pop over to close so let's go ahead and do that now we have to go back inside of our uh form pop over here and we have to create a ref uh 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 uh going to be null and now we have to assign this close ref to the pop over close so find the pop over close right here it's wrapping our button and give it a ref off 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. current question mark uh doclick 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 SL 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. [Music] push slash board data. 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 H oh all right all I had to do is press command shift b or control shift B and then type reload window here it 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 uh intellisense and since we updated our Prisma schema it didn't really sync up right now there are no errors here and in my form popover you can see that it says the 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 open 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 off set of 18 so make sure you are on desktop mode so have this big create button and there we go go you can see how now we can do it from here as well as from here and uh 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 uh the form popover component which is used to create 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 great 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 uh 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 uh boards from here and let's also extract the current organization ID from out util which we get from clerk nextjs 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 SL navigation to/ select dorg and I imported next navigation from here great so if there is no organ oranization ID present even though this shouldn't happen because our middle or 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. find many 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 import here let me move it to the top and let's go ahead and give it some props so HRA is going to be slash board SL 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 backs to be URL and inside let's go ahead and access the board image thumb URL so uh 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 uh load it when it comes to well loading a board entirely and let's add a class name here which is group relative aspect D video BG Das no- repeat like that BG Das Center uh we're also going to have BG Das cover and let's also add BG Sky 700 in case it's still uh loading rounded small h- full w- full padding two and let's go ahead and add overflow hidden like that that great and now inside of the link let's go ahead and let 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 inser Z BG black 30 and group- 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 uh 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 uh darkened effect and when we click on individual one it redirects us to that uh specific board ID per perfect so what I want to create now uh is a little loading state right right now as you can see it's just plain like this so let's go ahead uh 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. skeleton to be function skeleton board list and let's go go ahead and return a div with a class name to be grid grid ds-2 small grid ds-3 large grid ds-4 and GAP 4 and inside let's render the skeleton component from at/ components UI skeleton and let's go ahead and give it a class name of aspect D video h- full w-o and padding off two and let's go ahead uh 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 uh and do that let's head back make sure you save this in the board list and let's head back inside of page. vsx 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 fullback to be board list. skeleton which we just created like that and let's see this now so when I whoa 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 uh regarding UI done with this page right here we can create new boards what we'll focus on now is creating this 404 page which uh well is going to render the individual uh board where we're going to be able to change the title of the board delete the board and 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 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 for 44 page but just before we do that I forgot about one thing that I want to do and that's when we 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 task ify 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 organiz ation 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 low Dash like this and I believe we also going to need to install the types for that so let me just try and import X from low Dash yes we also need the types so get back in the terminal and after you do low Dash do npm install D npm install DD at types SL low Dash as well so we have the types and that's it we can close this and there we go and from low Dash go ahead and extract start case and now let's also go ahead uh and create another function here export asynchronous function generate metadata and go ahead and extract organization slug from out from clerk nextjs 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 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 tasky if I switch to a new organization now it says test pipe task ify so how does it generate that pipe tasky 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 uh inside of a layout uh inside of a different layout which is not the root layout it keeps the title but it moves it you know here to the side and we actually fill only this variables it keeps the pipe in between perfect so now what I want to do is I want to create uh 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. CSX 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 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 outside uh inside of this nov bar here so we're going to have to move it uh 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 uh our board ID layout so inside of board ID create a layout. vsx like that and let's do uh 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 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 uh 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 prams because remember layout is a server component so every server component including layout also always has access to pams so let's add Pam 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 board ID with a capital I so that is also very important 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 uh 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 nextjs if there is no organization ID we can just redirect so we do that from next SL navigation and just redirect to select organization there we go make sure you have next navigation imported uh and now uh what I want to do is fetch the current board so const board is a weight DB I imported that uh from at SL 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 aate database uh. board. find unique where ID is prams board ID and the organization ID is the one which the user is currently uh using great and if there is no board this is a cool thing that I just recently discovered in next 14 uh 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 you know we'll leave that at the end uh if we have time to do it uh all right so make sure you import not found and now let's go ahead and uh give this div a style to be background image open backx URL and go ahead and use that board to get image full URL so this is where we're going to use that full image which we stored and give it a class name of relative h- full vg- no- repeat BG Das cover and BG Das Center like this and there we go now we can see our beautiful image here and let's try uh 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 I just did with organization right I wanted to say test in the tab right here so the user knows that that's the uh 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 par 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 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 pam. board ID and org ID and let's just return title to be board question mark. tile 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 2 3 my tab now says test one 2 3 perfect uh great great job so that's what I wanted us to do what we're going to do next uh is we're going to whoops what we're going to do next is we're going to go ahead and create a little navigation bar at uh here so we're going to able to so we're going to be able to rename the board if needed so inside of the board ID layout here we're going to add a new component called board navbar so just above this main element which is wrapping our children we're going to add uh a board knv bar like this whoa I completely butchered that sorry so board knv bar like this and if you save you of course we're going to get an error because board knv bar does not exist so let's go inside of the board ID folder and create another underscore components folder inside and let's create dboard dnvb bar. vsx and in here let's go ahead and let's write con board navbar actually export const board Navar and let's just return a div saying board Navar like that go back to the board ID layout and now import import that from slore components board navbar and when you save you should no longer have any errors what I want to do before we head inside of the board navbar is actually just uh darkening this uh 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 uh going to be visible so let's go ahead just below this um just below this board uh navbar create a div element which is going to be a self closing tag and give it a class name of absolute inset zero and bg-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 uh the actual uh props here so interface board navbar props and let's go ahead uh 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 word navbar props like that and then let's just go ahead and pass in the ID to be pam. board ID because the board navbar uh is still a server component so we can actually uh 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/ 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 uh just 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 uh otherwise uh 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 sl50 fixed top 14 Flex items Center px-6 Gap dx-4 and text- white great so now as you can see we have a nice little knv bar below our first nav bar 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 uh well server action for that so we're going to do that here inside of board knobb bar so let's create a component called board title form so it's going to be in the same level as this board Novar so let's write board- title- form. 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 accept 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 uh and let's render data. tile and let's go ahead and give this button a uh class name of font D bold text large h-o w-o p-1 and px-2 and let me just reorder my imports a bit uh all right let's save this and let's go back inside of the board nav bar here and instead of this text here let's render the uh well the board title form form do/ 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 KN bar here so let's go ahead inside of components UI and find the button. vsx and just below this primary uh 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 slash2 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 nov 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 used 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 uh 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 and on click to be enable editing and 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 of flex items Center and GAP X2 and let's add the form input component from at/ components SL form form input like this and let's go ahead and give it an ID of title let's give it an on blur 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. sorry data. tile and let's give it a class name to be text LG font bold px- Open brackets 7 pixels py-1 h-7 vg- transparent Focus D visible outline dnone Focus Das visible is going to have ring transparent and we're going to have border none great and now let's go ahead uh 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 to be used ref again element ref attach type of input and null and now let's assign these two refs properly so the form ref is obviously the form ref and the ref for the input is input ref great and now let's go ahead and let's create uh let's modify this enable editing here to actually Focus right so I'm going to add a little uh set time out here and let's do input ref. Curren Focus input ref. 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 conso log I am submitted and we can also immediately extract the title to be form data. 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 1 through three and press enter there we go it says I am submitted one two three perfect and now it's time for us to actually uh go ahead and we create the actual form action but just before we we do that uh 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 the VII and find the input from shaten 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 are here let's also change the rounded MD to be rounded SM like this so This Way It just fits more to what uh shatan actually uh not shaten but the Trello actually does perfect so now we have this kind of Flushed uh experience when changing a board there is no difference between you know the original one uh and this one very nice great and now let's go ahead and let's actually uh well just wrap up this board title form on blur here right so when it 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. 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 type 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 is going to modify uh this new title from here so I'm going to call that action uh 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 uh 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 SL client let's import action state from lib create save action and let's import update board from do/ 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 uh action State input type and board great and now let's finally go uh inside and create whoa 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 in put type from do/ types and returns a promise which gives us a type of return type again from do/ 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 nextjs 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 uh 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 s/ lib DB I'm going to move it here so await db. board. update where ID matches and of course organization ID matches as well and let's use data to be title so using this organization ID we have prevented anyone from outside of this organization to update this board so only the person from uh which has the proper organization ID inside of the AL util which cannot be mocked or Faked in any way uh can actually update this board so if you watch my previous tutorials this is similar to checking that the current type of user which is trying to up 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 U 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 and let's revalidate the pack 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 con update board to be create save action passing the update board schema so you can import that from do/ schema let me just move it here so make sure you have create safe action from at/ Li create sa action and schema and in the second argument pass in the Handler great and now we can go back uh 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 uh sorry board board ID components board title form right right here let's go ahead and import that action so update board from actions uh update board and let's also import the use action from hooks use action all right and let's go right here I'm going to do this at 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 soner I'm going to move it to the top here so toast. success and let's immediately say so open backs board open annotations and we're going to spell out the exact typ title of this board so data. title so board the title of the board update so the user can immediately see that we successfully reflected uh those changes right and let's go ahead uh and call the disable editing here great and now let's go ahead and use this execute so I'm going to go inside of the onsubmit here and let let's go ahead and let's call execute let's pass in the title and pass in the ID to be data. ID like that um great and just before we try out one more thing that I quickly want to do I want to keep my title inside of State rather than uh inside rather than directly accessing it from data so we can do optimistic updates on it so go ahead and write con 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 insided here on success uh before we disable the editing let's do set title to be the new data. tile great and let's also extract on error get the error and let's do toast. error the error uh 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 updated 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 Novar so it's located inside of app platform dashboard board board ID components board Das 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 ml AO so this way we push it all the way to the right and 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 con 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 Novar and now you can import the options from do/ 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 nav bar here here and now let's go ahead and let's import everything we need from the popover component so import from s/ 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 uh 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 s/ 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 class name of h-4 and W-4 and let's give this button a class name of h-o and w-o and padding of Two and a variant of transparent so it fits the look of our invisible Navar 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 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 at the popover close let's give it a prop as ch 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- aouto w- AO padding to off solute top-2 wr-2 and text- neutral D600 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 uh 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 of ghost just let's not misspell variant and let's give it on click for now to just be an empty Arrow function uh and let's give it a class name of rounded dnone w- full h- aouto p-2 px-5 justify Das start font Das 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 board and first let's go inside of the schema to change what we need so previously we needed uh 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. Cs 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 save 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 trying 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 bored 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 SL navigation I'm just going to move it here so we're going to be using the read direct and let's go ahead uh 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 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 extract execute and is loading from use action let's pass in the delete board option let's modify the on error call back if we have an error we're going to call Toast which we can import from soner and let's call toast. error and Light directly log that error and I'm just going to move this soner to the top here and the reason we're not going to be doing anything with the on success uh well there's no need right because we're going to directly redirect as you saw in the action back to the organization page so the user is clearly going to see that the delution was successful and now let's go ahead uh 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 task ify I have a board called another change and let's go ahead and deleted from here it's loading and there we go it is no longer here perfect so we successfully finished uh our initial uh 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 uh 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 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 add 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 the lead we're going to Cascade so let me go ahead and zoom out so you can see this uh 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 at add index here for board ID to get rid of that warning so this is it this is the code without any warnings oror 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 uu ID 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 uh this items like 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 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 in side 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 ad 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 an 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 uh to our database so 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 npx Prisma migrate reset so this is going to remove all the existing boards that we have inside uh after that is done go ahead and run npx Prisma DB push like this wait a couple of seconds for this to align and let's do MPX Prisma generate great and then mpm run Dev finally perfect so again if you have a feeling like you did something wrong you can always visit my GitHub uh 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 uh 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 local host 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 the app folder platform dashboard let's go inside of the board board ID and here in the page. CSX 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 if 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 pars and now let's mark this entire function as an asynchronous function and let's go ahead and let's let's import out from Clerk nextjs and let's also import DB from at/ 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 IID in that case we are going to redirect using next SL navigation so let me move that here we're going to redirect the user to to/ select organization select or 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 uh list we now have it because we did npx Prisma DB push and npx Prisma generate and now we can do db. list. find many and in here let's write board board ID should be pam. board ID and now to confirm that only the people inside of this organization can uh load this lists we're also going to write if board itself so the Rel the relation 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 uh 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 uh maybe I should have named it position so you can just imagine its named position if it's confusing you right basically what it is meant to represent is the exact order uh 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 uh the card or the list itself right for the list 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're going to be able to modify this list ID as well so it's 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 list so this is just going to be empty but you know it's good enough for now we can go ahead head and style this div now and write a class name to be padding 4 W sorry H full and overflow X AO 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 pam. 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 uh create this list container so inside of this underscore components where we have the board knv 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 uh which is going to be list from Prisma client and word 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. CSX and now you can import that fromt slore component list- container and there we go now we have a text list container in here but something in my interface is actually uh 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 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 back inside of our list container component which we have an 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're ready to create uh well our first kind of item here which is going to be the list list form so inside of this list container right here let's instead use an ordered 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 Z and withth of one so this is going to help uh 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 flush 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 con list form for now to just be a div same list form go back to the list container and import the list form from slash list form and you should no longer be having uh 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. CSX in here go ahead and import uh well actually let's create an interface list of rapper props and let's get the children to be react react node and let's export con 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 Z H4 and W of 272 pixels and select Das 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 divs 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 uh box here perfect now let's go inside of here uh and let's create uh a little button which is going to say uh add a list inside of this button let's go ahead and give it a class name of w- full rounded medium BG white sl80 uh hover vg- white sl50 transition padding of three Flex items Center f- 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 uh great and Below uh 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 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 need use ref and element ref from react so let's go ahead and prepare is editing and set is editing to be used 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 uh great and now let's go ahead and let's just create a couple of handlers like we did for uh 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 this set is editing to be true and then callose the set timeout and let's just do input ref question mark current actually we can directly access the current and then Focus like that and now let's write cons 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 uh const on key down to have an event of keyboard event and if e do key is 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 want to copy all the NIT trick it has like when you click on escape the entire thing closes right so we're going to 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 uh 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 uh to listen to key down and call the on key down and let's also add use on click outside again from use hooks TS so use on click outside is going to list listen to form ref and then it's just going to do disable editing if we click outside the form ref great and now let's go ahead and let's actually uh assign this button to do something so give it an onclick option to call the enable editing like that and now let's go ahead and let's write if uh 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 in component from add/ components form form input so make sure you import this and let me just move it here above uh like that great and now let's go inside of this form input and let's give it an a 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 cover 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 this 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 these class names well yeah you wouldn't guess these 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 uh and now let's give it a placeholder here enter list title and let's give it uh 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 I click outside it goes back uh well it goes back to being uh a button perfect so now I want to create a little button uh below it so let's go ahead uh and inside of this form well I actually want to do another thing uh I want to go ahead and create a hidden input here so a hidden value and let's go ahead and give it a value of pams board ID and I didn't add params so let's go ahead and let's get our params use params from next SL navigation make sure you import that and now let's go here and just get prams 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 own 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/ components form form submit 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 uh 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 uh actually no class name but let's give it an onclick to be disabled 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 uh when well um 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. THS 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 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 make just make sure you have validation for title and validation for board ID so that's what we expect to for the user to enter and now inside of types dots 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 save 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 EX 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 tri block we are assigning to that new list constant and calling AWA DB board. list and do create that me this means we're not going to be using the work Clause at all instead we're going to be passing the data only we're going to pass in uh 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 uh 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 Y is order as 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 uh 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. 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 uh 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 uh board I believe so let's just revisit our types. CS yes so let's go ahead and instead of importing board let's import list from Prisma client and put list as the second argument in our action State and now as you can see we have no errors inside of our save action Handler perfect so now let's go ahead and let's find our our app platform dashboard board components list form and let's go ahead and let's import everything we need so we're 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 used action and create list and let's go all the way to the top here uh 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 uh and let's actually add some existing um well callbacks right so on success we get the data and let's just do toast which we can import from soner so just make sure you do that I'm going to move it uh to the top all right so we have that and let's do toast. success open backck and let's write list open annotations and let's write list data. tile created so we immediately show that we fetched and uh well uh 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 us router from next navigation so make sure you import use router from next navigation and then in here let's do router. refresh so we refresh all of our server components and on error let's fetch the error and do toast error and the error itself uh great and now that we have that let's go ahead and let's create uh our onsubmit function so const on submit 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 while like that and then let's use this onsubmit to be an action of this board right here and let's also go ahead and give this form input errors of field errors uh 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 uh we cannot see it here because we don't do any iteration over uh any Fields right but what we can do is we can visit our Prisma studio so let me do that npx 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 want it perfect great great job so what we're going to do next uh we're going to create uh 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 the 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. CSX 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 cons data set data or actually we can call it uh 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 the 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 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 um great and now that we have that let's go ahead inside of this ordered list and just before we do uh the list form let's go ahead and let's iterate over data actually ordered data do 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. ID index which is list. ID as well uh 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 list item so go inside of underscore components where we have the list container and create a new file uh list- item. TSX and go ahead and Mark this as Ed client and Export con 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 /list item and when you save uh you should be having a list item here like this so now uh let's go ahead uh and let's just quickly give this ordered list a class name of flex Gap X3 and H4 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 uh 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 uh when we Implement drag and drop and now let's uh assign those props so list item props and we can extract data and the index uh great so now let's go ahead uh and let's change this div to be a list item and give it a class name of shrink 0 h- full and w272 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 mdbg Dash and go ahead and write hash 1f uh 1 f24 2 F4 Shadow MD and padding bottom of two great and now inside uh well for now we can just uh 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. vsx like that Mark it is use client and Export con 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 SL list header and when you save you should no longer uh be having an error and I made a small mistake here in the list item component so the background should be 1 F and then it should be directly uh sorry it should be F1 F2 to4 like this and there we go so now you should be seeing a whsh color like this so not exactly white but just a slight uh 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 two PX of two as well text small font semi bold Flex justify between items start and GAP x two great and now inside instead of rendering list header let's go ahead and open up a new div which is going to write list header here and give it a class name of w- full text small px- 2.5 py-1 h-7 font D medium and Border D transparent like that uh 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. Tyle here uh 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 uh 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 uh 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's 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 use State data. title and then let's replace this data the title to use the state version of that uh great besides this state we're obviously going to have the is editing and set is editing from use State and by default is going to be false and now let's go ahead and let's add form ref to we use ref with element ref of form and give it a null for the default value copy and paste that and replace um 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 do 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 uh you can explore flush sync if you're interested in that but the react docs mentioned that it should only be used as a last resort in all of the attempts that I've tried using 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 time out 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 we only actually need select uh great but we're going to try that in a minute let's now just continue and let's write the disable editing so con 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 uh 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 do key is escape in that case we're going to submit uh this request so form ref. current question Mark request submit that's what we're going to do great and now let's go ahead uh 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 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 going to do now is we're going to actually well make the input here but we're also going to 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 ref 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 D1 and px2 pixels and let's write an input inside and let's uh go ahead and make it a hidden input with an ID of ID name of ID and value data. ID let's copy the hidden input and and let's change this one to be board ID and name board ID and the value is data. board ID like that and then let's add the form input component from add/ 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 uh 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 uh well card right and it 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 7 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 uh the actual server action which is going to well trigger uh the submit so let's 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. Cs 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 uh we will not be expecting the board to return we'll be expecting the list to be returned and 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 uh as well so let me just copy this right 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 uh 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 a wait db. 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 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 where is it 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 at/ 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 sonor like this let me just move it uh to the top here and let's call toast. success here open backx renamed to open annotations and let's render the new name like that and then let's set the new title so we op mystically updated 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 uh the const handle submit here which accepts the form data which is form data and let's get the title so form data. getet 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. 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 R all right and now uh let's go ahead and let's assign all of these things so our form needs to have let just collapse these items so our form needs to have a ref of 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 um 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 uh 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 uh 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 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 con list options and return a div list options and now let's quickly create an in interace 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 car great let's go back to the list header and now we can import this list options from /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/ components UI popover so let's go ahead and import the popover itself let's import the popover content the popover trigger and the pop over 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 at/ components uui 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-o w-o 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 pop hour 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 P adding 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 uh 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 name of h-4 and W-4 and let's give this button a class name of H AO w-o padding to top uh sorry absolute uh top to right to and tax- neutral D600 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 onclick on ADD card which is as you remember an empty function for now and let's write a class name here rounded dnone w- full h- aouto 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 uh 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 uh 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. board ID and inside we're just going to render the form submit uh which we imported from at/ components form form submit and inside Let's uh 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 the 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 going to refresh the entire page but later we're going to 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/ components UI separator so just confirm that this is your import and not the radic 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 to 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 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 Z infer 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.ts 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 um delete list great and now let's go ahead and modify this so the this can of course stay the same and from the data we now uh D structure 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 file 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 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 going to 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 or going to get the data and let's go ahead and call Toast which we can import from soner so make sure you have the toast which I will add here there we go so let's call toast. success and let's go ahead and open back tis 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 toast. 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 theit as the form action for our uh 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 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 want to 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 import use ref and element ref from react and I will import them right here so element ref and use ra now let's go ahead and assign the close ref here to the popover close component where we uh 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 on success right here we can call the close ref current question mark 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 going to 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 let list here and now we're just going to do things slightly different in the tri 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 descending 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 con new order to be last list question mark last list. order + one or just one and now we can finally uh create a new list so list is equal to await DB list create data board ID is list to copy. board ID title is going to be open backck the original title from list to copy. title but we're going to append a little copy uh 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 data to be list to copy do cards. map get the individual card from here open an immediate object and return the title to be card. tile 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 fail to delete we're going to say failed to copy perfect and the revalidate 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 executed Elite 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/ actions copy list which we just created so I'm just going to move it here alongside the leete 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'm going to go ahead and click copy list and let's 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 uh and let's add con 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 cons disable editing is very simply going to turn the set is editing to false and now let's go ahead and Define con enable editing which is going to be set is editing true and also set time out here and now let's just focus on the text area ref a text area ref current Focus like this great and now inside of our list header we can now add uh on ADD card here to be enable editing because remember we have a 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 uh now outside uh 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 B disable [Music] 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. ppsx let's mark it as use client and Export con 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 do/ 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 uh 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 uh 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. display name to be card form like that and now alongside this uh props here add a comma and exclude the ref itself like that perfect so now what I want to do is render uh well a button here to say add a card so let's give this div a class name of padding top and px2 let's open a button from at/ components UI buttons 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 two let's give this button on click to be enable editing let's give it a class name of h- AO px2 py 2 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 uh form text area so let's go ahead and do that I'm going to 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 D text area. vsx let's mark this as use client and let's go ahead and write an interface form text area props which is an ID an optional 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 uh and let's actually call it form text area with a lowercase a like this so form text area. display name is going to be form text area now let's go inside the forward ref let's go ahead and open parenthesis and let's go here and let's give it an html text area element and form text area props like that so basically uh 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 parenthesis again and immediately let's go ahead and destructure the ID label placeholder require IR D disabled errors on blur onclick 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 uh before we end this parentheses open uh 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 y 2 and w- full and now let's add a new div with a class name space y1 and w- full as well and inside if we have the label we're going to go ahead and render the label from do/ UI label and I'm just going to replace that to use slash components and let me separate it all right let's go ahead and close the label here and inside let's run under the label like this and let's also give this an else to be null let's go ahead and give this lab label an HTML 4 element to be ID plus name to be text extra small font semi bold and text neutral 700 like that and now let's go ahead inside of the shet CN sorry inside of the terminal and I'm just going to go ahead and add npx Shad CN UI my latest add text area like this let's wait a second for this to install Perfect and now uh just below this label here go ahead and enter the text area from do/ UI text area which I 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 Onkey down on blur is going to on blur on click is going to be on click ref is going to be 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 /li SLU and inside of here go ahead and open parenthesis and first let's uh write the default classes which are going to be resize dnone Focus - visible uh ring- Z Focus D visible ring- offset d0 again then we're going to have a separate ring- Z Focus ring- Z and outline dnone and we're going to have Shadow small like that and now add a comma and paste in the class Name Pro like that and let's also add area- described by to be uh open back the id- 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 do/ 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 uh it's disabled while the form is pending so extract pending from use form status which you can import uh from react Dom let me just move it here to the top like that and then let's use this pending here uh in the disabled prop so we're going to use either pending or disabled like that perfect so now that we have our for 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 uh 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 m-1 pa- 0.5 px-1 and space y4 and inside render the form text area from at/ components form uh text area which we just created and let's go ahead and let's give it an ID of title let's give it uh 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 uh 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 uh well we actually I don't think we need to do uh this class name instead let's just pass in the errors to be errors uh 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 onclick 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 uh no errors here perfect uh 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 s/ 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 uh 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 an 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 uh 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 call 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 a card like this and what we expect uh 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. vs and let's do the same thing so create card from the schema and we scroll all the way down create save Action Now uses create card and the function name should be create 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 D structure 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 tri block let's go ahead and remove everything here so make sure the tri 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 a wait 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 con 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 that 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 hard 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 uh perfect so that is it it the error can stay the same the 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 idore 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 finish 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 uh 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 uh and let's see if that's going to be it I would also like to import use ref from react uh as well as element ref and let's also import keyboard uh event Handler like that uh great and oh I already started this react so yeah let's not have two Imports let's just move the forward drive to be here so no need to have uh multiple Imports of exactly the same thing in fact I think it will throw uh an error uh okay and I think this should be uh enough for now now let's go ahead and just also add uh use params from next SL navigation like that and let's go ahead here inside of the card form and let's add const params to be used params which we imported const form ref to be use ref element ref of form by default it's going to be null and let's write const use action uh create C hard and let's go ahead and execute uh and extract execute and fi errors great and now let's write const on key down which is event keyboard event if event. key is escape in that case we're going to disable editing all right so we have that done now let's go ahead uh 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 uh and also import use event listener here so we're just doing some uh user experience features now right when they click Escape when they blur uh 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 area element HTML text area element it accepts the event prop and open 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 uh request submit for the form so we're going to consider if an 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 get title as string let's copy this two times let's replace the second one to be 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 uh assign the ref to this form to be form ref and let's give it an action on submit great and now uh 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 uh this function uh execute and field errors and now let's go ahead and let's import toast from sonor package and let's go ahead and add some callbacks so on success is going to get the data and we can call post. success open btics card open annotations data. title created like this and let's do form ref. current reset so it clears the form and let's add on error here to get the error and call 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 Local Host uh 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 uh thebug y 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 going to do is I'm going to confirm that this onsubmit is actually uh having all the values so T title list ID and and board ID so let's try that out now so I'm going to open the console here and let me just focus uh all right let's write test here and when we click test oh the board ID is null oh oh all right so we forgot to pass the board ID that's why the function uh is not working here uh so let's go ahead and 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 uh onsubmit 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 pam. board ID here directly and also write it as string like like this and let's see if that's going to improve this so 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 uh 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. CSX 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 /lib SLS and let's give it some default classes which are going to be mx1 px1 py 0.5 flex flex-all and GAP Y 2 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 into 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 going to 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 uh so let me close everything about 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 card item. vsx let's go ahead and let's mark this as use client and let's export const card item here and return a div card item and now let's just quickly create an interface card item props to accept the the data which is the card from Prisma client and an index which is a number and now we can go ahead and D structure 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 SLC 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 border 2 border transparent hover border black py2 PX three text small BG white rounded MD and Shadow SM and if I take a look now there we go we 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 going to do that before we do opening a card uh in a model right we're going to 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 uh 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 uh 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 uh the drag and drop package and implement the functionality so head inside of your terminal and let's go ahead and run mpm install at hello Das panga SL DND D so this is the same as react beautiful DND D 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 dropable from at hello Pangia DND D like that 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 dropable component so dropable here WP the 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 the structure 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. dropable props so spread them like I did and give it a ref of provided do inner ref 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 going to 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 dragable 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. dragable props and ref is going to be provided do 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. drag handle 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 dropable so just go ahead and wrap that let's go ahead and give dropable the dropable 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 do inner ref let's just not misspell inner ref and let's go ahead and spread provided do droppable props and now just at the end of this ordered list go ahead and render provided. 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 UND 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 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 dragable from DND D so import dragable from D and D like this and we have to wrap the entire div inside of dragable so let's go ahead and do that let's indent the element let's give the draggable uh 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 W 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 uh 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 option here so all of those things are working perfect and now let's actually implement the functionality first to locally uh or optimistically mutate uh the state of orders and then we're going to do the request on the back end 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. splice and index zero and removed and returned result so this is going to reorder them by indexes and now we have to create a very big function on drag and 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 in 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 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 do dropable ID is equal to Source droppable ID 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 uh what happens if user is moving a list so let's write a comment user moves a list let's write if type is list so we know that because we gave this dropable a type of list the same way that in list item we have a dropable element right here with a type of card so we know what exactly we're dropping so go back inside of the list container and if the user moves a 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's 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 uh well uh trigger server action so we have to create a server action which is also going to update this on the back end 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 uh 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 order data so we can mutate on this and let's go ahead and first get the source and destination list so cons Source list is new data New Order data. 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 con Des list is going to be again new ordered data. find we get the individual list and we compare the list ID this time with destination. 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 uh different dropable 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 list there's nothing we can do so let's just break the function and now we have to handle the cases if uh 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. cards is an empty array now let's go ahead and check if cards exist on the destination list so in here if exclamation point destination list. 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 list and if they move the card uh in a different list because we have to modify their list ID in that case so we can safely send that to the back end so first let's do uh 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 parts 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 do for each we have the car and the index and let's write write card. 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 um 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 SDF is now on top and I can move it all the way to the back and it doesn't reset one through3 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 uh and add a little comment here uh 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 dropable ID is different from the destination's dropable 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 uh new card or actually moved card so we're going to do that using moved card list ID is going to be destination. dropable ID and now let's add the card to the destination list so we successfully just change the card ID card cards list ID and now let's use the destination list. cards. 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 uh 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 do cards. for each get the card the index and card. order is the index uh whoops let me just go uh 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 right card. order is the new index and now we can do uh 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 to three in the middle of this it should stay and it does if I move this one at 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 uh 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 going to close everything going to actions and I'm going to copy uh 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 doob 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 ad to be z.e and the updated ad 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 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 uh 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 inside of our Tri function so make sure the tri 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. update sorry 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 to your attention that I did not add any curly brackets here so this goes directly after uh 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 uh curly brackets otherwise I think you have to return this function uh great and now let's go ahead and write lists to be await db. dollar sign TR transaction and pass in 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 end 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 uh 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 let's we are not going to work with data instead we're just going to call the toast from sonor so just make sure you add uh 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 UND 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 uh 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 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 I 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. 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 the this authorization can stay the same and we're no longer extracting the board ID instead we're 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 tri block and let's go ahead uh 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 file 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 con update updated cards to be await DB sorry not const but we just modify the updated cards from here await db. 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 uh the name is update card order correct 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 the 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 uh card order instead of the uh 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 a 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 uh the board ID do we 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 uh 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 1 two 3 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 1 through 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 right out for the Moment of Truth now I'm going to drag and drop 1 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's try with a new card so new card let's see if it's 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 re we can rename it we can add a description and we can also do some actions here so let's go ahead inside of our hooks folder here and I want to 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 uh 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 the 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 uh is I want to actually create the card model in order to do that we're going to need to have the dialogue components from shat CN so let's go ahead and run npx chassen UI latest ad dialogue like this and let's wait a second for all of this to install and let's do mpm run Dev again and just refresh the Local Host if you Shu 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 uh card model and inside create an index. DSX because we're going to have multiple components uh 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 want to do is I want to return a dialogue from component CI dialogue so just make sure you don't accidentally use um the from the radics and then let's render the dialogue content from components UI dialogue 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 use card model hook so const use card model is use card model and let's actually change change this to be card model and now let's go ahead uh 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 be use card model get the state and do state is open and let's do cons on close to be use card model get the State and State on close and now we can apply those props to the dialogue itself so open is 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 null 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 uh 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. DSX and just below the toaster go ahead and add the model Provider from components providers model provider let me just align my uh 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 uh 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 and on click which is an arrow function which calls card model on open and passes in the data ID so we know exactly uh which uh 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 back backround is just a bit too light for me so what I want to do is I want to go inside of the dialogue itself let me close everything and let's go inside of components UI dialogue right here and I want to find the dialogue 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 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 10stack 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 mpm install at 10stack Dash SL react D query like this let's wait for a second uh for this to install let's do mpm run Dev again and let's go back inside of our providers in our Global components here and create a new file query provider. DSX 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 at tack 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 uh the 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 passing 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 your query Provider from components providers query provider so make sure you do it from here and including the children as well so uh 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 children are server components just in case you had that doubt so let's just refresh the Local Host 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 in and let's go inside of the A 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 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 pams 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 uh the error first so let's just do return new next response from next SLS server and let's pass in internal error and a status of 500 inside of the tri block first let's attempt to get the user ID in the organization ID from the out Handler so make sure you import clerk from nextjs if we don't have user ID or if we don't have organization ID you can return new next response an authorized with a status of 401 and now let's let's go ahead and let's import our database from at/ lib DB and in here let's go and fetch the card so const card is await DB card find unique where ID is prms 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 you're going to need from the list where this card is is the title so write Select Title true so no need for any uh additional information and now let's just write uh return next response. 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. DS 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 inut import the used 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/ types so let me show you that you should have card with list which is a card from Prisma client and it 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 SL lib fetcher and passes in backtick SL API cards and then pass in the individual ID like that and then inside of your const you're going to have data which we're going to uh 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 3 uh it shows one two 3 and you can debug this this if something's not working uh by going inside of your network tab here and let's go to SL cards is all 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 comp 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 sing header let's go back inside ins side of the uh 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 be card data and let's import the header component from dot slhe header so this one right here and let me just uh 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 uh 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 the structure the data like that perfect so what I want to do now uh is I want to go ahead and render this but I I kind of want it to be uh 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 3 margin bottom of 6 and W full and now I want to go ahead and add a layout icon from Lucid react so just make sure uh 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 one 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 uh which we don't yet have so let's not do that just yet uh 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 a data. title like that and make sure you import use state from react because we're going to do some optimistic update uh 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 so sorry width of 95% 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 uh so let's just refresh so we ignore this uh okay let's try it like this let's use data question mark title like that or actually think now yeah now nothing's going to be displayed here all right let's let's just continue developing and we will see the results uh 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 six and inside we render the skeleton which we can import uh from at/ 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 one 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 six margin bottom of one and BG new control 200 copy and paste this below and let's give this one a width of 12 a height of four and it doesn't need any margin bottom and now we can use this header skeleton here so go ahead here and remove the question mark for data. tile 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 car data in that that case we're going to go ahead and render header. skeleton like that otherwise we're going to go ahead and render the actual header like this so let's try that out now I'm going to refresh here and when I click you can see how for a second I 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 top we're also going to need the pams so use pams which we can get from uh next navigation as I did uh right here besides this we're also going to need to create a ref so let's 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. current form request submit all right and now uh let's go ahead and let's uh 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 uh the default value so that's good and now let's just go ahead and let's create Con on submit here with form data which is is a type of form data and let's conso log form data. getet 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 going to 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 uh field which is going to be submitted to the server action which we are yet to 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 when I refresh and click here there we go you can see it says new card and then uh 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 uh 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. string and then we're going to go ahead head 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 whoa 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 let to be a card and then in here let's go ahead and let's simply change the card to be await DB card. 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 going to 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 uh and actually uh we create this uh uh 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 onsubmit 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 uh that it also needs a couple of uh more things here so let's fetch the board ID to be pams board ID as string remember we have the prams from used pams here so make sure you have that uh all right and let's pass in the board ID and the ID is going to be data. ID like that perfect uh 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 and one more thing that I just want to do is uh add the callbacks right so first let me just go ahead and let's import the soner uh sorry toast from soner and then inside of the used action let's go ahead and add on success let's get the data from here and first thing I want to do is reinal the query so query client. invalidate queries and let's go ahead and pass in the query key to be card and data. ID like that and then let's call post. success and open backt renamed to open annotations and render data. title and set title to be the new data. tile and let's add on error here error and inside uh 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 that does the exact same thing perfect so now we're going to go ahead and create some additional actions like changing the description uh copying the card or deleting the card and then we're going to go ahead and learn how to uh 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 uh 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 used client let's export cons 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 do 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 pool inside of this div let's render our skeleton from add/ 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 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 six and margin bottom two let's copy and paste this one and let's also uh 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 grid 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 uh 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 4 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 3 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 the above so if we don't have card data in that case let's go ahead and render description from do/ description which we just created so just make sure you have all the necessary uh exports here so you can import it properly so we're going to render description. 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 uh 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 uh and style that now inside of this description uh component right here so let's give this top div a class name of flex items Center actually let's make it items start Gap X3 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 uh 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 a line 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 H5 W5 margin top of 0.5 and text neutral 700 below this icon let's open 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 uh 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 big box here at the bottom um 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 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 but button that we can click here and inside and right here we're going to have our actions right so let's go ahead uh and now we create the functionality to enable editing right for that we need to add all of those things like enable editing uh 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 used State and by default they are false so make sure you import used 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 a 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 uh the query client and the params so const query client is use Query client from 10stack 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 cons param is use perams from next navigation so let me just add this as well great so we have everything uh 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 uh 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 false great and now let's create our on key down so const on key down is going to take in the event which is the keyboard uh event and if event key is escape in that case let's go ahead and disable the editing so little bit of good user experience on our side and now let's go ahead and let's add the uh 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 con 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 cluse and you can remove this data the description here like that so two Dives here and one div here and we can indent this I believe yes all right so now let's give this uh 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 inide let's render the form text area component which we created recently so from add/ components form form text area and let's give it all the necessary prop 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 a 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 uh the form submit component uh yes form submit component from add/ components form submit so make sure you add uh 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/ components uh 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 uh the execution for that and we don't have to to create any new server action we can reuse our existing action called uh update card because in the schema you can see that we already prepared for accepting the 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 AC action and I'm going to import update card from actions update card and now let's go ahead uh 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 soner so let's move the soner to the top here and let's go ahead and uh 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 backs and we're going to write uh 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 atast and let's also extract the field errors from here and then we can use that to pass in the this form text area as errors great uh all right so now I just think we need to add some uh refs here so let's uh actually we need to add the action here so action to this form is going to be uh the on submit right I think that's what we called it right here and now let's use the execute and let's pass in the description and the board ID uh 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 uh it seems to be missing it seems to be missing uh the title oh 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 uh 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 uh great and I think we did not assign this textt 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 uh that's going to be enough to uh trigger it back to your 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 uh we finish 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 inside 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 Y 2 and margin top of two 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 uh width 20 height 4 BG neutral 200 let's copy this two more times uh the second one is going to have a full width and the height of eight 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 uh well let's do it like this so if we don't have card data we're going to render actions from do/ actions. skeleton so make sure you import the actions in the same way we did description and the Heather so just make sure you have all the necessary uh 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 uh 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 uh 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 uh 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 it 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 uh button a variant uh 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 uh 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 uh and let's give it a class name of of w full and justify start but I also want to give it a new size uh 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 AO PX of 2 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 uh so now we actually have to create the uh 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 uh and let's just quickly visit okay so I added text small to the in line okay everything is fine so we can close everything and let's go inside our actions and let's for example copy um well 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 what's everything uh we need inside of this so we need the ID and we need the board ID I think that is uh 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's all we need but we're going to be working with a card so just change this and now let's remove everything inside of the tri 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 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 uh our card let's find the last card in this list so const last card await DB card B 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 + one otherwise it's just one and let's write card to be await DB card create and let's use the data here let's give it a title to be open back Tex and we're going to copy the original uh card to copy. 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. description so that can stay exactly the same order new order and list ID is going to be card to copy. 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 uh we only need uh the 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 typle delete card inside of the schema and let's give this here this is here perfect go inside 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 tri block for the delete card because all we need to do is card is wait DB card delete where ID is matching and the link 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 uh inside of our actions do I have an error here I believe it's just let me just I'm going I just pressed command shift p 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 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 card and he's going to use the delete card action let's go ahead uh 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 prams 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 uh what I want to extract from here is also going to be well the uh 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 onclick 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 call backs 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 uh card model from use card model so you can import that from hooks use card model and let's also import toast from soner 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. tile copy it and I believe yeah we can close the model on copy as well so card model.on close I think that would kind of make sense on error let's get the error and let's do toast. error 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 through three first I'm going to try to copy there we go it's disabled and 1 two 3 copy now let's try delete and it 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 uh 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 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 you u i dialogue right here and we already modified this BG black here but let's take a look uh right here in the dialog content and in here you can find the max with LG how about we change that to 3 Excel 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 now is first let's shut down the app because we're going to be modifying not Port Terminal because we're going to be modifying our schema so shut down your app and let's go ahead inside of prismas schema. 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 uu ID now let's give it an organization ID which is a string let's give it an action which is the 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're also going to have the user ID which created that we're going to have the user image string DB text and username string DB text as well and we're 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 the updated at which is also a date time with the value at updated at and I'm just going to go ahead uh and align all of this 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 uh 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 npx Prisma DB push so this is going to add this new model inside side of our uh database and now we also have to do npx Prisma 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 uh 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. CS 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 nextjs 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 uh 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 audit uh 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 or organization ID from out and now let's extract the entire user from await current user so both of these are imported from Clerk nextjs 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 idid entity type entity title uh did I misspell that entity title uh looks like something uh is 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 modifi the schema run npx Prisma 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 do MPX Prisma DB hush to synchronize it with the actual database on planet scale or wherever you're running your database and then mpm run Dev and everything should be fine great besides entity title uh 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 user name 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 going to go ahead and also open my Prisma studio so npx Prisma Studio make sure you have this running and let's just paste it uh in here so I have no audit logs you can see it says zero here so I'm going to click here and focus on 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 uh 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 uh the await DB card create to the card uh constant and below that we're going to add await create audit log which you can import from s/ Li 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 have entity type which is going to be entity type which you can import from at Prisma client so entity type. card and we're going to have an action which is going to be action which you can again import from Prisma client. 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 save action from as/ Li create save 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 uh 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 I also have the user ID user image username everything I need perfect so now we are ready to create an API route uh 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. CS 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 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 SL 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 nextjs and while we are here let's also import the database from s/ 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 and authorized with a status of 401 and now let's get con audit logs to be await DB audit log find many and let's go ahead and write where the organization ID matches entity ID is prem's card ID and entity type is entity type which you can import from Prisma client entity type. 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- logs let's change the fetcher to go to/ API cards slid 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 these audit logs it's time for us to render them so inside of here let's create a new component called activity. DSX let's mark it this use client and let's export con 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 and let's return a div inside which is going to have a class name of flex items start Gap X3 and W4 it's going to have a skeleton which you can import from s/ components UI skeleton and go ahead and give this skeleton a class name of BG 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 going to have uh two more skeletons like this let's go ahead and give this one a width of 24 and a height of six and let's 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 two 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 uh the items here to be act 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 uh any error here let's see we we seem to be having uh errors oh we didn't assign that yes so all the activity props and extract the items uh 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 uh lower loading state right here and it seems something uh 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 uh 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 uh 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 you know 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 uh sorry no I I copied the one from above so uh height of five width of five margin top of 0. five text neutral 700 let's create a div with a class name of with 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 2 and space y4 and in here let's iate over our items like this and let's just create a paragraph item item and this should be item of course so item uh dot well let's just say um entity title for now just so we see something here let's try it out so not all of our cards are going to 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 uh component sorry of our card 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. DS and let's go ahead and import action from Prisma client and audit log uh from Prisma client as a type and now export const generate log message is going to 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 backx 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 uh 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 uh 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 uh 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 uh 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. DSX so let me just close this so uh 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 shat cn- at latest add Avatar because we're going to need to display the image of the person that created uh this specific uh log right so okay that's fine and let's also import that so import avatar from /ui 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 flex flex-all and space- 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 username and give this span a class name of font semi bold lower case and text neutral 700 and then uh 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 uh which is going to well say when this was created and for that we're going to need to install mpm uh I date FNS so just make sure you install date FNS so we can work with dates now so let's go to the top here and let's import format from date FNS and now here we're going to go ahead and render format new date data. created at and we're going to format it in a type of mmm D comma y y y y and then open the small annotations at and then we place the time H mm a 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 uh of our activity inside of the card model here and instead let's surrender 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's going to be true so it's going to be having uh 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're going to go inside of each of those and add where we can so let's start with with the copy card go inside of the index here and in here where we assigned the new card go ahead and await DB sorry a wait create audit log from s/ lib create audit log and give it an entity uh title to be c. tile go ahead and give it entity ID to be car. ID entity type to be entity type from Prisma do client so make sure you import that card and and we're going to have action to be action again from Prisma client. create like that so let me show you my imports I imported the create audit log here and I imported uh Prisma client action and entity type and we're going to do that uh 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 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 uh 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 uh 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 board 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 SLC 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 uh 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 uh 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 tri block let's import create audit log let's use use the list information we import The Entity type we import the action The Entity type is list but the Del the action is correct now let's go inside uh 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 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 AIT 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 uh 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 happen so right now you can see when I try let's try 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 re re invalidate the card log if that is the correct one so it's card logs so reinv validate the card logs and now let me just refresh pick a random one change the title and now it should reinv valid it 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 of the description here uh and let's see where is our on success it's right here so after we reinv validate the card also reinv validate 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 about 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 uh you're going to see always see some activity here or you can use Prisma migrate reset perfect so we finished the activity here uh 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 so uh 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. DSX and let's go ahead and write const activity page let's return a div activity page and don't forget yet 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 uh you have to confirm inside of your nav item here in the routes that you're in in this rout routs constant that your activity also doesn't have a typo here so just some possible culprits that could happen uh great and now inside of this page let's go ahead and let's give this a class name up with full let's go ahead and let's enter the info component fromt do/ components info so we have it uh 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 underc components and inside create activity dl. DSX like that it's going to be 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 clerk nextjs if we don't have the organization ID in that case we can redirect the user using next navigation to select or so make sure you import redirect from next navigation and while we are here let's also import the database let's import the activ item from components activity item and let's import the skeleton from components UI skeleton now let's go ahead and let's fetch the 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 one 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 in side 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 let's just go ahead uh and create the skeleton for this one so activity list. skeleton 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 % with this one an 80% and last one a 75% like that so make sure you have this skeleton now go back to the page. DSX 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 do/ components uh activity list so let me just separate my imports just a bit and now let's go ahead and give it a fullback of activity list. skeleton like that there we go now we have the activity page so you can see in here we shouldn't have any uh uh 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 uh 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 uh and create a card all of those things let's uh 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 a card updated the board created a list we have everything we need in here uh and just one more thing that I want to do uh and this thing I think yeah it should be like this activity list. skeleton or not oh I have a mistake here skeleton uh 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 so it is in proper order otherwise it doesn't give us uh too much information there so we created the board we updated the board created a 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 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 that five are remaining but it is hardcoded 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 uu ID 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 ad field which is going to be a date time and a default of now and we're also going to have the updated ad with a date time and a default sorry updated ad and let me just align all of these things let's try 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 count 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 hush like this and I actually just want to clear out the entire database so let's do that as well MPX 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 uh let's go ahead and proceed with this so now that we have the organization limit I want to go ahead and I want to create uh a constant called boards so let's go inside of our constants folder where we already have the images and let's create a new file board boards. DS and let's export const Max three 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 uh 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. Cs and let's go ahead and let's import the out from clerk nextjs let's go ahead and import the database and I'm just going to change the import to use at/ lip 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 out 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. organization limit. find unique where we have the organization ID and I actually think it would be better if we threw an error here so throw new error and now authorized 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 uh 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 do count + 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 doesn't not exist so we have to create it await DB org limit. 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 uh decrease the uh amount so let's go ahead and copy this function like that and let's rename it uh 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 uh 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 do 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 con 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. 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 balse and let's go ahead and 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 uh UI helpers right so let's go ahead and fetch const get organization limit to be await 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 I limit we're going to return zero otherwise we're going to return the organization limit ount great and now that we have this we can go back uh inside of our board list so let's go inside of our board list component so that's inside of the app folder platform dashboard organ uh 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/ liborg limit like that and now what I'm going to do is after uh I do this fetching of the boards here I'm going to get the available count so const available count is going to be a wait get available count like that and then let's go all the way to the bottom here 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 fre boards like that minus the available count remaining like that perfect so let's refresh our uh Local Host and let's actually make sure that it is running first mpm runev so let's refresh this now and let's wait a second uh 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 uh well first it returns zero 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 five by zero which in turn is five 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 uh write await increment available count and make sure you import increment available count from at/ liborg limit like that uh 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 uh and use this has available count right after we check for uh authentication let's go ahead and write con 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 account 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 four 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 uh to confirm two remaining great we are getting close to getting our error there we go one remaining and let's go ahead uh and let's get the last one here one remaining there we go zero remaining and now let's go ahead uh an 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 want to 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 uh and do that so the only thing we're going to need is that delete uh 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 or 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 going to remove this board so delete this board I 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 so for that we have to head back inside of our Prisma schema so let's close everything here let's go inside of prismas schema. Prisma and let's create a our last Model organization subscription so model or subscription for short is going to have an ID which is a string uh and a type of ID with the default value of uu ID 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 uncore customer ID so exactly what we have here but in a different case like that so let me just separate this to so we can see them uh more clearly and let me just uh 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 undor subscription _ 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 uncore price uncore ID and last one is stripe current period and date time optional and let's map the name to be stripe current period underscore end like that perfect so make sure you have all of those of course you can always visit my GitHub to see uh 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 uh 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 npx Prisma 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 mpm 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. DS let's import Stripe from stripe let's export cons stripe to be new stripe and then we're going to give it a process. environment. stripe API uncore 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 autoc completed for you so you can see for me it's 2023 1016 for you it might be something different for example my previous tutorials don't use this exact version right but you can 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 auto complete like this and I'm going to show you another way to confirm which is the newest version when once we get to the stripe dashboard so don't just don't worry for now you can just write it like this if you have an error I believe you 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 LI folder and create a new file check actually let's just call it subscription. DS and in here let's go ahead and let's import the out from clerk nextjs let's import the database and I'm going to change this to SL lib and let's write a constant const day in milliseconds is going to be 86 400 0 like that uh and I close that my apologies all right and now let's export con 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 any subscription here so we are not ever going to say true uh 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 is await dbor subscription. find unique where we have a matching organization ID and let's select stripe uh subscription ID to be true stripe 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 is going to check if organization subscription do stripe price ID exists and if organization subscription. stripe Uh current period and question mark. getet 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. now and let me just zoom this out so you can see it in one line so stripe current period at 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 uh 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 uh for sure sure a Boolean uh 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 uh 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 uh to copy uh our API key so click on developers right here in the nov bar and here we have the API I 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 aior key and paste the key inside and double check that your stripee 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 uh 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 onopen we can remove it here as well and change this from setting any ID so both the on close and onop and also remove the ID from here great and now let's go ahead and let's create uh the actual uh pro model component so I'm going to go inside of components models and just create a new file pro- model DSX let's go ahead uh and let's mark it as use client let's export con pro model here and let's go ahead and start uh styling this so I'm going to create a dialogue component from do/ dialogue and I'm immediately going to add the pro model from use pro model from our hooks right here and let's go ahead uh and let's give it a 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 dialogue content from do/ UI dialogue again and let me just change this to use at/ components just Ure you're not accidentally using the radic version and inside of here let's give this a class name of Max V MD 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 uh 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/ hero. SVG which we just added an ALT of hero class name of object Das 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 uh let's go inside of our providers so in 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 do do/ models pro model or/ 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 uh the app running and that should be enough for you uh to finally see the actual pro model let's just wait a second to 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 uh 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 uh developing on this one so outside of this div let's go ahead and give it a class name text neutral 700 MX outo space Y6 and padding of six like that let's go ahead and write an H2 element upgrade to tasky Pro today like that and let's give this h2a class name of font semibold and text XL outside of the H2 element let's create a paragraph which will say explore the best of tasky 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 Das three like this and let's create uh an unordered list with a class name name of text SM and list disk and inside create a list element which is going to say unlimited boards 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 uh in this application uh 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 do/ UI button here components and let's go ahead and let's uh 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 uh it's 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 cons 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 let me just refresh and if I try and create a new board there we go you can see that I have a popup great now let's go ahead and create an action that when we click on this upgrade button we actually get redirected the stripe checkout so I want to begin by going a quick 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. environment. nextore _ appcore 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 uh exactly what where our app is hosted so now make sure you copy this environment variable go inside of environment environment and add it so HTTP Local Host 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 card here and let's rename it uh 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 uh 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 uh 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 I don't even want this make sure you only have uh 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 back Tex 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 go inside a try and let's write const organization subscription to be await DB organiz ation 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 con stripe session to be await stripe. billing portal oh let's also import Stripe from at/ lib stripe as I did here so if we already have a subscription we're going to open the billing portal sessions create and passing the customer to be org subscription. stripe customer ID and the return URL is our settings URL and now let's just return the URL to be the strip session. 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 con 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 uh method types is just going to be card you can of course 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 outo the customer email is going to be user email uh 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 nextjs 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 uh and let's go ahead and get the user email addresses the first one in the array. email address let's R light items here let's write price data the currency is US dollar product data has a name of Tas ify Pro and description is unlimited boards for your organization and now let's go ahead and give this product a unit amount of uh 2,000 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 uh inside of this if clause and let's write URL is equal to stripe session. 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 organ 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 passing the error like that perfect and now let's go ahead uh 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 web hook 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 Web hook like this and in the web hook 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 go ahead and import next response from next SLS 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 a wait request. text let's get the signature since this is going to be called by the stripe uh well dashboard or SDK we have to confirm that it is them that's calling that so let's use the headers get stripe Das signature as string basically we want to protect this endpoint from Foul Play Let's Define the uh event here and now let's try and get the event using the stripe web hooks. construct event in the combination of our body the signature which we have and the process. environment. stripe web hook uncore 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 creating this event it means that this is then uh well we can continue and actually finish the user's check out 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. dat. object as stripe. checkout. session if event. type is checkout. session. completed then let's create a subscription await do stripe sorry await stripe. subscriptions retrieve so we retrieve using the session. subscription as a string if we don't have session metadata organization ID which we just passed inside of the stripe redirect server action that's why it said it's very important we are going to be throw 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 or subscription create data or ID session metadata. orgid stripe subscription ID is subscription. ID stripe customer ID is going to be stri uh subscription. customer as string stripe price ID is going to be subscription. items the first one in the array. price . ID so let's just check if this is correct uh subscription. items. 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 current period endtimes 1,000 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 do price. ID and stripe current period and it will be updated to new date with subscription current period end times 1,000 like that perfect so we finished our stripe web hook and very important at the end of the web hook you have to return new next response null with a status of two 100 great and now we have to find a way to keep this web hook running locally so let's go ahead inside of the stripe dashboard here and in here in the developers tab you have web hooks 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 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 listen forward to and let me show you exactly where we're going to listen so stripe listen-- forward D2 local post 3000 SL apiweb hook 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 Local Host 3000 API webhook and go ahead and press enter and after you press enter you're going to be seeing your web hook 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 web hook secret API and uh environment key so let's go and do that I'm going to go inside of my uh environment here and I will add stripe web hook secret and paste it here so please make sure that you copy that and confirm that your stripe web hook secret matches so stripe web hook secret needs to be exactly the same and again confirm from your ter you 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 wi space uh great and now what we have to do is allow this API endpoint to be uh visitable without authentication because we're going to be using that signature event to confirm that it is the uh well the correct party so let's go in inside of our middleware file we have a middleware dods here and besides this being the public route we also need to add the API web hook so let's add SL API SL web hook 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 LS 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 you this is your first time working with stripe you need to enter this exact uh 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 customer 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 uh 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 uh let's see where it is so in here we have the can create now let's add const is pro await check check subscription which you can import from lib subscription so if we cannot create end if we are not pro only then are we going to throw an error otherwise we can uh 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 so they're going to 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 uh stripe subscription now we have to modify that to say Pro here in the info component as well as in this one here uh 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 component 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 hardcoded 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 uh the info component so let's find info like this so first one is in the organization ID page 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 is Pro from await check subscription from as/ liip 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 l 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. vsx here and let's go ahead and do const billing page and let's return a div with a class name of w full let's make sure this is an asynchronous component let's check is pro using a wait check subscription and now let's go ahead and let's render the info component from do/ 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 uh 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/ billing if it's not find the nav item component and modify uh let me show you nav item component and in here you have the routes and your billing should be/ billing make sure it doesn't have a typo and of course make sure that the page uh the billing folder doesn't have a typo either and that it is inside of the organization ID folder uh 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 of the billing folder and create the underscore components folder and create a new file subscription Das button. CSX let's go ahead and Mark this as use client export con subscription button and return a div for now now go back to the billing page and you can import the subscription button from do/ components subscription button let me just align my imports now let's modify the interface for the subscription button so sub 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 actions 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 call backs here so on success we get the data and then with the data we have to handle window. load location. hre to be the new data if it is an error we have to handle the error inside of the toast from sonor so just make sure you import the toast from sonor and now let's go ahead and let's write const on click here be an arrow function which which calls if is pro execute but otherwise it's going to 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 uh inside of your main project here uh you should be having a text uh where is it oh we're not logging any error uh basically we have to enable the billing portal so go inside of the dashboard uh 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 uh 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 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 wrapped this in is pro so let's go ahead and check that here const is pro is await check subscription from at lib 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 uh 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 uh 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 get ad get 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 add 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 versel and click on add new project right here and find that uh 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 environment and you can just copy absolutely everything from here select the first field and paste and there we go that's going to fill up everything here so after we deploy we're going to have to change the stripe web hook 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 result olve it together so my deployment seem to have failed because of this shaty and Sheet component that we have because the property class name does not exist on type dialogue portal props so I Googled the error and it looks like two weeks ago there was a comment here uh which explained that this was fixed for uh 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 mpm install uh red this exact radx 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 radx ui/ react dialogue with this exact version and let's go ahead and paste that inside great let's go ahead and run get add get commit I'm going to write deployment fix here and I'm going to push so make sure you do get push and that should automatically trigger the next deployment on ver cell 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 web hook environment key because remember currently we created our stripe web hook 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 web Hooks and here we go I have local listeners so now let's click add endpoint for hosted endpoints here and now we have to go ahead and we have to copy uh this this URL which we have paste it here and go to SL API SL web hook so make sure you don't forget the SL 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're using the invoice let me 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 uh 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 uh this sign in secret and now we have to go back in inside of versel here select your project go inside of settings environment variables and go ahead and find the stripe web hook secret here edit it and paste the new version inside it's going to be much shorter like this and we have to edit uh the next public app URL if you try and take a look now you can see it's HTTP Local Host 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
Info
Channel: Code With Antonio
Views: 216,977
Rating: undefined out of 5
Keywords:
Id: pRybm9lXW2c
Channel Id: undefined
Length: 714min 47sec (42887 seconds)
Published: Tue Nov 14 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.