{
  "video_id": "Big_aFLmekI",
  "title": "Build a Course & LMS Platform: Next 13,  React, Stripe, Mux, Prisma, Tailwind, MySQL | Udemy Clone",
  "url": "https://www.youtube.com/watch?v=Big_aFLmekI",
  "transcript": "Hey there, my name is Antonio and welcome to the newest video on my channel. In this tutorial, you're going to learn how to build an amazing learning management system platform. As you can see, I already have some courses which I've purchased and they are in progress. Some of them are completed and some of them are not. And I can also query these courses by clicking on a specific category like engineering and that's only going to show the engineering courses. Or I can use the search bar at the top right here, or I can search for an example, basics. And this is only going to query the courses which have basics in the title. Great. Now let's go ahead and let's find a course we like so we can purchase it. So I like this course, Yoga for Beginners. It has five chapters and it costs $15. So once I click on a specific course, I'm redirected to that course page. And before I purchase the course, I have a free chapter for me. As you can see in the sidebar right here, introduction is free, but all the other chapters are locked. I also have the enroll button right here, as well as my description of this course. If I try and manually click on a locked chapter, you can see that it says this chapter is locked. And I also have a banner which says you need to purchase this course to watch this chapter. So let's do exactly that. I'm going to click enroll for $15 right here. And now I'm going to get redirected to the Stripe checkout page. So I'm going to go ahead and fill out my fake information, my fake name, and I'm going to click pay. Once this information is processed, I'm going to get redirected back to the course page just like this. Once I am back in the course page, you can see that I no longer have the enroll button. Instead, I have a mark as complete button. I also have attachments for this course, which my teacher put inside. And if you look at the sidebar, you can see that all of the chapters are now unlocked. I can either manually click Mark as Complete, which is going to update my progress, or I can wait for the video to end. And once that happens, you can see that the very same thing will happen. Progress has been updated and I'm redirected to the next chapter. So let's go ahead and let's manually complete these remaining chapters, because I want to show you what happens once you reach the end of a course. So I have one more video to watch and let's let the video player completed this one for us. So once it reaches the end, you can see that we have a nice little confetti because we finished the entire course. Great. Now let me show you how the student dashboard looks like. So in the student dashboard, you can see all of your courses. All that it's important is that you've purchased them. It doesn't matter if you completed them or not. As you can see, some of them are 100% complete and some of them are still in progress. But now let me show you how easy it is to edit, manage, and create your courses as a teacher. So I'm going to click at teacher mode right here. And as you can see, I have a table with all of my courses, their price, and as well as their status. Now let me show you the analytics tab right here. So teacher is able to look at the total sales they've made, total revenue they've made, and how much each course has generated for them. Now let's go back to the courses right here. And let me show you how easy it is to edit a course. So I'm going to choose Cinematic Techniques and I'm going to press edit right here. Inside, I'm redirected to the course setup page. And let's quickly change the title to Cinematic Techniques edited like this. And let's click save. That is immediately updated. Great. I can do the same thing for description. I can do the same thing for a new image. I can also change the category. I can add new attachments, change the price. And one cool thing that I can do is I can reorder my chapters. For example, you can see that introduction comes after the outro. Well, that's not good, isn't it? So let's drag the outro all the way to back. And there we go. This is now updated. And now my chapters are in the correct order. I can also add a new chapter or I can edit an existing chapter in a completely separate view. As you can see here, I can upload a new video if I want to. And I can change my description inside a rich text editor like this. I can also change the access settings. So, for example, check this box if you want to make this chapter free for preview. So I can choose as many chapters as I want so my students can see it for free. I can also unpublish a specific chapter. And if you go back to your course setup, but before we do that, you can see that I already have a banner saying this chapter is unpublished. It will not be visible in the course. So if I go back to the course setup, you can see that this now says introduction and it's a draft. I can also unpublish the entire course and now it will not be visible anywhere. And I can just as easily publish it back. So now let me show you how easy it is to create a new course from scratch. So let's click on the new course right here. Let's give it a name like advanced web development like this. And let's click continue. We are going to get redirected to a familiar screen. So let's add a description. This is a course about development like this. Let's click save right here. And notice how my publish button is grayed out because I cannot publish this course yet. I still need to finish a couple of fields. So let's add a course image using upload a thing right here. I'm going to go ahead and I'm going to select a specific image from Unsplash. and once it's uploaded it's going to be immediately shown in this form right here. Great. Now let's add a category. So this one is going to be computer science and let's click save. Great. Now we have the category. Let's give our course a price. So I'm going to set a price for this course to be $10.99 and I'm going to click save right here. I still cannot publish my course because I am missing a chapter. So let's go ahead and let's add a chapter for introduction. For example, I'm going to click create and I still cannot publish my course because I require at least one chapter to be published and not a draft. So let's go ahead and let's click on this pencil button right here. Let's give this chapter a description. So I'm going to write this is a chapter description like that. And let's click save like this. And let's go ahead and edit access settings. So this is free for preview. I'm going to click save. And last thing I have to do is I have to add a specific video. So let's go ahead and let's upload a video here. So again, we are using upload thing to upload the video, but we are using mux to process the video. So it's in highest quality and in a very nice video player. As you can see, I have a message here that videos can take a few minutes to process. And that's what's written right here. The video is not currently available. But this is enough for us to publish this chapter. So once I published this chapter, you can see that now if I go back, I can finally publish this course as well. But before we do that, let's add just another chapter, outro, for example, and let's click create right here. And let me demonstrate one more time how easy it is to do this. So I can just drag and drop. And now I've reordered my chapters. So let's move them back to their proper position now. and let's quickly edit everything we need for this chapter. So I'm going to write this is an outro like this and I'm going to click save. And once again, I'm going to upload the same asset right here. Let's wait for upload thing to do its thing. And then let's just quickly wait for Mux to process the video. So after this is done and after this is refreshed, I can click on publish for this chapter as well. And I can go back to my course setup right here. And as you can see, I have a free tag for this chapter, but not free tag for this chapter. I don't have to add attachments, but I can if I want to. And now I can finally click publish for this course. And after this course has been published, I have a nice little confetti here. So let's look at our courses right here. And as you can see, now I have advanced web development costing $10 right here. So let's go ahead and let's find that course in our platform. So I've just clicked on the exit button right here. and let's go to browse where we already were in the beginning. And in here, you're going to be able to see a new course, Advanced Web Development with two chapters. And once I click here, you can see that I have an introduction, which is free and an outro, which is locked. So this is what you're going to build. I think this is an amazing, amazing project and 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. So I'm going to be using ShadCN UI as our design library, and we're going to be using Next.js as our framework. So you can go ahead and visit the ShadCN UI documentation because I'm going to follow these instructions to set up our project. So the first command I'm going to run is the create next app, which you might already be familiar with. So I'm going to go inside of my Visual Studio code right here, and I'm going to go ahead and open the terminal. In here, I'm going to paste the command, which I just copied, but I'm just going to modify it a little bit. So as you can see, it starts with npx, create next app, then an add sign, and it says latest. So that ensures that your next version is always going to be the most up to date version. But in my experience, as of writing this video, the newest version is actually a bit unstable in development. So what I actually recommend doing is that you remove the latest and write npx create next app at 13.4.12 like this. Then just change my dash app to your desired project name. So in my case, it's going to be lms-tutorial like that. And you can leave everything else the same. So I'm going to expand this screen so you can see the final command. So there it is. npx create-next-app at 13.4.12, lms-tutorial, dash-tipescript, dash-tailwind, and dash-slink. Perfect. So if you're watching this in the future, if you want to, you can use the at latest, especially if 13.4.12 is outdated in your opinion. But if you're watching this just recently after it's been released, you can feel safe to use the version that I've written in this command right here. And just go ahead and press enter. When this is prompted, just type yes and press enter like this. Now it's asking us whether we wanna use the source directory. For that, you can select no. For the app router it important that you select yes because it gonna be crucial for this project Would you like to customize the default import alias You can select no and just let the packages install Great Now that you have your project installed, you're going to go ahead and open it. So as you can see, mine is in the lms-tutorial folder. So let's go ahead and let's open that folder. So lms-tutorial like that. And now that you're inside of this folder, if you get this prompt, you can safely press yes, I trust the authors. Great. What we have to do now is we have to run the CLI for chat CNUI. So I'm going to copy that from the documentation as well. Don't worry, you're going to see exactly what it is in the terminal now. So make sure you've opened your project. So in your sidebar, you should have the app folder, the public folder, the next configs, the tailwind config, the TS config and stuff like that. So make sure you are in the proper project, and then open your terminal again. And inside of this terminal, go ahead and paste npx chat cn-ui at latest init like that. And go ahead and press enter. So it's asking us whether we're using TypeScript. Since we initialized our next project with TypeScript, the answer for that is yes. For the style, choose the default. For the color, you can choose slate color. For the global CSS file. This is the correct location. You can select yes for the CSS variable options as well. Our tailwind.config.js is located in a proper. So what you want to do with this one, you can just press enter, but you can also check. So just make sure that it's tailwind.config.js. If it's not, make sure you write the correct one here. So for my case, it's the correct one. The import alias, it's also correct. The utils are also correct. And we are using server components. So this is related to the app router option in the next 13. Let's go ahead and press Y. And now it says whether we want to write this configuration to components that JSON, which practically means do you want to confirm all of these options? So just go ahead and press Y. And now it's going to install chat CNUI. Great. Now that that is done, you can go ahead and finally run npm run dev inside of your project. And you're going to have your project running on localhost 3000. So let's go ahead to localhost 3000. And in here, you're going to see a screen similar to this. Great. Now what I want to do is I want to clean up the entire project and maybe show you a little bit how to use chat CNUI. So I'm just going to collapse this a bit. And I'm going to go ahead inside of my app folder. And I'm going to go inside of page.vsx right here. So I'm going to go and remove everything inside of this return function. So all the way from the starting of the main element, all the way to the bottom where it ends. And I'm just going to remove it. And instead, I'm just going to write a paragraph saying hello, world, like that. Great. Now it's a very, very small text. So let me zoom in for you. Great. Now let's test out our tailwind setup. So I'm going to go ahead and write a class name, text-3XL, font-medium, for example, and let's give it a color of sky 700 like this. As you can see, it works. So I've successfully styled my text here. And if you're wondering, how am I getting this little color? How do I have, when I hover over a CSS class, how do I have the exact CSS? And how do I have the autocomplete? Well, that's an extension called Tailwind IntelliSense. So you just Google, sorry, just search for Tailwind. And there you go. It's the first one right here. All right. Now what I want to show you is how easy it is to use ShadCN UI to add components. So if you take a look at your folder structure, you have the components folder, but it's currently empty. So ShadCN in it added this folder, but we have not added any components yet. So let's go ahead and let's do that. So I'm going to go ahead back into the ShadCN UI library, and I'm going to go and find a component like I like. So we are more certainly going to need the button component. So I'm going to choose that one. So let's go ahead now and let me copy this CLI installation. So npx-shtcn-ui-add-latest-add-button and let's go ahead and add that inside of our terminal. So I'm going to go ahead and open a new terminal and I'm going to write it here. So npx-shtcn-ui-add-latest-add-button like this. After you press enter, you just have to confirm to install this component. And now it's installing button. After that is done, I'm just going to close this second terminal because I don't need it. Make sure your app is running. And now let's go ahead and let's check our components folder again. So now you're going to see that in the components folder, you have a new folder called UI. And in there you have button.tsx. So this is how the chat CNUI library works. It's not going to add the components inside of our node modules like most component libraries do. Instead, it gives us full access to the component inside of the components folder. So this is perfect for building our own component system, or just using the default ones. Great. Now let's go ahead back inside of our app folder inside of page.tsx. And let's wrap this thing in a div like this. and below the paragraph, let's add a button from add slash components slash UI slash button. And let's write click me, for example, I'm going to visit my website back and there we go, you can see a nice little button that we have here. And let me show you how easy it is to add a variant, for example. So just write variant to be destructive like this. And now it's a danger button. But this is the cool thing about ShadCN UI. So you can go back inside of the components folder UI button that TSX. And if you want to, you can find this destructive variant and you can change the color from BG destructive to let's say you don't have to do this. So I'm just going to do this for fun. Emerald 700 like this. And see, I just changed the variant just like that. So you can play around. Of course, you didn't have to do this. You can just revert destructive to what it was. Or you can add a completely new variant here, for example, the sky and VG dash sky dash 700 like this. And now I can go back inside of my page and I can change the variant from destructive to sky because I just added that. So that's what's really cool about the chat CNUI library. It gives you full access to your components and fully, you can fully customize them to whatever you like. Great. So I'm just going to remove everything and leave the button as it is. If you change too much stuff, you can always rewrite your entire component using this command. So let's say I added a bunch of stuff inside and, you know, this can happen. I don't know, you forgot to save or you save too much and you cannot undo it. So I just ruined this button on purpose, but you didn't have to do it. You can always just go back to this CLI command and you can go ahead back inside of your terminal. I'm going to open a new one and let me expand it so you can write this, but just add dash dash overwrite like that. Let me just dash dash over write like this. And now that is going to overwrite the existing button component, right? And if I go back here, you can see that there is no gibberish. And now everything in my button works. And of course, I have to remove the sky because it doesn't exist. Neither does the danger. It's destructive, right? Like this. Great. So you just learned the basics of using the chat CNUI library, all the components are built in pretty much the same way, but it gives you a lot more customization than any other component library project would. So this is going to be perfect for our custom LMS platform. Great, great job so far. One thing I want to do before we move on is quickly head into our app folder inside of globals.css. And just before, I'm not going to remove anything here, but let's just add HTML let's add body and let's add root like this and let's just write height 100 so this is going to come useful later perfect so you're not going to be able to see any changes here but that's good enough for now great great job and see you in the next part so now it's time for us to learn how the project structure works how are we going to use these folders and what types of folders we are going to use. So before we do that, I just quickly want to go back to my package.json. And remember that I told you to use npx create next app 13.4.12. Well, I thought that was also going to change the next version to that version, but it's not doing that. So let's manually change that. Because next 13.4.19 is working, but in development, it causes a lot of crashes because it has some memory issues. So what I want you to do is go inside of your package.json and find next 13.4.19 and change that to 12 like this. And the same thing for eslin config next. So 13.4.19 is now turning into 12 like this. And then just quickly head back inside of your terminal right here. So I'm going to close this. I'm going to shut down my application. I recommend you do the same and just write npm install. That's all you need to do. So the new version, which we just written here is going to be set up in the project. So we're going to work with next 13.4.12. Great. And now we can go ahead and run your project again. And you shouldn't actually see any changes. But trust me, your development is going to be much easier with this version. That's something I encountered while developing this on my own. Great. So no big deal if you don't manage to do this. You can still work with 0.19. It is more up to date than 0.12. But as I said, as of writing this video, there are some development issues with it causing a lot of crashes and making you have to restart your application every time it happens. Great. So this is going to ensure that we don't have to do that every time. Perfect. Now what I want you to do is I want you to install a very cool package called next.nav. So just search for next.nav inside of your extensions. And it's this one right here. So make sure you install that. And what I want to show you is how cool it is. So I just discovered it the other day. And what you can do is press Ctrl Shift P or Command Shift P if you're on a MacBook. So you have this console open right here and just write next.nav like this and press enter like that. And I'm going to zoom out just a bit right here. Right now, all of this is dummy, right? So this data doesn't really exist. We don't have this yet. But what you have to do is you have to click on this button right here. So let me zoom in back. So this button right here, and it has a placeholder for source slash app, we are not using the source folder. So all we have to do is write app like this and click submit like that And there we go Now you can see our very simple flow right here where we have the globals the layout and the page inside of our app folder And then that's exactly true as you can see in my folder here. So I only have the globals, the layout and the page, no other subfolders inside. But now we're going to play around with the routing inside of Next13. So you learn how to do that. And so you learn how to do something called route groups, which will not affect the URL. So without me talking, let's actually start and let's create a very simple route. So let's say we wanna go to localhost 3000 slash test. If I do that now, we're going to get a 404 that this page could not be found. So let's go inside of our code, inside of the app folder, and create a new folder called test. And inside of that folder, create a new file, page.tsx, like this. Now, in the tutorial, you're going to see me using this shortcut a lot of times. So I'm going to write SFC and then I'm going to press tab. And this is going to autocomplete for me. If you're interested in what that is, it is an extension called Simple React Snippets. So let me, there we go. So Simple React Snippets, this one. So just install that if you want to use the same shortcut as I am. So I just write SFC after I installed that. You can see I have an autocomplete for stateless function component and I just press tab. And then I can go ahead and write test page like this. I can press tab and then I'm focused on the props. I can press tab one more time and then I'm focused in the return function. So inside I'm going to write a div saying this is a test page like this. And now you can see that we no longer have an error on my slash test page. So it's neither a 404 nor it is an error. So it is exactly the page that we've written here. And let's visit our next.nav now. So I'm just going to go ahead and reload my window here and let me reload next.nav. All right, let me add our app folder again. And there we go. You can see that now I have the app folder. And I also have the test folder with page.vsx inside. So that's exactly our structure here. So I think this is very useful for you to learn the workflow and the project structure inside Next13. Great. So now we learned how to create a test route. Now, one cool thing that we can also do is create a new folder and let's call this one API. And inside, let's create another folder called, I don't know, again, you can do test like this. And inside this time, we're gonna write route.ts. So instead of page.tsx, we're gonna write route.ts. And let's go ahead and let's write a quick route here. So we're going to use route handlers. So write export asynchronous function get like this. And let's just return next response, which you can import from next slash server dot JSON like this. And let's just write test. Hello as an object like this. And this is now available at slash API slash test. So I'm going to go inside of my page and I'm going to change my route to slash API slash test like this. And you can see that I have a response from the server giving our object back. So that's how we are going to create our API routes. Great. So now you know how to create a route. You know how to create a page. But let me show you one cool thing that we can do. So there is the thing in Next called route groups. So as you've just learned, every folder that I create, which has a page inside of it, becomes a route in our project, right? But what if we want to use a folder simply for organizing and not with the intention for it to become a route? Well, we can do that by using route groups. So I'm going to create a new folder like this. And I'm going to call it group like this. And inside, I'm going to go ahead and create a new folder. And this is going to be called, I don't know, let's say route A like that. So it doesn't matter. Whatever you want. You can write your name inside if you want to. And inside, create a new page.tsx like that. And let's go ahead and use the shorthand. So route A, and let's write a div saying, this is a route inside a group folder like that. So what do you think? How do we access this route now? Well, it seems like we have to go to slash group and then to slash route A, right? Well, that's not correct. All we have to do is go to slash route A or whatever you named your folder. So see how this looks. So it's just slash route A. That means that we successfully told the Next.js that this group folder is purely for organizing. So we don't care what you put inside. This group is not gonna be a part of URL. And this is very useful because this route group can have its own layout. So for example, let's say that I want all the routes inside of this group folder to have a background color of red. So let me show you that I'm going to create a new one saying, I don't know, let's call it a test. Let's call it Antonio, like my name, for example. And inside, I'm going to create a new page that the SX, SFC, Antonio page, a div saying Antonio page like that. So again, if I go to localhost 3000 slash Antonio, now you can see that I have on your page. But let me show you one cool thing about layouts. So if I create a layout that TSX file inside of this group folder, it is going to reflect to all of the routes, or whatever you want to call them, or the folders inside of this route group folder. So let me create a new file here called layout.tsx like this, right, you can see I have an error because layout requires a default export. So make sure you're inside of layout.tsx, which you created inside of your parentheses group folder. And let's just go ahead and write group layout like this. Now the required prop inside is children. And we can quickly write the types for it as well. So colon children, react.react node like this. So I'm using GitHub Copilot, if you're interested in why am I having these auto completions. Great. And what I'm going to write is a div and I'm going to render children inside. If I save, nothing has changed with my Antonio page and nothing has changed with my route a page. So they are both exactly the same. But let's do a cool thing with a class name of h-full and bg-sky-700 and text-white, for example. So I added some styles and look at what happened now. So every single route inside of this group folder will now have this exact style, which I've written here, right? So if I go to slash Antonio instead of route A, look at that. It also has the same background and the text is white. But if I go to my slash test, you can see that it doesn't have that. That is because my test route, you can see it is outside of this group folder right here. So that's how useful this group folders are because they are used for exactly that for grouping routes inside of one folder without affecting the URL, but still having a really cool functionality of having custom layouts. So we're going to use this a lot of times. And one more thing that you can do is you can break from your main layout. For example, let's say inside of your app, you have a sidebar and you have a navbar, right? And what if you want to create a new route which doesn't have the two? Well, you can do it exactly like this by creating a new group route outside of that other group route. Don't worry, it's going to make a bit more sense when we start using these folders. So I want to go ahead and delete the group folder now. I want to go ahead and delete the API folder now. And I'm also going to delete the test folder now as well. And you can close this next.nav. I'm going to open it every now and then so you get familiar with our structure. What I want to do now is I want to move this page.tsx from the app folder inside of a route group. So this is the structure that I'm going to use. I'm going to create a new folder called dashboard like that. And inside, I'm going to create a new folder called routes. Make sure both of these folders are in parentheses. So none of this is going to be inside of the URL. and inside of these routes, I'm going to drag and drop the page like this and click move like that and this might happen to you. So you can see that in my open editor, it says I have one unsaved file and if you look at that file, it's page.ts from .next slash types slash app, right? So what you can do is just open that unsaved one. You can see it's very weird code. We did not write it and just press save and that's it. So you're not going to get any error anymore. But in case you mess this up, in case you think you did something wrong, don't worry. So this .next folder is just cache. So let me show you what I'm going to do. Let's say I accidentally messed it up. So what I can do is I can shut down my app and run rmrf.next or whatever your command for deleting a folder is. Or you don't even have to use the command. You can just manually right click and delete the folder dot next like that. And all you have to do is run your app again. And let me show you. So in a couple of seconds, there we go. I have a completely new generated dot next folder. So all that dot next is is cache. So you don't have to worry if you mess that up. That unsaved file can happen every time you move a page inside of another folder. So I'll just show you a quick way to fix that if it ever happens to you. Great. Now I have my main page, which has the hello world and the button inside of two organizational routes. But if you look at my URL, it hasn't changed. So it's still at localhost 3000. That's exactly what I want, right? And now what I'm going to do is I'm going to create another folder called auth, like this. And inside of here, I'm going to go ahead and create another folder routes the same. And then I'm going to create a new folder called sign dash in, for example, it doesn't matter right now, this is just dummy, right? And inside create a new file page.tsx like that. Let's go ahead and let's call this sign in page. And let's just write div. This is an unprotected page like this. And let's go inside of our dashboard routes page.tsx. And let's go ahead and change this to have a paragraph of this is a protected page like that And you can remove the import from the button from our components folder like this Great So you can see that now it says this is a protected page This means that in the future this entire dashboard folder, which I've created right here, is not going to be available for not logged in users. But everything inside of the out folder is going to be available for the not logged in users. And those are going to be two routes, sign in and sign up. And the reason I created this routes folder one more time, you don't have to technically do it. But I just want to separate my logic because inside of this out folder, I'm going to have a new file called layout. And you're going to see that in the next part where we added the authentication. So now on localhost 3000, it says this is a protected page. But if I go to slash sign dash in, which is the folder we created, sorry, it's going to be Yeah, it's just the slash sign in my apologies. It's correct. And now it says this is an unprotected page, right? So this is going to be visible for everyone. But the local host 3000 is going to be protected. Right now, of course, this doesn't work. But in the next part, we're going to add clerk, which is going to protect our entire application with a middleware. Great. So you just learn the basics of folders. And if you want to, you can use next.nav. So command shift P or control shift P next.nav like this. And let's go ahead and let's click on the button and let's write app like that. And there we go. Now you can see our structure. So we have the app folder, we have the route group called dashboard, and inside we have a route group called routes. And inside we have a single page that TSX, which is actually our protected page. Great. In the out folder, we have a routes folder and we have a sign in folder. And inside of that, we have our unprotected page. So that's our structure for now. Great, great job. So now let's go ahead and let's create our authentication for which we prepared the folders right here. So I'm going to go ahead and I'm going to visit clerk.com. So you can either Google clerk authentication, or you can use the link in the description, or just go to clerk.com. Go ahead and find the sign in button. So let me just expand a bit. So sign in or sign up, and you can use GitHub, Google, or your email address to create an account. So go ahead and do that. Once you're inside of Clerk Dashboard, you're going to get greeted with a similar screen. So let's go ahead and let's give our application a name. So I'm going to call it LMS-Tutorial, like that. Let's go ahead and let's choose how our users will sign in. So I have pre-enabled the email address and Google. If you want to, you can look at 20 more and you can enable all of this. You can even use Web3 MetaMask login if you want to. So for me, I'm just going to choose email address and Google. And after that, I'm going to click create application like this. Now let's go ahead and let's add this API keys for the next JS project. So make sure you click next JS here. And let's go ahead and let's copy this environment keys right here. So I'm going to go inside of my project and I'm going to do a couple of things. So before I create my environment folder, first thing I'm going to do is go inside of dot git ignore file. So let me just zoom in. So .gitignore. And go ahead and find your .environment.local. And below that, just add a .environment file as well. Great. Now, after you've done this, let's go ahead and let's create our environment file. So I'm going to click on new file, .environment, like that. And inside, paste your clerk keys, which we just copied from this dashboard right here. So let me show you one more time. So you can either use the copy button or you can click on the little eye icon and select everything and copy it here. Great. Make sure you added those to your environment file. So make sure you have next public clerk publishable key and clerk secret key and save this file. Great. Now that we have that, make sure again that your next JS is selected and click continue in docs like this. Now let's go ahead and let's install the package for clerk next JS. So I'm going to go ahead and copy this. And I'm going to go inside of my terminal right here. We can shut down the application. And let me just expand my screen a bit. And let's install clerk next JS like this. So just go ahead and press enter and wait a couple of seconds for this to install. After it's been installed, let's take a look at some other steps that we have to do. So the step two is set environment keys. But we already did that inside of our dot environment file. right here. So we can skip step two. Now let's go ahead to mounting our clerk provider. So what we have to do, we're using the app router. So this is a good preselected tab. Make sure that is preselected. And as you can see, we have to go inside of the app folder inside of layout.dsx. And we have to add this clerk provider wrapping our entire HTML. So let's go ahead and let's do that. Now. I'm going to close the terminal. I'm going to close everything here. Go inside of your app folder, find the layout.tsx right here. And let's go ahead and let's import clerk provider from at clerk slash next JS like that. And then go ahead and write your entire HTML inside of this clerk provider like this. And you can indent it so it's easier for the eyes. Great. So now we have the clerk provider. What we have to do next is we have to create a middleware for our application. So find step four, protect your application and copy this code like this. Now let's go ahead. Let's go inside of our folder structure and create a new file, not inside of the app folder. So you can click on, for example, git ignore and then click new file. there we go. So make sure it's outside of all folders and name it middleware.ds like that. And inside you can paste this right here. Great. So now we have the middleware. Make sure you save that file. Let's look at some other steps that we have to do now. So we have to create our sign in and our sign up pages. As you can see, we have to create a folder sign up. Inside of that, we have to create a dynamic folder, sign up, and then page.vsx and paste this inside. So let's go ahead and let's do that. So we're not going to use the root. Instead, we're going to use our pre-made out group folder. So go inside of this out folder and go inside of the routes. And you can actually remove this sign in folder. Let's do it all over again. So go inside of your out folder, go inside your routes folder and create a new folder called sign dash up like this. And inside, create another folder, open square brackets and spread sign dash up like this. But before you press enter, make sure you put double square brackets like this. So double square brackets dot dot dot sign dash up. So it's very important that it follows this convention because this convention will enable clerk to have access to all the dynamic routes it needs in the next JS project. And inside of that, create a new file, page.tsx, like that. And what we're going to put inside is written right here. So you can just copy this, like that, and just paste right here. There we go. Great. Now let's go ahead and let's do the same thing, but for sign-in page. So it's the very same process. Inside of the routes folder, create a new folder, sign-in, like that. inside of that create another folder with double square brackets and spread sign dash in like that and inside create a new file page.tsx again and let's go ahead and copy so make sure you don't copy the same one so this is for sign up and this one is for sign in so copy that and paste it in that page.tsx and save the file there we go so let me just click expand my folders so we have the sign-in folder with the sign-in dynamic routes and inside we are importing the sign-in and returning it and same is true for sign-up. Sign-up folder, dynamic sign-up routes and page.tsx is importing sign-up and exporting sign-up or if you want to you can go ahead and look at next.nav. Let's add our app folder and let's see how that looks. So as you can see inside of my app folder I have routes. These routes have sign-in and sign-up folders, and they have dynamic sign-in and sign-up folders again, which have page.tsx, which export the proper components. Great. Before we can move on, let's go ahead and let's see what else we have to do. So we have to update our environment variables. So you can just copy this right here, and let's go ahead and let's go inside of our dot environment file. So find your dot environment like this. And after the clerk secret key, go ahead and paste the sign in URL, sign up URL after sign in URL and after sign up URL. So how do we know that this sign in URL is slash sign in and this one is slash sign up. So again, make sure you save this file after you pasted this keys. So the reason we know that is because we named our folders that way. So we know that this is sign dash in, that's why we put slash sign dash in. Same is true for sign dash up. So if you want to rename this to register and login, you can do that. But make sure you also rename the dynamic folder inside. So if this is register, this has to be dot dot register as well. Same is true for login, right? And then you have to change those to login and register. But you can just follow along and leave it exactly as it is. Great. And now that we have that, we are actually ready to test our application up. But before we do that, let's go inside of the app folder, inside of the dashboard folder, and let's go inside the routes page.dsx right here. And one thing I'm going to do is I'm going to import user button from at clerk next JS. And all I'm going to do inside is render a div with user button, whoops, user button like that. And let's go ahead and let's give it a prop after sign out URL to just be a slash like this. Let's go inside of our terminal now. And let's go ahead and let's run npm run dev like this. Great. Now let's revisit our application. If I refresh now, what's going to happen is that our slash route is protected. So we are going to get redirected to the login page like this. But as you can see, it is not centered. I want this entire thing to be centered. So let's see how we can do that. Well, we can make use of our route group now. So that is located inside of this out folder here. And we have the routes folder here. So what I'm going to do is I'm going to collapse the route folder. I'm going to go inside the out folder and I'm going to create a new file layout.psx like this.\ngo ahead and let's import our layout. We need the children for the layout. Let's add the props for the children. So children is a type of react.react node. And let's go ahead and let's add a div and render the children inside. For now, nothing is going to change. But if I add a class name of h-full flex items-center and justify-center, you can see that now our form is centered exactly as I want it. And same is true for the sign up route. So both sign in and sign up are now rendered. And as you can see, if you try and manually change the URL to something that is not sign in or sign up, it's not going to work, right? As you can see, it immediately redirects. But let me quickly show you before we log in how you can add custom unprotected routes. So as I've said, everything inside of our dashboard folder is now not accessible unless you're logged in. Everything inside the out folder is accessible. Why? Well, because we named our folders sign in and sign up. And clerk is automatically going to unprotect those routes if you add them in the environment variables. But let me show you how you can add other unprotected routes regardless of the environment key and regardless if they are used for actually signing up. So inside of my app folder, I'm going to create a new folder called test. And inside, I'm going to create a file page.tsx. Let's go ahead and let's give this a test page. And they do. This should be unprotected. Right? So even logged out users should be able to see our test page. So I'm going to go to localhost 3000 slash test. But look at this. I'm redirected to sign in. So how do I enable this? Well, very easily. All you have to do is visit your middleware.ts. So let me expand my screen a bit so I can expand the sidebar. Visit the middleware.ts. Go inside of the auth middleware. and all you have to do is add public routes like this. So this is an array and inside you can just write slash test like this. So now after you save that file and revisit the test, there we go, look at this. I can now visit this page even if I'm not logged in. Now for our use case, we are not gonna need that. So all of our routes are going to be protected. But this is just a tip for your personal project if you're using Clark. Great, so now if I refresh, there we go, I'm back to this login page. So go ahead and log in into your page. And since my last page was slash test, I am now, after I've been logged in, I'm redirected back to that test page. But if you go to slash localhost, you can see that now we have a nice user button here. And here you can see your email, you can manage your account, or you can click sign out. And after you sign out, you're back into the login page. So let me just go back inside. And there we go. I am back inside. Great. So you successfully finished the authentication for your project. I think this is a really fast method of using authentication. And Clerk gives you the best security practices. So by using Clerk, you actually have an entire authentication team dedicated to giving you the best authentication experience, security, all with up-to-date practices. Great, great job so far. What we're going to focus on next is creating our layout. So our navbar, our sidebar, and then we are slowly going to go and set up our database and create some first courses. All right, let's go ahead and let's create our layout with our sidebar and with our navbar for the dashboard group folder. So I'm going to go ahead and I'm going to go inside of my app folder. I'm going to go inside of the dashboard folder. And in here, I'm going to create a new file called layout.tsx like this. Now we have this error because currently our layout component is not exporting anything. So let's go ahead and fix that. So I'm going to use the shorthand like this. I'm going to call it dashboard layout like that. I'm going to extract the children from the props. And I'm going to go ahead and give the children a type. So children are a type of react.react node like this. Great. Now I'm going to go inside and I'm just going to render a div, which renders that children. And there we go. Nothing has changed, but now we have our dashboard ready to be developed further. So let's go ahead and let's give this wrapping div a class name of h-full like this. Now let's go ahead and let's create our sidebar. So I'm going to go ahead and create a div like this with a class name of hidden. So hidden is going to be only on small devices, right? As you can see, when I hover over hidden, it says that all it does, it says the display to none. But I'm going to add a new class here called MD Flex. So on all devices up until medium, it's going to be hidden. But when it reaches medium device is going to be flex, meaning it's no longer going to be hidden and it's going to use the flex box attributes. Great. Let's also give it an h full, a w of 56, flex dash call, so all the items go underneath each other, a fixed, and inset dash y dash zero. And let's also give it a z index of 50, so it's above other elements. Great. Now inside of that div, let's go ahead and let's render the sidebar like this. So I'm just going to end this tag and I have not imported this from anywhere. So when I save, I'm going to get an error because sidebar is not defined. So I'm going to show you our structure for creating components which are not going to be reused throughout the project and are only going to be needed inside of a specific route group. So usually in my other tutorials, I put all of my components inside of the components folder. But for this tutorial, I want to try out something different. So in this components folder is only going to be used for global reusable components across multiple files. But when I am inside of the dashboard route group, if I know that the specific component is only going to be used inside of this folder, we can leverage a new convention here. So let's go ahead and let's create a new folder with an underscore components. So underscore will exclude this folder from the URL, right? So similar to wrapping your folder in parentheses. The only thing is that this one does not have all the attributes and properties that route group has, which we've seen in previous parts of this tutorial, like adding a shared layout. So this is actually perfect for just using for organization of our components, right? And not our entire routes. Great, so inside of this underscore components, which is on the same level of this layout, which is currently having an error, let's go ahead and let's create a new file called sidebar.tsx like this. Great, and let's go ahead and let's write export const sidebar like this, and let's return a div saying sidebar like that. Great, now let's go back to our layout where we have the error. And now we can just import that from dot slash underscore components slash sidebar, right? Because we are in the same level. So from the layout, we just go inside of this folder inside of this sidebar, like this. Great. And now you can see that we no longer have any errors. So let's go back inside of that sidebar component inside of our underscore components. And let's develop further. So I'm going to go ahead and give this the class name of h dash full border-r, so only on the right side. And let's give it a flex, flex-call, overflow-y-auto, vg-white, and shadow-sm like this. And if you're not seeing anything, make sure that you expand your screen. So you can see how now it is visible, right? But if I collapse, it's not visible. And if you don't remember why that is, well, that's because on small devices, dashboard, sorry, sidebar is going to be hidden and we're going to trigger manually using a menu later when we have the navbar. So you can either do one of the two things. You can either expand your screen so you can see the sidebar or if you're like me and you want to display both of these things you can just zoom out a lot. For example you can see how when I zoom in it disappears but when I zoom out it's visible. So just make sure your sidebar is visible while you're developing this. So you can either expand your screen or zoom out one of the two. There we go. Great. Now let's go back inside of our sidebar component right here where we added this class names. And let's go ahead and remove the text sidebar. And let's add a div with the class name of whoops, of padding six on all sides like that. And inside, we're going to create a new component called logo like this. So once I save this, you can see that we get another error. So let's go ahead. And while we are in this underscore components folder, let's create a new file called logo.tsx like that. And inside, we're going to import image from next slash image like that. And let's export const logo to return an image like this. Let's go ahead and give it the height of 130, a width of 130 as well, an alt of logo, and a source of slash logo. In my case, it's going to be .svg. So this doesn't matter. You just have to go ahead and find an image for the logo you like. And let me show you how I'm going to do that. So there is this cool website called Logo Ipsum. You can visit it, logoipsum.com. It's a very cool website where you can find dummy logos like this for your side projects, right? So just make sure you don't actually use this for your business. If you want that, you can contact the creator of this website. But for side projects or just a tutorial like this, where we need a placeholder for our logo, this is a really cool website because it has really nice logos, right? So I'm going to go ahead and pick this one right here and I just copied the SVG. So what I'm going to do now is I'm going to go inside. Let me just close everything here. So I'm going to go inside of my public folder and I'm going to go ahead and create a new file called logo.svg. So you can either create it manually like this or you can literally find any image you like and just put it in the public folder. So for me, I copied this SVG and I'm just going to paste it here. There we go. That's it. And now I have my logo. Now I'm going to go back inside of the components, sorry, inside the app folder, dashboard, underscore components, logo like that. I'm going to save this. So make sure it matches the path. So slash logo that SVG matches the public folder. Logo that SVG, no other folders inside. Great And now let go ahead back inside of our sidebar And now we can import this logo from dot slash logo because they are in the same folder like that So let me go back to our application And there we go You can see our logo right here at the top Perfect Now let go ahead and let create some sidebar routes So let go back inside of our sidebar Outside of this div which is wrapping the logo create a new div with the class name of flex flex that call and w dash full. And inside, we're going to create a new component sidebar routes like this. And if we save, of course, we're going to get an error. So let's go ahead and resolve that. So in the same underscore components folder, create a new file sidebar dash routes dot CSX like this. Let's go ahead and let's mark this as use client. And let's export const sidebar routes like this. And let's just return a div saying routes like that. Let's go back inside of our sidebar component. And just as we did with logo, we can now import the sidebar routes and we should no longer get any errors. Great. And you can see a very small text saying routes right here. Great. Let's go inside of this sidebar routes. And let's go ahead and let's prepare this. So I'm going to go ahead and create a div, which I already have. Let's give it a class name of flex, flex dash call and w dash full like that. Perfect. Now let's go ahead and let's create our routes. So I'm going to go ahead and I'm going to write const guest routes like this. I'm going to go ahead and open an object. I'm going to give it an icon of layout from lucid react. Whoops. I'm going to give it a label of dashboard, like that, an href of slash, like that. And I'm going to copy this like this, and I'm going to change this to a compass from Lucid React. And this is going to be browse, like that. So just browse, and the href is going to go to slash search like that. Great. Now let's go ahead and let's just simply write const routes is equal to guest routes. So this doesn't make any sense now, but later this is going to be dynamic. But let's just prepare this constant for now. Great. Now let's remove this text saying routes. And instead, let's write routes.map. Let's get the individual route. And let's go ahead and let's render the sidebar item, which we don't have, but we can already put it here. So feel free to save, you're going to get an error, but we're going to resolve that very soon. So give it a key of route.href, give it an icon of route.icon, and give it a label of route.label. And don't forget the actual href, so route.href, like that. Perfect. So now we have to create the sidebar item. So let's go ahead, inside of our underscore components folder, create a new file, sidebar-item.dsx, like this. Let's go ahead and let's mark this as use client like that. And let's go ahead and export const sidebar item and just return a div sidebar item like this. Now you can safely go back inside of your sidebar dash routes and you can import the sidebar item the same way we did with logo and the sidebar routes themselves. Great. And now you can see, since I have two objects inside of this array, you can see that what is rendered is sidebar item two times. So now we're going to style this sidebar item and we're going to use these individual icons of each route as well as their label. Great. So let's do that now. So I'm just going to expand everything for me here. And I'm going to go ahead inside of the sidebar item and I'm going to create an interface. So interface sidebar item props like that. is going to have an icon, which is the type of lucid icon from lucid-react like that. Label is going to be a string and href is going to be a string like that. Now let's go ahead and let's extract all of those props here. So icon, label and href like that. And let's go ahead and map them to sidebar item props like this. And now you can notice that inside of our sidebar-routes, we no longer get any errors for the types that we are sending here, because they match exactly the prompts which we defined inside of the sidebar item. So make sure you are inside the sidebar item. And first thing I want to do is I want to remap this variable icon by using a colon, and then using a capital icon like this. So now I can use this immediately as a component inside of my return function. Great. So let's go ahead now. And let's add a couple of hooks before we start working inside of styling, because we're going to need some dynamic styling, depending on whether this route is active or not. So const path name is equal to use path name. And you can import use path name from next navigation. So import use path name from next slash navigation like this. Great. Besides the path name, we also need a router. So const router, use router from next slash navigation. So whenever you import the router, make sure you didn't accidentally import it from router. So it's next slash navigation in the newer versions. All right, now that we have those two, let's define our isActive function. So as you can see, my GitHub copilot is giving me a very simple way that I can check whether the route is active by comparing the current path name with the href. But we're gonna need a bit more advanced function for that. So let's go and write const isActive. be equal in parentheses, path name is equal to href. My apologies, path name is equal to just a slash. And if href is a slash, in that case, that is going to enable the first route to be active. but we add a pipe pipe and then we write if path name is equal to href and if path name question mark dot starts with open template literals and write an href and the slash and there we go we have our is active finished. So why did we do this in such a complicated way? Well, first thing that we have to check is whether we are on the slash page, right? So just the root page. Then we have to check whether we are on another page like slash search. But we also have to check a specific case where we can be in a sub route of a specific route. For example, we are going to have a slash teacher route. And inside of slash teacher, we're going to have multiple other routes. So that wouldn't work with this version right here. So that's why we need three cases, one for a very simple check, if we are on the root page, one for a very simple check, if we are on the exact same page, and one just to check if it is the parent path of the specific page. Great. So it's a bit complicated, but it's gonna give a better user experience. Great. Now let's go ahead and let's write const on click like this. And all it's going to do is router dot push the href to the URL. Great. And now we can finally develop our sidebar item. So I'm not going to use a div, I'm going to use a native HTML button element. And let's go ahead and let's add some props here. So on click is going to be on click. Type is going to be the button. Class name is going to be dynamic. So open the curly brackets and write the CN. And you can import CN from s slash lib slash utils. And you absolutely have this because chat CN init added it for you. Great. So now we have this CN imported from s slash lib slash utils. And let's go ahead now and let's give it the default classes first. So these are the classes. The classes that I'm going to write now are the default classes, which are not dynamic, right? So let's write flex items-center gap-x-2 like this. We're also going to have a text-slate-500. Text is going to be small like that. Font is going to be 500 width. PL is going to be 6 like this. Transition is going to be all. hover is going to be text-slate-600, hover on the background is going to be slate 300 slash 20, like that. And after we've written that, go outside of your annotations and add a comma. So these are the default classes. And now we're going to go ahead and write dynamic classes. So if we are active, in that case, we're going to write text-sky-700 like this. bg-sky-200-20 like that. And hover is going to be bg-sky-200-20. So we don't want when the route is active and we hover on it in the sidebar, we don't want to change its colors. So that's why we are doing this. So another hover for the text. So text-sky-700 like that. Great. Now inside of this button, let's go ahead and let's create another div. The class name of flex, items-center and gap-x-2 and padding on the top and bottom of 4. And inside, go ahead and render the icon. So make sure that you have this icon with a capital I. And we did that by remapping the prop icon right here. So now we are available to use the icon like this. So it's going to be a self-closing tag like this. Let's go ahead and let's give it a size of 22. Let's give the class name of CN like this. The default is going to be text-slate-500. But if it's active, in that case, it's going to be text-sky-700 like this. Great. Now let's go ahead and let's render the label below that like this. Great. And let me show you how this looks now. So as you can see, I have my dashboard now, which is selected because we are on the root page, right? If you try and click on this, it's going to throw you a 404 because we didn't create the search page yet. So one more thing I want to do is a small little bar here on the end to further indicate that this is selected. So let's just quickly do that as well. So after the label here and after this div so just above ending the button create a new div which is actually going to be a self div so let go ahead and write it like this and I going to go ahead and write a class name, which is going to be cn again, like this, and let's give it ml-auto, opacity-0, border-2, border-sky-700, h-full, transition-all, like that. So those are the default classes. And now if it's active, we're just going to change the opacity to 100 like this. Great. So now if you see how the dashboard looks, there we go. You can see how it That's a nice little bar here to further indicate that it is selected. Great. So now let's go ahead and let's just enable this route so it's not a 404 error. So I'm going to go ahead and close everything. And inside of my app folder, inside of the dashboard folder, inside of the routes, I'm just going to create a new folder called search like this. And inside, I'm going to create a new file page.tsx. So let's just quickly add an SFC. search page. And they do this is a search page like this. Great. And now let's test out our routes. So when I click here, there we go, you can see how now it works. And take a look at the URL, it is at slash search. Now you might be wondering, where did the content go? We also had a little icon here to log out. And we also just wrote this text. This is a search page. Where is that content? Well, it's hidden behind the sidebar. So let's go ahead and let's resolve that now that we have semi-completed our sidebar here. So I'm going to go ahead inside of the app folder, inside of the dashboard, inside of the layout.tsx right here. And let's go ahead and let's wrap our children inside of a main element like this. And let's go ahead and give the main element a class name of MDEL-56. So we are going to push. So we're going to add padding to the left side by the amount of 56 on medium devices. That is because on medium devices, the sidebar is visible and its width is 56. So for that exact amount, we are going to push our content from the left. And let's also add an H-full like this. And now if you try, there we go. You can see this is a search page. And if you click on the dashboard, we have our little icon to log out from here. Perfect. So we have our sidebar now, what we have to do now is add our navigation bar. So let's go ahead and do that. So in order to add our navigation bar, we have to go back inside of our dashboard layout here. And just above this sidebar div right here, let's go ahead and add a new div. And let's give it a class name of h dash open square brackets 80 pixels like this medium pl dash 56 to move it away from the sidebar fixed inset dash y there's zero w dash full and z dash 50 like that and inside let's go ahead and render the navbar like this and if we save we're going to get an error so let's go inside of our underscore components and create a new file navbar.tsx like this. Great. Inside of that navbar, let's go ahead and let's export const navbar like this. And let's just return a div with the class name of p-4 border-bottom h-full plex items-center bg-white and shadow-sm like this. Let's go back inside of our layout and now we can import that from dot slash underscore components the same way we did with the sidebar and save the file and there we go you can see that now I have navbar and a sidebar here. And you can see that navbar is visible on mobile mode as well. So this is where we are going to add a little button to open the drawer for the mobile sidebar. So let's go inside of our navbar now. And let's add that component. So mobile sidebar like this. And if I save, we are going to get an error. So now let's go ahead inside of our underscore components and create a new file, mobile-sidebar.vsx. Great. Let's go ahead now and let's import a menu from Lucid React like this. And let's export const mobile-sidebar like this. So return. And let's go ahead and just return the menu like this. Go back inside of the navbar. And now you can import that from .slash mobile sidebar. And see how now I have a nice little menu here. Now let's go ahead and let's turn that into an actual button which opens a drawer. So in order to do that, we have to go inside of our terminal. I'm going to open a new one. And I'm going to write npx chat cn dash ui at latest add sheet like this. So let me try and expand my screen a bit more if we can see it in one line. So like this, NPX, chat, CNUI, latest ad sheet like that. Press enter and confirm the installation like this. After it's done, we're gonna go ahead and close this terminal. So I'm just gonna close that one. Make sure your app is running. Great, and now we can go back inside of our mobile sidebar here. And let's go ahead and let's import sheet, sheet content. sheet trigger from s slash components slash UI slash sheet like this. Great. And now we can wrap this entire thing in a sheet like this. And let's go ahead and wrap this in a sheet trigger like that. And let's give it some class name. So class name. This time it's going to be hidden on medium devices. So when the sidebar is visible, no need to show this menu. So on medium devices, hidden. Otherwise, PR is going to be four and hover is going to be opacity-75 and transition. Great. Now let's go ahead and let's add sheet content like this. And inside, let's render our sidebar from dot slash sidebar like this. and let's just give some props to this sheet content. So side is going to be left like this and class name is going to be P-0 NBG-white like that. Great, so if you go ahead and try now, so make sure that your sidebar is not visible. So collapse it like this and when you click, there we go. Look at this, a beautiful mobile sidebar. Great. So now what I want to do is add our user button right here. So we're all going to prepare that. So let's go back inside of our navbar. I'm going to close everything. And below the mobile sidebar, add a new component, navbar routes, like this. And we're not going to create that inside of the underscore components, because we are going to reuse it for multiple navbars that we are going to have in our project. So let's go inside of components, not inside of the UI. So we're going to leave the UI for chat CN components. But for our custom components, let's create a new one inside of the components folder called navbar-routes.tsx like this, because we're going to reuse this navbar routes. Let's go ahead and let's mark this as use client. And let's export const navbar routes like this. And let's go ahead and let's very simply just return a div like this. and let's go ahead and give you the class name of flex gap dash x dash two ml dash auto. Whoops, I forgot to write class name. Like that. And let's just write the user button from add clerk next JS like this. So this is going to make a bit more sense because later this navbar routes is going to have multiple items inside. So that's why I'm already preparing this gap here. So let's go back inside the navbar and now we can import the navbar routes from add slash components like this. So let's go ahead now. And there we go. You can see that now I have my icon to log out in the navbar and it's also visible on mobile as well. And it's all the way here to the left. Great. So we finished the start of our layout. So what we're going to do next is we're slowly going to adapt this navbar and this sidebar to also handle views for the teacher view, right? Because the teacher is going to have different sidebar items and it's also going to have different actions here in the navbar. And same is through when we are on a search page. We're going to have a nice little search input here whenever we can query our courses. Great, great job so far. So now let's continue developing our layout. What we're going to do now is we're going to add a little button here next to the user button, which is going to say teacher mode. So when you press here, you're going to be able to create the courses. So let's go ahead and do that. I'm going to go ahead inside of navbar routes right here. And what I'm going to do is I'm going to add a couple of hooks inside. So make sure you mark this as use client at the top. So we are inside of navbar routes, which are in our global components here. So not inside of underscore components, but right here. and let's go ahead and let's add the path name. So const path name is equal to use path name from next navigation and we're also gonna need a router. So const router is equal to use router like this and make sure you import that from next slash navigation as well. Great, now let's go ahead and let's write const is teacher page like this to be path name question mark dot starts with teacher like this. Great. And now let's write const is player page to be path name, path name dot includes, oops, path name dot includes chapter like this. And let's also add a question mark here just in case path name is not available. Great. So we have the is feature page and is player page. So we going to use this dynamic constants here to change our navigation bar right So if we are in the teacher view we going to have the exit button here So we can go back to the student view And what is the player page Well that is the individual course page, right? So when we click on a course, we're not going to have the normal sidebar. Instead, we're going to have a sidebar with chapters of that course. So that's why we say we're already preparing that for the navbar here. It's going to make more sense when we go and develop further. Great. So let's go ahead below, sorry, above this user button. And let's go ahead and let's write is feature page or is player page like this. And let's go ahead and open a parenthesis and let's add a button element, which we have inside of our UI. So let's go ahead and just add that. So import button from add slash components slash UI slash button like this. So we added the button in the first part of the tutorial. And now that we have the button, let's go ahead and let's add the logout from Lucid React, which is a self-closing tag like this. And let's write exit like that. So make sure you import the logout from Lucid React. All right. And now let's give this logout a class name of h-4, w-4, and mr-2 like this. Great. And now let's add the alternative here. So again, open parenthesis and this time open a link like that. Next slash link like this. Let's go ahead and let's add a button which we have imported and just write feature mode like this. And let's give this an href of slash teacher slash courses like that. Great. So there we go. You can see how now I have this teacher mode right here. So let's go ahead and let's edit this a little bit. So I'm going to give this teacher mode button a size of small and a variant of ghost like that. There we go. So now it looks a tad better. And now let's go ahead and you can already, no, you actually cannot click on this. If you click, you're going to get a 404 because we haven't created this route yet. And let's just add the same thing. So size SM and variance to the button, which holds the logout as well. All right. And now that we have that, uh, oh, one more thing I forgot is in this user button, we have to add something. Otherwise our sign out is not going to work properly. So we have to add after sign out URL to be a slash like this. Otherwise, when you log out, you're going to get redirected to the clerk website instead of our own. So just refresh and then you can test logging out. Great. And let me just separate this imports like that. Perfect. And now let's go ahead and let's just create a dummy teacher route so that I can show you the exit button from here, right? Because now if you click on the teacher mode, nothing happens. So let's go ahead and let me close everything here. I'm going to go inside of the app folder here inside of the dashboard inside the routes and create a new folder called teacher like that and inside create another folder called courses like this and inside of courses go ahead and create page.psx like that let's go ahead and let's export courses page like this sources page so a very simple route and now when you click on the teacher mode, you shouldn't get an error. Instead, you should have this little exit button right here. So you can see how different that looks now. Now we have an exit button. And when I click here, I should get redirected back, but I'm not. So we forgot to do that. Okay, let's go ahead. Let's go back inside of the navbar routes. So let me just find that inside of our components folder, navbar-routes. And let's do the same thing. So we can wrap this link around this button as and make sure to close it at the end. And it's just going to redirect us to a slash like this. And we actually don't need the router at all then. So you can remove that import and that hook. And let's try that now. So when I click on exit now, you can see that I'm back on the dashboard. But when I click on teacher mode, I am in a different screen. Great. So one more thing we have to do is when we are in the teacher mode, I want to show different routes in the sidebar here, right? So let's go ahead and let's do that now. So I'm going to go ahead and I'm going to go back inside of my sidebar. So let's go inside of the app folder, inside of dashboard, components, sidebar dash routes right here. So we already have those routes, which we hardcoded to guest routes. What we're going to do now is we are going to add the routes for the teacher. So let's go ahead and just below this guest routes, let's write const teacher routes like this. And let's go ahead and copy this. And let's give this instead of a layout, let's give it a list icon from Lucid React. And let's make this write courses like that. And give it an href of slash teacher slash courses like this. And for the compass, let's do a bar chart from Lucid React. Label is going to be analytics. And it's going to go to slash teacher slash analytics like that. So make sure you have bar chart and list imported from the Lucid React. And now let's go ahead and let's go inside of our sidebar routes. And let's grab the path name. So const path name is equal to use path name from next slash navigation. So just make sure you have this import. I'm going to move it to the top. All right, now let's write const is teacher page to be path name question mark dot includes slash teacher like that. Great. And now we can change this dynamically. So is teacher page, in that case, teacher routes, otherwise, yes, routes like this. Great. So let's see how that looks like. So right now I have the dashboard and the browse button. But if I click on teacher mode, I have courses and analytics. And of course, we get a 404 here. So if you want to fix that, let's quickly go inside of our app folder, inside of dashboard, inside of routes, teacher, and let's just create the folder called analytics. I think I spelled that right. Page.psx. SFC analytics. So I did not spell it right. Okay. Analytics. So let me just fix this like that. Great. So make sure you didn't misspell analytics and make sure it matches the URL and make sure it matches the sidebar routes for the teacher routes right here. So make sure the route is correct. The folder should be teacher and the inside folder should be analytics. So inside of the app folder right here, make sure you have teacher and analytics and page inside. Great. So now you can see how my sidebar is working here as well. And it should also be working on mobile. You can see that on mobile, it automatically also switched to the teacher view, right? So only when we click on exit, are we back on our dashboard and our search page. Great. So one more thing I want to do. So we push the content left, right, to be next to the sidebar. But now we forgot to push it from the navbar. So let's quickly go back inside of our layout page. So I'm going to go ahead and close everything. And let's go ahead inside of the app folder and find the layout. Sorry, inside of the dashboard folder and find the layout. So inside of dashboard layout right here, let's go ahead and find the main element. So we push the content from left. And now let's also do the same thing from the top, right? And we're going to push it for the amount of the height of the navbar, which is 80 pixels. So let's just write 80 pixels like this. And there we go. Now we can finally see our search page. We can see our dashboard page, which still has this little button, which we actually moved here to the top. You can see we have the courses page when I clicked on the little teacher icon. And we have the analytics page. Great, great job. So you completely finish this dynamic layout. And what we're going to do next is we're slowly going to go ahead and add some more features to this teacher mode. So we can actually create some courses. So before we move on, one quick thing I want to do is I want to change my logo. Because in the intro video, I showed you a different logo and I did some color change on that. So I just want to show you how I do that. So I'm going to go back to logo Ipsum. And this is the logo that I'm actually using from my intro. So I'm just going to copy that. And I'm going to go back inside of my logo.svg file. So you don't have to do this if you're satisfied with your logo. But I just want to stay true to the intro that I show you. So I'm going to go inside of the logo.svg right here. And I'm going to paste it and replace it right here. So let's see how that looks like. Now you have to refresh the page. There we go. Now it looks much better, but we have to change the colors to match our dark color right here, right? So let's go ahead and do that. So what I'm going to do is I'm going to go ahead and find the fill option, right? Like this. I'm going to click Command F or Control F. And now what I'm going to do is I'm going to change it to another color. And that's going to be a 0, 3. Whoops. So select this color, 2F, 2, 3, 4F. and then replace that with 0369A1 like this. And I'm going to click replace all like this. So if I save now and if I refresh, there we go. Now you can see that my text has changed the color, but I still have to edit the icon. So let's go ahead and let's quickly find the color that we need here. So I'm just going to go ahead and find a different color here. I'm not sure which one is different. So I'm just searching through hashtags here. And there we go. It's the last one right here. So I'm going to make a search for it. So 7F5, 7F1, and I'm going to replace it again. And there we go. Now, if I try and refresh, there we go. Now you can see how it looks exactly like in the intro, which I've showed you. So where I want you to go now is I want you to go inside of teacher mode. And I want you to go inside of the courses page right here. And we're gonna go ahead and we're gonna add a simple button\ninside. So go inside of your app folder, inside of your dashboard, routes, teacher, courses, page.dsx right here. And instead of courses page, go ahead and add a button from add slash components UI button and add new course like this. So for now, that's all it's going to do. And you can give it a little bit of P-6 padding like this. And let's go ahead and let's add a link from next slash link like this. And let's give this link an href of slash teacher slash create like this. So now if you go ahead and click on this, it's going to lead you to a 404 page. So let's quickly go ahead and create this. So I'm going to go right here. Inside of my teacher folder, I'm going to create a new folder called create like that. And go ahead and create a new file page.psx like this. and first thing I'm going to do is I'm going to mark this as use client and then I'm going to export the create page like this create page like that and now if you save there we go you shouldn't have any errors now so that's what we're going to do once we click here now we're going to open the form to give a title for our course and that's going to create a draft course and then users are going to have to further add more information like an image, like the price, like the visibility and that stuff. So let's go ahead and let's create that form. So let's head inside of our terminal because we have to install a couple of ShatCN packages. So I'm going to go ahead here and I'm going to write npx ShatCN-UI at latest and I'm going to go ahead and write add form like this. So this is going to add all of the form elements that we need. And that's also going to include a couple of packages like React Hook, Zod, and some others. Now let's go ahead and let's write npx chatcn-ui-at-latest-add-input like this, because we're also going to need an input which comes separately. After we are ready, we can go ahead and run dev again. Great. Now let's go back and make sure you're inside of your create page right here. And let's add some imports. So I'm going to go ahead and write import owl as z from Zod like this. I mean to import Axios from Axios. So I just reminded myself that we also have to install this package. So let's go back inside of the terminal and let's just write the NPM install Axios like this. And then let's run the app again. So now we have Axios. Great. Let's go ahead and let's import Zod resolver from at hook form slash resolvers slash Zod like this. Let's go ahead and let's import use form from react hook form like that. And let's also add import use router from next slash router because we are going to need it later. Now let's import all the form elements that we're going to need from ShatZN UI. So go ahead and import from add slash components slash UI slash form like this. We're going to need form, form control, form description, form field, form label, and form message like this. Great. Now below that, let's also add the input and the button. So the button like this and the input in the same well. Like this. Great. So now we have everything we need and now we can create our form schema. So let's write const form schema to be z.object like this, which is going to have a title of z.string like this. A minimum length is one. and let's add a custom message saying message title is required like this. Great. So now that we have the form schema, let's go ahead and create our form hook. So const form is equal to use form, which is going to use z.info type of form schema like that. Let's go ahead and let's add the resolver to be Zod resolver, which uses the form schema, which we just created. and the default values are going to be title, which is going to be empty by default like that. Great. Now let's extract the states from this form. So const and let's extract is submitting and is valid from form.form state like this. Great. Now let's go ahead and let's write a very simple on submit function here. So const on submit. For now, it's just gonna take the values, which is type of z.infer type of form schema like this. And let's just console log those values because we're not gonna do anything further with them for now. Great, now that we have that, let's go ahead and let's slowly start styling our form. So in this top of the, let's go ahead and let's give it a class name of max-w-5xl and max-auto. So what I'm doing with this class name is I'm limiting how wide this can go because when you resize your screen, the elements get as wide as the screen. So I'm limiting it so that they can only go to 5xl width, right? And it can hover if you have the extension and you're going to see exactly how much that is in pixels. And I'm using MXAuto to tell it that once it reaches this screen limit and once it goes above it, just center the items by adding the equal padding from left and right. Sorry, the equal margin from left and right. Now let's also add a flex. MD is going to have items dash center and MD is going to have justify dash center like this. H dash full and P dash six like this. Great. Let's go ahead and let's open another div inside and let's add an h1 element inside of that div which is going to say name your course like this and let's give this a class name of text dash to Excel like that. And make sure to refresh your page. So you're seeing the most up to date changes after you've shut down your app and install some packages like this. Great. And below that, go ahead and add a paragraph and add something like, what would you like to name your course? Don't worry, like this. You can change this later. So something like this, It doesn't matter. And you can see that when I say this, it works, but I'm having an error here because I'm using this quote. So you can escape that by using appos, for example. So I'm gonna do that. I'm gonna replace the quote with appos. There we go. So you can see how the syntax recognizes it and it still looks the same. So in the text, you can see that it's still an apostrophe right here. Great, now let's give this paragraph a class name of text dash small like this and text dash slate dash 600. it like that. There we go. So now it looks a tad better. Perfect. Now below this paragraph, let's go ahead and let's add our form element, which we imported. So form like this. And what we have to do is we have to pass it the all the form props from this hook, which we created right here. So we're going to do that by simply spreading the hook inside. So I'm going to write dot dot dot form like that and no more errors. Perfect. Now inside, I'm going to write a native your HTML form element like this. And I'm going to go ahead and I'm going to give it an on submit function. The form dot handle submit. And inside, we're going to pass our custom on submit function. So we have access to these values here. And then we can send them to the server or do something else with them. So inside of your handle submit, just pass in the on submit like this. Great. And inside, give it a class name of space dash Y dash eight. So all of the elements inside of this form are going to be equally spaced on the y-axis, right? So one below another are going to have equal space. Great. And margin top of eight. So it's separated from the title and description, which we've written here. Great. Now let's go ahead and let's add the form field. So I'm going to write form field like this. And form field is going to be a self-closing tag. So let's go ahead and give it a property of control, form.control like this. And let's go ahead and give it a name of title like that. And let's give it a render. So in this render, go ahead and immediately destructure the field. And let's go ahead and immediately return a form item like this. Great. So let's check whether we have this imported. It looks like we don't. I forgot to import that. So make sure you also import the form item from s slash components UI form. And now, whoops, I accidentally opened the types. And now you're not going to have those errors. Great. Now let's add the form label, which is simply going to be course title like this. And below this, let's add the form control like this, which is going to store our input, which is a self-closing tag. So let's give it a disabled prop of is submitting. And let's go ahead and let's give it a placeholder of e.g. And let's use single quotes, advanced web development like this. There we go. And let's also spread all the field inside. So what did we do by spreading the field? Well, you can take a look inside of this field here and you can take a look inside of the t field values. let's try and find them alright I cannot find them right now but basically what we are doing here there we go so we are passing the on change, on blur, disabled value, name and ref right so by spreading this simple thing in one line we are replacing it from manually writing on change and on blur and all that stuff right so we are just passing the field spread like this great now below this let's go ahead and let's add a form description right so go outside of form control and write form description like this which you should have imported from from a ui components form and let's add what will you teach in this course like this great and below the description let's add form message which is a self-closing tag which is going to to display errors if there are any. Great, now let's go ahead and let's go outside of this form item but still inside of the form And let add a new div and give it a class name of flex items dash center and gap dash x dash two like this and inside let go ahead and let add a link element from next slash link so make sure you import the link from next slash link and i'm just going to move it to the top like this great and let's go ahead and give this link an href of slash like this. And let's add a button inside, which is just going to say cancel like this. And let's give it a variant of ghost and very important, give it a type of button. Otherwise, it's going to be used to submit the form and we don't want that. And just below this link element, go ahead and add another button element. And this one is going to say continue like this. this is going to be a type of submit so once user clicks on this we're going to submit the form but we're going to add a disabled prop to it if it's not valid so if the form is not valid or if the form is submitting in that case we're going to disable this form so you can see now when it types something it's enabled and what should happen what should happen now when i open the console and right and click continue i should see that in my uh in my values right here there we go because we wrote that inside of our onSubmit function to only console.log these values. Great. So what we have to do next is we have to change this onSubmit function to actually send a request to our API. So let's go ahead and do that now. So I'm going to change this onSubmit function to an asynchronous onSubmit function. I'm going to go ahead and open a try and catch block. you actually don't need to do anything with the error here and for now let's just go ahead and write console log something went wrong we're going to change that to a toast later and now let's write const response to be await axios which we have imported dot post slash api slash course and let's pass in the values as the body and then we're going to use router dot push whoops we didn't add the router. So let's go above the form hook. And let's just write const router to be user router, which we should have imported from next. Oops, my mistake. So not next slash router, it should be next slash navigation, right? So always check for that. Otherwise, it would not work. Great. So after we get the response from this Axios call, we're going to get redirected to that course edit page. So I'm going to write router dot push like this. I'm going to use backticks slash teacher slash courses slash and I'm going to use response.data.id like this. Perfect. So now if you try and let me open my console tab here and now if you try and write test and click here you're going to get something went wrong that's because we have a 404 because we never created that API route but before we do that one thing I want to do is add our toast element instead of console logs here, right? So let's go ahead and let's go inside of our terminal so we can install a new package. So I'm going to go ahead and write npm install react-hot-toast like this. Let's wait a second for this to install. And then you can run your app again, make sure to refresh your local host. And let's go ahead and let's go inside of our components folder and go ahead and create a new folder called providers. And inside, create a toaster-provider.tsx like this. Mark this as use client and write export const toast provider like this. And very simply, it's just going to return a toaster from react hot toast. So let's go ahead and import a toaster from react-hot-toast like this. and let's actually destructure it like this. There we go. So it needs to be destructured. So import destructured toaster from react-hot.toast and just return it as simple as that. And make sure you have used client. That's very important. And now let's go inside of our app folder, inside of the root layout right here where we have the metadata, our font and stuff. And let's go ahead inside of the body here like that. And let's just go ahead and add a poster like this. but not from React Hot Toast. We're gonna use the Toast provider, my apologies, like this. So Toast provider, and make sure you have imported that from add slash components provider, toaster dash provider, like that. And what you can do now is you can go back inside of your dashboard, routes, teacher, courses, whoops, create, pages at TSX. And instead of console log, you can now use the Toast. So I'm gonna use Toast from React Hot Toast dot error. something went wrong like this. And let me show you where I imported this toast. So right here, import toast from react hot toast like this. And let's move that to the top. And now if you try, you can see that we have a much more graceful error here. There we go. Something went wrong. Perfect. So in the next part, we have to create an API, which is going to handle this. But in order to do that, we're going to have to set up our Prisma and our course model. So let's go ahead and do that. So let's go ahead and let's create our schema for the database. In order to do that, first thing we have to do actually is we have to install a couple of packages here. So let's go ahead and let's close everything and let's go inside of our terminal here. So I'm going to shut down the app and I recommend you do the same. And let's first install Prisma. So npm install dash capital D Prisma like this. Once this is done, we're going to go ahead and run a command which is going to initialize Prisma inside of our project. So let's go ahead and write npx Prisma in it like this. There we go. So now what you're going to notice is that we have a couple of new files here. So I'm going to close everything. And now we have the Prisma folder right here. And inside we have schema.prisma. One more thing that you're going to notice is if you visit your dot environment file, after our clerk environment variables, we now have this big comment and a database URL. So what we have to do now is we have to change this database URL with our actual URL, because this will not work. This is just a dummy URL. So for my database, I'm going to use MySQL, and I'm going to use PlanetScale. You don't have to do this exactly as I am. So it doesn't matter. all you have to do is get this database URL and it would be good if you found it for MySQL. So there's a bunch of free solutions like spinning up your own MySQL locally using Docker or you can just Google free MySQL instance. You can also visit my Discord where a couple of people have found some free solutions but PlanetScale is by far the fastest solution but it does require a credit card. So let's go ahead and click on start with an empty database, right? I don't know what your view will be. But maybe you're gonna immediately see something like this, maybe. So let's go ahead and give our database a name. So I'm going to use it LMS like this. And make sure you select the free hobby option. So if this is your first time using PlanetScale, or if you only if you have never created databases here, you always have one free database, right? So you can always remove your old one and start with this one if you're just using it for a tutorial. Great. So we have a free database here. And you have to click create a database. If your button is disabled, it means you have not added your credit card. So the reason they require a credit card is to prevent fraud and free tier abuse. So just make sure you select the free option here. And make sure this says free as well. And then you can click create a database. So what we have to do now is we have to select Prisma because that's what we're using. So select Prisma right here. And then you are going to have to create your password. So just click create password. You don't have to change anything inside of this input like this. There we go. Now that you have this, you can copy this too and save it somewhere. But what we're going to do is we're going to go ahead and just copy this database URL. So go ahead and find this and click copy like this and replace the database URL like this. There we Okay, you can see how now it starts with MySQL right here. And this is my password and all the other stuff. Great. So now that we have that, we have to modify our schema Prisma. So I'm going to go ahead and copy this. And I'm going to go inside of my schema.prisma. I'm going to select everything and I'm going to paste it. So you can copy it from here or you can just find it in the PlanetScale and copy it from there. So if you're using MySQL with some other solution, which is not PlanetScale, just make sure that you add relation mode Prisma right here. Make sure it matches my schema Prisma. So most important is that the provider is MySQL and that relation mode is Prisma like this. Great, and after you've done that, you're pretty much done. So you can go ahead and look at some instructions here if you wanna learn more about how it works. But I'm gonna stop here and I'm gonna go ahead and I'm gonna create a util which is gonna enable us to access the database using Prisma. So let's go ahead and close everything. Let's go inside of our lib folder and create a new file db.ts like this. Let's go ahead and, well, we cannot write anything here. First, we have to install a package called Prisma client. So let's go ahead and write npm install at Prisma slash client like this. And let's wait a second for this to install. Great, after this has been installed, you don't have to run the project yet, but you can already import Prisma client, Prisma client like this from at Prisma slash client like this. And let's go ahead and let's export const db to be global this dot Prisma, pipe pipe new Prisma client like this. And then let's just write if process dot environment dot node environment is not production, in that case, assign the DB to global this. So global this.prisma is equal to DB like that. And as you can see, now we have some errors because Prisma doesn't exist inside of the global this element. So let's use declare to add it there. So declare global var Prisma is equal to Prisma client, whoops, pipe undefined like this. And there we go. The errors are now gone. So why are we doing this? Why are we not just doing export cost DB new Prisma client like this, right? This should work. Ignore this for now. Like imagine if we just did this So this should technically work right And yeah it correct It would work But the problem is it would cause issues in development because we have something called hot reload which means that every time we save a file a new Prisma client instance would be created and that would easily overflow your project and it's going to crash in development, of course. But in production, it actually works exactly like this. Take a look at this code, right? So we have a constant called DB. And what we do is we look for global this dot prisma. If we don't have global this dot prisma, in that case, we use new prisma client, right? And then we have an if clause here, which checks if we are not in production. So if we are in staging or locally or development, in that case, we're going to use global this dot prisma to assign the DB. Why are we using global this dot prisma? And how does that help with preventing the overflow error, which I just warned you about? Well, that is because global this is not affected by hot reload. So by appending the Prisma client to global this, we have did a little trick to save us from crashing our project in development. Great. So make sure you have this little util. And now we're ready to create our API route. Well, actually, we are not. We first have to create our models, right? So let's do that first. My apologies. So go inside of your Prisma folder inside of schema.prisma right here. And let's go ahead and let's create a model course like this. Let's give it an ID of string ID and add default to be UUID like this. And before I go any further, if your schema.prisma file doesn't have this pretty colors like mine has and this clear syntax, which separates string from an ID and strings from a variant, right? Well, what you can do is you can install an extension called Prisma. So just make sure you have that. Great. So let's add, we added the ID. We're also going to need a user ID, which is going to come from clerk. And it's just a string. We're going to have a title, which is a string and DB dot text like this. We're going to have description, which is a string and DB dot text as well. Image URL, which is also going to be an optional string like this, and DB dot text like that. Price, which is going to be an optional float, is published, which is going to be a Boolean with the default value of false like this. And we're also going to have the category ID, which is an optional string and a relation to the category. So I'm going to leave the space for that now. So we're going to go back to this in a moment, but I just want to finish the other stuff. So let's also add the attachments. Oh, my apologies. Yes, we can add attachments, which is just going to be attachment and array. So we're going to have an error here because we don't have this model defined, but we're going to do that in a second. And let's also add created at to be a date time at default now. And let's also add updated at to be date time at updated at. like this. So first things first, I think I have a typo here. So category, that's not correct. It should be category ID like this. So make sure you don't have the typo here. And now let's quickly just fix this attachment thing right here. So what we have to do is we have to create an attachment model, which is going to be relating to this model course. So let's go ahead and let's write model attachment like this. It's going to have an ID, which is a string type of ID and a default value of UID like this. It's going to have a name, which is a string and the URL, which is a string and DB dot text like this. Now we have to create a back relation to this course. You can see we still have an error here. So we do that by adding course ID, which is a string and course to be a relation with a course model. Make sure you don't misspell it and add at relation fields, course ID like this, references ID and on delete cascade like this. And you can see we no longer have this error here. But now we have a warning here. And we can easily fix that by adding at at index course ID like this. Great. And above that, let's also add created at date time at default now and updated at to be date time at updated at like this. Great. So let me just expand this if I can, or I can just zoom out so you can see this in one line. So course is a course model relation fields course ID right here, which is referencing the ID field of the course, which is right here. And the on delete cascade means that once the course has been deleted, all the attachments will be deleted as well, which have a relation to that course ID. Great. And now we can go back here. And let's go ahead and let's create our category model. So what we have to do is just below the attachment or actually above it, right? So model category like this is going to have an ID, which is a string at ID at default UUID like this. Name is going to be a string and unique like this. Sorry, did not execute. And courses is going to be course like this. So category can have many courses inside of it, right? But a course can only have one category. So go inside of the course right here and go ahead and add a relation to the category. So category is category, which is an optional relation, fields, category ID like this, references ID like that. And what we have to do now is just add an index here. So add that index, category ID like this. Great. And now you can see that we no longer have errors in the category either. Perfect. So our course is still not finished as a model. But later on, we're going to define some chapters and other stuff. But it's not important for now. For now, what we want to do is just a simple creation of this model. So you might be wondering, why is description optional? Why is the image optional? Price and category? Why are all of those things optional? Well, the reason they are optional is because they are not required for creating the course, right? We are not going to enable publishing the course if we don't have a price image URL and description. There might even be a way to do this with Prisma, but I'm going to do it manually using the API logic, right? So only the title is required to create a course, but everything else is completely fine if we don't have it. Because when you create a new course, so when we enter this title here and then we click continue, it's not going to immediately publish that course, right? It's just going to be a draft. And then when we add description and image and price and mark published as true, only then it's going to be visible to the students. Great. So it created a very good start to our Prisma schema. And what we have to do now is we have to create an API route, which is going to, well, use all of this information. But before we do that, one important thing you have to do every time you play around in schema.prisma, you have to go inside of your terminal, you have to shut down your application, and you have to write npxprisma generate. That is one of the two commands that we need. So what generate did is it added these models to our util right here, which we created in the lib folder, right? So now when we import this DB and try to access it, so let's try, I can use it DB dot, let's see, course. There we go. You can see that now it auto-completes my course, right? Because it knows it's inside of my schema Prisma, right? And same thing is true for attachment. You can see how it auto-completes, right? If we didn't run npx Prisma generate, that would not work. And the other thing we have to do is we have to run npx Prisma DB push. So the first command was to add it locally. But the second command is to push it to planet scale or whatever database URL you put in the environment file. So if you get any errors here, that is because your environment variable is most probably incorrect. So just double check in your dot environment that you have a proper database URL, which is accessible and working. And of course, make sure it's a MySQL. If it's not, if you're using something else, then just make sure to change the provider in the Prisma schema to use that different provider. Great, great job so far. So now we are finally ready to create our API route to create our first draft course. So one thing I want to do is I want to go inside of the app folder, dashboard, routes, teacher, and create page right here. And I just want to modify my API route. So right now it's going to slash API slash course. Well, I want to modify it to go to slash courses. So I made a typo here. So go to slash API slash courses. And I just noticed that we still have this test folder. So if you want to, you can remove it. We're not going to work with it. Great. And now let's go ahead and let's close everything here. And just as we did in the first part of the tutorial where I taught you how to create the routes, well, now we're going to create a new folder inside of the app folder called API. And inside, we're going to go ahead and create another folder called courses like this. and inside we're going to create route.ts. So this is going to be equivalent to slash API slash courses. Let's go ahead and let's write export asynchronous function post which takes the request which is a type of request and let's open a try and catch block with the error inside. And one thing I'm going to do in all of my API routes is a console log and then I'm going to write in square brackets courses like this, and then I'm going to pass in the error. So you don't have to do this, but it will help you a lot in development. If you see random errors popping up in your terminal, then you're going to know that something is wrong with the courses API route right here. And then we're going to actually just return new next response, which you can import from next slash server and go ahead and just say internal error like this and return a status of 500 to the client so it knows something is wrong. Now let's go ahead and let's extract the user ID using a clerk using its alf function like this And then let go ahead and let extract the title which we get from our form And we can get that using await request dot JSON like this Now let's go ahead and check if there is no user ID. In that case, return new next response, unauthorized, unauthorized like this, and passing the status of 401 like this. Great. Now let's attempt to create a course. So we're going to go ahead and write const course to be await db. And make sure you import db from add slash lib db. So it's that little util that we created just recently. So make sure you have this import here. Great. And now let's write course await db.course.create like this and pass in the data. And we're going to need a user ID, which is required. And we're going to need a title like this. So let's take a look at our schema Prisma a bit. So it's easier what we are looking at. As you can see, user ID is required and title is required, but all the other fields are optional, right? So that's why this is working. Great. And all we have to do now is write return next response dot JSON course, right? And this is going to immediately be caught inside of our dashboard routes teacher create page dot DSX on submit function, because we're going to get this response, then we're going to get the data, and we're going to get the ID. So that's why we are returning the course, right? So response.data is not part of our API route. We are immediately returning a course, but response.data is here because we are using Axios, right? So when we do Axios.post, we have to get response, we have to get its data, and then we get the course ID, which is represented as ID in here. So the moment we create a new course, we're going to redirect to the page where we can add the course description image and stuff like that. And while we are here, we can also add toast dot success like this course created like this. So we know it's going well. Now let's go ahead and let's attempt to try this now that we have that route. So make sure you saved your API for a file. And what I expect to happen is a success message right here in the network tab and a 404 page inside of here. So let's click continue here. And it looks like that is not true. Let's see why. Let's see what happened exactly. So it still says not found. Let's see. Oh, I think that's because I have to. Yes, because I'm not running my application. So perhaps you're not running it as well. So just go inside of your terminal and make sure you do npm run dev and then refresh your application. So I'm just going to prepare my stuff again here. my terminal is ready. And I'm going to go ahead and select the network tab right here. And let's write test, continue. It's loading, it's loading. And there we go, course created. And we have a 404 page. Perfect. So you might be wondering, well, how can I see a database in my inside of a table, right? How can I see all of my items and entities? Well, Prisma has a really cool thing. So I'm opening new terminal here and I'm going to write MPX Prisma Studio like this. And you can see that that is opened on localhost 5555. And in here, you can see all of the models which we defined in our schema. And as you can see, I have one course here. So when I open this, there we go. I have an ID. I have a user ID. I have a title of test and everything else here is empty and is published is false. Great. You can see I have a relation with a category. Well, I don't, but there is a potential relation with the category here. And you can see I have zero attachments here. So there we go. I have a created ad and updated ad files. Perfect. So what I'm going to quickly do is just fix this 404 error right here. So if you want, you can keep your Prisma Studio running. And let's go ahead inside of the teacher folder, inside of the create folder, sorry, inside of the courses folder here and create a new folder called course ID like this. So this is a specific convention in Next.js where we write dynamic folder names, right? So this is not going to be hard coded as course ID in the URL. Instead, it's actually going to be a value which we can then extract in our page. And then we're going to know which course do we load using the ID. So inside this folder, create page.tsx, make sure this folder is inside the brackets. And let's go ahead and let's write course ID page like this and a div saying course ID page like this. And there we go. You can see I no longer have my 404 error. So let's go back inside of the courses now. So you can just click on this sidebar here, click on new course, write test again, click continue. And there we go success message and look at my URL. It's using the newly created ID right here. And let me quickly show you and explain how this course ID now works. So what you can do here, once you have this course ID in a folder, every server component inside of this folder can now access that through params, right? So I'm going to write params here. And I'm going to give it a type of params, which is an object, which is going to have a course ID, which is a type of string. And then I can write course ID to be params.courseID like this. And there we go. Look at this now. Now it says exactly which course ID is loaded in here. And let's look at our Prisma Studio. I'm going to refresh this course page. As you can see, that exact ID is in here. So it's correct, right? And I can manually copy the ID from our old one and change my URL here since we don't have a table for now. And there we go. Now it's loading that course. So that's why we did that dynamic folder, because we're going to use this information in this server component to actually fetch the course and all of its information, right? Because we are going to have forms to update the course, but we also need to provide the user all the existing information about this course, like its title, its description, its image, and all the other stuff, right? And this is the way we're going to fetch that using the course ID. So if this is not working for you, few things to check here, make sure your course ID is not misspelled here. Make sure it's not misspelled here. And most importantly, make sure it matches the folder name and make sure your folder has brackets, right? So make sure there are no misspells here. That's the only reason it should not work, right? And you can even see, you can manually change the URL to, I don't know, one, two, three. And there we go. Now it says course ID one, two, three. And if we attempt to actually do this in our finished app, we're going to throw an error, right? Because there is no course to be found using this ID. Great. So great, great job so far. What we're going to do next is actually start working on our form. So let's continue with the development of individual course page. So let's go back inside of our course ID page where we rendered the params course ID. And what I want to do here is I want to fetch the course. So let's go ahead and do that. So make sure you mark this course ID page as an asynchronous function like this. And while we are here, let's already import the DB from as slash lib DB like this. And then inside we can write const course to be await db.course.find unique like this, where course ID, whoops, ID is equal to course ID. And it's actually params.course ID like this. But one more thing I want to change is that we also check that the person who is trying to edit this course is also the creator of this course. And we know who the creator is by checking the user ID. So that's why we added the user ID to the model course. So that's how we're going to keep track of which user created which course. So let's go ahead and let's extract the user ID from the out function, which I've imported from ad clerk next JS like this. So make sure you have both of this here. And before we attempt to fetch the course, first let's check if we have or don't have a user ID. So if we don't have a user ID for any reason, let's just return a redirect, which we can import from next slash navigation. So make sure you add that as well. And we're going to redirect back to the root page like this. And same thing is true if we don't find a course. So let's go ahead and right. If there is no course in that case, let's go ahead and return redirect to a slash root page. Perfect. And now let's go ahead and let's create an array of our required fields. So we're going to start by developing a little, we're going to start by developing some heading, which is going to say you are missing two out of six required fields, right? Or something like that. So in order to do that, we need to create an array of the required fields. So let's write const required fields like this, which is going to be an array. And inside, let's put course.title, course.description, course.imageURL, course.price, and course.categoryID like this. Great. So make sure you have this array. And now let's go ahead and let's add a constant total fields to be required fields dot length like that. Const completed fields are going to be required fields dot filter. And you can just write boolean, right dot length. So what this is going to do is this is going to get the number of all of the required fields. And this is going to get a number of all of these fields, which don't equal to false. So if description doesn't exist, this is going to return a null. And when we filter that to the Boolean, it's not going to add to this completed length field. And I just think I have a type of here completed. Yeah, so it's completed fields like this. Great. And now that we have that, Let's go ahead and write const completion text like that. Open backticks and open parenthesis and go ahead and write completed fields slash total fields. So this is going to say like two out of six, right? Or in our case.\nThis one, two, three, four, five. Later on, we're going to add another required field. Perfect. So now that we have that, let's go ahead and let's go back inside of our teacher mode here. And you can just click on new course. Just write something and click continue. And you're going to go back here. If you enter something invalid in the URL, you're going to get redirected back because we're not going to be able to fetch an existing course. So just make sure you are on this page with the course ID. So let's go ahead now and let's go inside of this div here. Let's give it a class name of p-6 like this. We can now remove this text here. Instead, let's open a new div with a class name of flex items-center and justify-between like this. And inside, open another div with a class name of flex, flex-call, and gap-y-2. Now let's add an h1 element course setup like this, and give this h1 element a class name of text-2xl and font-medium like this. Great. And now below this h1 element, open a span and write complete all fields, and then render the completion text. So this constant right here. So you can see that now it says complete all fields one out of five. That's because we already have the title, right? We entered that when we created the course. That's the one required field for even creating a draft course. Great. And now let's go ahead and give this span a class name. So I'm going to go ahead and I'm going to write text-sm text-slate-700 like this. And if you save, then this is what you're going to see. So we have core setup right here and complete all fields. And we actually don't need this space here, at least visually, it just looks a bit bad. So let me just remove the space from this completion text like this. So just a slash in between no spaces. And when I save, I think that looks just a little bit better. So now let's create our first section, which is going to be for adding the description, the title, the image, and the category, right? So we're going to call that customizing our course. So let's go outside of this div, which wraps our title and our span, but still inside of the p-6 div right here. So I'm going to go right here and go ahead and open a div inside and give it a class name of grid-calls-1. So just make sure you don't misspell it like I did. That one, okay. So on mobile devices, we're not gonna do any grid, right? It's just gonna be one thing below another. But on medium devices, it's gonna be grid-calls-2, like this, so two items side by side. Let's give these items a gap of 6 and let's separate this from the title by margin top 16, like that. Now inside I'm going to open another div like this and then just open another div with a class name of flexitems-center and gap-x-2. Perfect. Let's go ahead and let's write an h2 element, customize your course, like this. and let's give it a class name of text-excel like this. Great, so you can see how this looks now. One thing I want to do is I want to add a little icon next to the side of this text, customize your course. And we're going to call that icon component, icon badge. And we're going to reuse it throughout this tutorial. So let's go ahead and let's create that file now. So I'm going to go and find my global components folder right here where I have my navbar routes. And I'm going to create a new file called icon-batch.tsx. So make sure you create that file. And let's go ahead and let's import Lucid icon from Lucid React. Let's import CVA and type variant props from class variance authority like this. And don't do the same mistake I did like this. Okay. And let's also import cn from as slash lib utils. So you have this installed because you did chat cn in it, right? So this is where that class where this import comes from. And the same is true for Lucid React, just in case you were wondering how do we suddenly have this. Great. And now what we have to do is we're going to use this CVA thing to create different variants for this icon badge because I want to be able to change its color and I want to be able to change its size. And there are many ways we can do that, but this is a very standardized way of doing it. So first let's create the variants for the background colors of this icon badge. So let's write const background variants to be CVA. Let's go ahead and let's give with a default class name of rounded-full, flex, items-center, and justify-center, like this. And now let's open an object, and let's create variants, like this, and a variant option, so variant, a single, right? So this is multiple, and this is just a single variant, and that variant can be a default version, which has a BG-Sky 100, or it can be a success version, which has bg-emerald-100 like this. And now let's go ahead and let's give it an icon variant like that. The default one is going to be text-sky-700 or the success one is going to be text-emerald-700 as well. Great. And now let's add a size variant. So size like this. default is going to be p-2 like this and small is going to be p-1 like that great and now outside of variance let's go ahead and let's write default variance and let's just write variant to be default and size to be default like this great and now let's go ahead one more time and let's create the icon. So actually we don't need this icon variant, I think. Let's go ahead and let's develop further. So we're gonna logically see if we need it or not. So I'm also gonna separately create the icon variants because I don't think we can actually mix and match it because we're gonna use this background variants for two different elements. So let me just clear this up a bit for you. Why am I even doing this? What is this? Maybe you've never even seen this. So let me just quickly jump back into components UI and take a look at button, for example. So this is what I'm doing. Look at how ShadCN adds variants to the buttons, right? So because of this CVA library, we can add a default value, a destructive value, an outline, secondary, ghost, and link, right? You know, when we give a size to our button and it magically becomes small or when we give it a variant of destructive and it magically becomes red. Well, I wanna reuse that logic and that structure because I think it's a really good standard of doing it rather than just a bunch of template literals, right? So that's what I'm trying to do for this icon batch component inside of here. So just stay along with me and you're soon gonna see the result of doing this. So let's separately write one more time const icon variants, CVA, and let's give it an empty string for the default class. Let's open an object again and let's write variants and let's write a single variant like that. Default is going to be tax-sky-700 and success is going to be tax-emerald-700 like this. Let's go ahead and let's write a size. Default is going to be h-8w-8 like this and sm is going to be h-4 and w-4 like this great and now outside of this variance just add default variance for the variant option we're going to use the default one and for the size we're also going to default to this one right so default variants tell us okay so for this key variant right here which one of these so either default or success should i default to and that's what we've write it here, right? So I can change this to success. And then that's going to be the default one if user doesn't pass any variant as the props, right? So that's what this block of code does. Great. Now let's go ahead and let's create types using this CVA items right here. So write type background variants props. And that's going to be variant props, which we already have imported, right here. So variant props, it looks like I misspelled it. Variant props like that. And it's going to have a type of background variance, right? Or this constant right here, which we wrote first. Great. So we have that type. Now below that, create a type icon variance props to be variant props like this, type of icon variants like that. All right. And now let's finally create the interface for this component. So interface circle, sorry, icon badge props extends background variant props and comma icon variant props like this and then open an object. So let me try and expand this. There we go. Or I can even zoom out. So what's important is that this is in one line, right? And then open an object at the end. And inside, go ahead and write an icon, which is a React. Sorry, which is Lucid React. So Lucid icon, my apologies, like this. Great. So now we have the icon badge props right here. Let's go ahead and let's export const icon badge like this. And let's write icon. Let's immediately remap it to icon. And now we automatically let the variant and we get the size, right? But we still have the errors. So let give this icon badge props type here Did I do this right Let me just go ahead and try and continue like this There we go Okay so I just had to open the arrow function. And now you can see that it expects the size, right? And you can see how it can be default or it can be small. And same for variant. It expects it to be default or success. Perfect. And now we can just simply write a div inside with a class name of cn background variants and go ahead and pass in the variant and the size like this and inside open an icon which is a self-closing tag so just make sure you remap this icon to icon with the capital letter like that and same thing here so just a class name with cn icon variants and go ahead and pass in the variant and size like this. There we go. So my apologies, we actually do need this icon variant inside, it seems. Or actually, maybe we don't. I know I'm going back and forth, but you're going to see how this looks now in a second. So we're going to decide whether we need it or not. All right, so you can save this file. And now let's go back inside of our page right here where we added the h2 element saying customize your course and let's add this icon badge from add slash components icon badge so it's the component we just created and i imported it right here and go ahead and give it an icon of layout dashboard from lucid react like this all right let's move this to the top and seems like i'm having an error here so let me just refresh to see what's going on. Okay. It says icon batch cannot be used as a component. Let's see why that's happening. So we are importing it from the correct way right here. And we are exporting it. But I forgot to write the return function. So right, so I just randomly start typing divs, make sure you return this. That's why we have that error. Great. And now there we go. Look at it. Look at this. Now we have this nice little section, which has like a little icon for customizing our course right here on their side. For example, we're going to have a dollar sign, which is going to say sell your course, right? So we're just going to separate this entire thing into nice little sections here. So let me show you what did we exactly achieve by adding this variance here. So as I said, this is going to be reused throughout the project. So if you find the icon badge here, Try and give it a size of small, for example. And now you can see how the icon is small, right? And that's all without us passing custom class names, right? So it's very structured this way. And I can also mix and match that with a variant. So I can go ahead and write success, for example, like this. There we go. And now you can see it's green. So I'm pretty sure that we actually don't need the icon variant because we're not passing it anywhere here. So go back to your background variants and just remove the icon variant. I'm very confident we don't actually need that. If we do, we're going to go back to this component and add it. But yes, since we're not extracting it in the props, we're not even passing it in any of this background or icon variants. So I'm pretty sure we don't need it. Great. So now that you have that, you can remove this variant and this size and just leave it like this. Great. So now that you have that, we are finally ready to start creating our forms for updating the title and the description, right? So we're going to work by using the patch method to individually update all of these fields. And then how we update each field, that's going to refresh this counter for completing all fields, right? When I add a description, it's going to say two out of five, right? That's what we're going to do next. Great, great job so far. So now let's go ahead and let's create a form which we're going to use to display the current title of this course and also to enable updating the title. So go ahead and outside of this div which is encapsulating the icon badge and the h2 element right here go ahead and add another element called title form like this and if you save you're going to get an error because it doesn't exist yet. And I just already want to prepare the props for it. So initial data is going to be the course which we just loaded like this. And I want to keep a separate prop course ID just for easier working with so course dot ID like this. Technically, we could also get that from initial data dot ID. But I just found it easier like this, you can pick and choose what you like. If this is redundant to you, feel free to use the initial data dot ID. But I want to have it clear like this. Great. So save this file. And now let's go ahead and let's create this title form. So what I'm going to do is I'm going to go inside of this course ID folder, and I'm going to create a new folder underscore components, right? Because these components are only going to be used inside of this folder. And inside, create a title dash form dot DSX like this. Go ahead and mark this is use client and export const title form like this and return a div saying title form like that. And now go back to your page.vsx where you added the title form and just import that at the top like this. So the slash components title form and I like to separate this from the at signs. And now I have a text saying title form right here. Perfect. So let's go inside of the title form. And first thing I want to do is I want to create an interface that matches the props we are passing. So interface title form props like this is going to have initial data, which is going to be a title and a type of string. And we're going to have the course ID, which is a type of string as well. Now let's go ahead and let's extract this props here. So initial data and course ID. And let's map those props to our newly created title form props like this. And now in your page, you should not have any errors in this title form right here. Great. And now let's go ahead and let's add everything we need for our form. So import all as Z from Zod like this. Let's go ahead and import Axios from Axios like that. Let's go ahead and let's import Zod resolver from at hook form slash resolvers slash Zod like this. And let's also import the use form from React hook form. So use form from React hook form like this. Great. And now while we are here, let's also import all the elements we are going to need to develop the UI. So import from at slash components slash UI slash form. we're going to import the form form control form field form item and form message like this and let's also import the input separately from components ui input and we can do the same thing for the button but from ui button like this great and now below our interface let's go ahead and let's create our form schema. So const form schema is equal to z dot object like that. And all it takes is a title z dot string like this minimum value of one and a message title is required like this. Great. And now that we have the form schema, we can go ahead and create the form hook. So let's be use form. Let's go ahead and give it a type of z.infer type of form schema like that. Go ahead and open an object inside of the parentheses here. Let's add the resolver to be zod resolver which we imported and use the form schema inside and default values are going to be initial data like this. Great. And now let's go ahead and let's extract the is submitting and is valid from this form. So const is submitting and is valid from form.form state like this. And const on submit function is going to be an asynchronous arrow function, which takes in the values, which are a type of z.info type of form schema inside. So it's an arrow function, nothing special. You've already seen this when we were creating the course. And let's go ahead and just console log the values for now. And we're going to learn how to work with them later. All right, now let's go inside of this div. And let's give this div a class name of margin top 6, border, bg-slate-100, rounded-md, and padding of 4. So you can see how now my form is separated from this text and how it has a little border, rounded corners, and a padding. Great. And now inside of this, let's go ahead and let's open another div with a class name of font-medium flex items-center and justify between like this. So these two elements inside of this div are going to be apart from each other, each on its own end. First one is going to be the course title text like this. And then below that, open a button, which we imported. And what we're going to do is we're going to add a pencil icon from Lucid React. So just make sure you imported the pencil icon from Lucid React. I'm just going to move it to the top like this. Let's go ahead and give this pencil icon a class name of H-4, W-4, and margin write 2, like that. And let's give this button a variant of ghost, like that. Great. And let's go ahead and write edit title like this. Great. and now let me just zoom this out just a bit so it looks better there so this is what it's going to look like great and now what we have to do is we have to add a state for editing or not editing right so when we click on this thing we want to show the input otherwise we just want to show the current title so in order to do that we're going to have to use state so let's add const is editing Set is editing. Use state from React with the default value of false. So just make sure you import use state from React. I'm going to move it to the top right here. Great. And now that we have that, we can modify this button even more. So go inside of this button and write is editing And open parenthesis like this and a fragment cancel like this And in here question mark is editing like that and then and wrap this inside of a fragment as well. Like that. Perfect. And now I want to write a very simple toggle edit function. So let's go just below this and let's just write const toggle edit like this. And all it's going to do is set is editing. It's going to get the current and it's going to set the opposite of the current value like this. And then we're going to use this toggle edit whenever user clicks on this button. So on click toggle edit like that. and now if all of this is correct when I click it should be cancel like this there we go beautiful right so when it's editing it's going to have an input here and we are able to cancel otherwise it's just going to say the current title and when we click it will show an input perfect so as you probably noticed you could have done this in a different way like you could have added a question mark here and you could have put the column here right and remove this for example maybe that's even better that it actually makes a bit more sense. Yeah, we can do that. So pick and choose. It's both are working, right? But let's be consistent. I kind of like this. Okay. And now what I want to do is I want to go outside of this div, which is encapsulating the course title and the button. And I'm going to write, if we're not editing, in that case, open a paragraph, initial data the title like this and give it the class name of text dash small and m2 dash two like this there we go so you can see that now it says the current title of this course and when i click on it it should disappear because instead we're going to render the input right but when i cancel like it's saying yes okay this is the course title and you can change it if you want to great so we have that now and now let's go ahead and below that and let's write is editing and so in this case we're going to go ahead and we're going to render our form element which we imported let's go ahead and spread the form element inside let's open form html native element let's give it on submit to be form dot handle submit and pass in the on submit function like that. And let's give it a class name of space dash y dash four, empty dash four, like this. Great. Now inside, go ahead and open a form field, which is a self closing tag. Go ahead and give it a control or form dot control. And go ahead and give it a name of title like this. And now let's open the render function. They structured the field from it immediately and immediately return the form item like this. And inside of the forward item, add a form control. And now add an input inside, which is a self-closing tag. So the props we're going to give it is disabled if we are submitting. So if we are doing an update request on the API, we're going to disable this input. let's give it a placeholder e.g and let's use backticks advanced web development or whatever you want really and let's pass this spread in everything in the field like that great so i think this is already enough for us to test this out so when i click on edit we now have the edit field like this perfect so that's exactly what i want so you can keep it in this one so you can see what you're doing. All right. And now what I want you to do is I want you to go outside. Well, actually, before we do that, go outside the form control and just add form message like this. Great. So this is going to display the errors if there are any. And now go outside of this self-closing tag, but still inside of the native HTML form element and add a div with the class name of flex items-center and gap-x-2 like this. Go ahead and add a button inside, which is just gonna say save like this. Let's give it a disabled prop of is valid or if we are submitting like this and a type of submit like this. There we go. Great. So we have finished the entire title form component here. So now if you take a look at here, I'm just going to open my console as well. And if I type new title, for example, and click save, there we go. You can see that I have log right here, title, new title. So what I'm going to change now is that it actually fires an API request in this on submit function. So let's go ahead and let's do that now. So I'm going to replace this console log with try and catch block. and let's use the toast from react-hot-toast. So toast error, something went wrong like this. So make sure you import the toast from react-hot-toast. I'm just going to move it to the top like this. All right. And in the try function, we're going to do await axios.patch. So patch is very important here. Slash API slash courses slash individual course ID. we have that as a prop and passing all the values like that and then write those dot success like that course updated like this great and toggle the edit so it returns back to its initial state and call router we don't have the router let's get the router so let's go ahead and let's import use router from next slash navigation like this. And let's go ahead and just after this toggle edit, let's add const router to be use router like this. And now we can call a router dot refresh. So once we save this, it's going to refresh the server components fetching the new data from the database. Perfect. So if I try this now, I'm very confident that I should get a 404 error. So I click save. And there we go. We have a patch of 404. And we have something went wrong in our toast right here. That's exactly what we want for now, because we don't have the patch function. So in the next part, we're going to create the patch function. Great job. Let's go ahead and let's create the patch function, which is going to update this course title. So I'm going to go ahead inside of my API folder. So go inside of the app folder, API courses, and create a new folder in brackets, course ID like this. So we can get the course ID from the URL. And inside, create a file route.vs like this. Let's export asynchronous function patch. We're just going to have a request, which is a type of request. Open a try and catch blocking here. and let's log the error. So I'm going to go ahead and I'm going to write course ID and an error like this. And let's return new next response from next server, internal error like this with a status for the client of 500 like that. Great. Now inside of here, let's go ahead and let's extract the user ID from out, which we get from at clerk slash next JS. like that. If there is no user ID, in that case, we can return new next response, unauthorized with a status of 401, like that. Great. Now let's go ahead and let's extract the course ID from the params. And we did not define the params yet. So the params come from our response object. So let's destructure them immediately. So params is a type of params, which is an object which has course ID, which is a type of string inside. So always make sure that this is in the second parameter, right? So it doesn't matter if you're not even using the request. It needs to go after the request. If you do this, it's not going to work because there is nothing to destructure from the request. My apologies. There are no params to destructure from the request. Great. So now that we have that, let's also get our values. So const values are equal await request.json like this. Great. And now let's go ahead and let's write const course to be await DB. So import that from at slash lib DB like I did right here. And let me just separate my imports like that. await db.course.update like that, where ID is equal to params.course ID and user ID is equal to user ID. So you can either write user ID like this, or you can use a short hat. And let's also give it a data object. And in here, I'm just going to spread the values which we passed like that. And after that, just return next response.json course like this. There we go. So let's just see if we used everything. Since I destructured the params here, I actually don't have to use params.courseid. I can just write course ID like this. So when writing these params, it's important that you confirm that this course ID is exactly the same as this folder name course ID. Make sure you didn't misspell in the capitalization or make sure you didn't forget the brackets. All right, and now that we have that, we can actually immediately go ahead and try this. So let me see if I still have my Prisma Studio running. I do. Great. So I'm going to fetch the course that I'm working with right now. And that is this one, DSFDSF. So this one, and let's go ahead and refresh this and let's try and change this to edited title and let's click save and let's see what's going to happen now. So it says course updated and it says edited title. If I refresh, it's still edited title. And if I refresh my Prisma Studio, this should also say edited title. Great. So we officially made our function working. One thing that I think it's not working, or maybe it is, maybe I just saw wrong. When I click save, this input should be disabled. Let's see if that's true. It looks like it's not. So let's see if I forgot to do that. So let's go back inside of our dashboard routes, teacher, create course ID components title form. So I want to use this as submitting also in this input. and it looks like I added it. Hmm maybe it just finishes really quick in that case All right so that seems to be working And now let go ahead and let create a very similar box like this one but for updating our description of this course. So in order to do that, we first have to add a Shatsian component called text area. So let's go inside of our terminal. I'm going to shut down the Prisma Studio for now, and I'm actually going to close this second terminal. So I'll just work in this one. I will shut down my app and I'm going to write MPX chat scene UI at latest add text area like this. And I think I misspelled this. So text area like that and confirm this installation. And then I'm going to run my app again. And I'm just going to refresh this page. Great. And now what I'm going to do is I'm going to go in this page that CSX where I have the title form and I'm going to copy it and I'm going to paste it below like this. So now you should have two of this course titles. And let's change this second title form to description form like that. And if we say we are going to get an error. So we're going to copy the title form inside of the components. And we're going to rename it to description dash form like this. And let's go ahead and let's rename the props like this. And let's rename the constant and this props right here. So the way I'm doing this is I select title form and you can see how it also selected this one and this one. But if I start typing, it's not going to change all three at once. But if I press command D or if you're on Windows, control D, you can see that now I have two arrow keys right here because I'm editing both of them. And if I press one more time, now I'm editing all of them. So I can easily change this to description form. all at once. Great. Now let's change the initial data to look for description. Like that. Let's change it here. Let's change this to description is required like that. All right. That seems to be all done. And now let's go all the way to the bottom here and let's change this to description like that. And let's change this form field to description as well. and now let's go back to our page where we added the description form and let's actually import it from dot slash underscore components. So the same way I did with title form. And now as you can see, I have a course title here and a course title here, but no description inside. So we have to change this title and we have to change this button right here to say edit description. So let's do that now. I'm gonna go back inside of my description form and find this where it says course title. Just make sure you are in the description form. Don't accidentally change the title form, right? So course title is gonna be course description like that. And this is gonna be edit description like this. There we go. So now it looks better. And now let's go ahead and let's go inside of this is editing thing right here. And what I'm going to do is I'm gonna modify just a bit. So I'm gonna go ahead and I'm gonna remove this class name. Instead, I'm going to open curly brackets and I'm going to import cn from s slash lib utils like this. So just make sure you have this imported and go ahead and open parentheses. Give it the default value of text slash small and margin top two. And then dynamically write, if there is no initial data dot description, in that case, style this as text slash slate dash 500 and italic like this. and inside of this, we're gonna extend it just a bit. So if there is an initial data dot description, then we can render whatever the description says. Otherwise, we can render no description like this and you can see how that looks now, right? Since I have no description in this project, it says no description here, perfect. And what we have to do now is we have to modify this to not be an input, but instead to be a text area. So let's go ahead and replace this with text area, which you can import from add slash components UI text area. So just make sure you have this imported like this and you can remove the input import like that. And let's go ahead and let's change this placeholder. So it makes a bit more sense. Instead of this, it's going to say E dot G. And let's go ahead and open this single fix. This course is about three dots like that. and I think this should already be working pretty well. So when I click this, there we go. We have a text area here and if I try and change this, this is the course description and click save. There we go. Course has been updated and now I have the description right here and if I refresh, it's still here meaning that it is saved. Great, great job so far. So now we can go ahead and copy these elements and modify them as we need for other fields. So now let's go ahead and let's create a little box similar to our description and title, but for editing the image of the course. So in order to do that, we're going to use Upload Thing. So head to uploadthing.com and go ahead and find the sign in button. And you can sign in using GitHub. So as you can see, I already have a couple of platforms inside. So just go ahead and find a new app here. So click create a new app like this and give your app a name. So in my case, it's going to be LMS and go ahead and click create app like this. Great. So what we're going to do now is we're going to click on the getting started right here. You have a description of what upload thing is. You can see how it works underneath and you can see how it manages file hosting. So So everything that you're interested in, you can find right here. So the reason I like iPod Upload Thing is because of this right here, where it basically says that we authorize users on our server, right? So we don't post to UploadThing.com like we would use in Cloudinary or, well, Cloudinary is an amazing tool, right? But sometimes we need a little more flexibility with our upload service. So this way, we are creating our entire upload service, right? So even if it takes a bit longer to implement than Cloudinary, for example, it gives us a much bigger set of tools to work with. For example, we can customize the ALF middleware on every request for upload, right? And that's something that's very useful for us when building our own custom app. So let's go ahead and let's click on the getting started right here. And first thing I want to do is I want to install these packages. So go ahead and copy this right here. And let's go inside of our terminal. write this, I will shut down my app and I'm going to write all of these tools here. So this is what it looks like in one line. npm install upload thing at uploading slash react and react dash drop zone like that. And go ahead and press enter and wait for this packages to install. Great. So after that is done, let's take a look at what we need to do next. So as you can see, it says we have to add the upload thing secret right here. So let's go ahead and let's prepare this right here. So I'm going to go inside of my environment variables. So I'm going to close everything, I will find my dot environment file. And after the database URL, I'm going to add uploading secret like this, right? So we're going to fetch it now. So just make sure you prepare this upload thing secret. And let's go ahead and let's go and click on the dashboard right here. And go ahead and find your project for mine is LMS. And go ahead in this sidebar and find the API keys like this and you can go ahead and just copy this entire thing and well you can actually replace this like that there we go upload thing secret and upload thing app id great so now we can go back inside of the documentation here so we just did step two which was adding the environment variables what we have to do now is we have to set up in your next js application and we are using the app router so let's use the app router way of doing it so the first thing you have to do is we have to create a core like this. So we're going to do that inside of app API upload thing core.ts. So let's go ahead and do that. So I'm going to go inside of my app folder here. I'm going to go inside of the app folder, API folder, my apologies, I'm going to create a new folder called upload thing like that. And let's go ahead and let's create a new file inside called core.ts like that. And inside you can just copy this entire code, right? So you can just click copy here and let's go ahead and let's paste it like this. There we go. So as you can see, we have to modify this out function here and actually make it use clerk, right? So this is what I was talking about, right? We have our own file router and in here we can define as it says, as many file routes as we like, each with a unique route slug and each with a unique configuration. Like for example, they put an image here with a max file size, but we can also do max file count, right? If we want to limit the number of files that can be uploaded at once. And not only that, we're also going to get type save components against this file router. So it's actually extremely useful. And as far as I'm concerned, this is the first time I've seen someone build a library like this. So that's why I like it that much. So let's go ahead now and let's modify this a bit to match our use case. So we're going to have a couple of routes here. And what I want to do first is I just want to clear this entire thing inside of the file router. So let me go ahead and remove this like that. I'm going to remove the comments as well, just so we can focus on exactly what we need here. So let's go inside of the our file router. And first thing we need is course image like this. That's going to be a function which takes in the image like that. Max file size is going to be four megabytes. Four megabytes like this. Max file count is going to be one like that. And let's go ahead and write a middleware here. And let's go ahead and let's just write a function out like that. And now let's go ahead. So ignore this error for now. We're going to fix that in a second. We also have to add on upload complete. And in my case, it's just going to be an empty arrow function like this. So now let's resolve this out thing. So instead of out, it's actually going to be called handle out like that. It's not going to take in anything in the props. Instead, it's going to destructure the user ID from out from clerk next JS like this. And then we're going to say if there is no user ID in that case, we're going to throw new error.\nunauthorized like that. Otherwise, return the user ID inside of an object like that. And then we can use the handle out like this in the middleware. So make sure you change the middleware to use the handle out like that. Great. And now let's add a couple of more routes here. So remember to put a comma here at the end. And let's add course attachments, course attachment, sorry, which is going to be an array of either a text or an image or a video or an audio or a PDF. So basically whatever could be useful for the students. Let's add the middleware, which is going to use the handle out function inside. And let's add the on upload complete like this. Now let's go ahead and let's add the chapter video. So we're going to also upload videos using upload thing. which is going to be a type of video. Let's give it a max file count of one and let's give it the max file size. And as you can see, you have a lot of options here. You can go all the way to 1024 gigabytes. So I'm going to choose something else like 512 gigabytes like this. Let's add a middleware for this. Handle out like that. And let's add on upload complete as well an empty function like this. and remember to put a comma right here. Perfect. So now we have our core.vs. So let's take a look at what is the next step in the upload thing. So what we have to do is to create an actual API route using our file router. So as you can see, we have to create uploading slash route.vs and we have to paste this inside. So let's copy this inside and let's go inside of upload thing and create a new file route.vs like this. and inside just paste this. There we go. That's it. And it's using our our file router right here. So it means it's going to expose this routes for course image, course attachment, and for chapter video with all of this configuration and this authentication, which we made. Great. So you can see how different this is from all others upload providers currently available. Great. And let's see what we have to do now. So we're going to have to create upload thing components, right? So you can see that they use source utils upload thing.thes. We're going to do it a bit differently, but we're going to use this code. So copy this code as well. And let's go ahead inside of our lib folder. So lib and create a new file upload thing.thes like that and paste all of this inside. And we're going to modify this wavy sign with an add sign. So we're going to fetch the our file router against the app API upload thing core, which is our file router right here. And what's cool is that all of these three components here, so upload button, upload drop zone and uploader are now going to be having the correct types of specific endpoints, which we defined here. Right. So they're going to be strictly typed against our own integration here. Great. And I'm pretty sure that is it. And you have an example of how to use it right here. And as you can see, we have this endpoint prop, which is for us going to autocomplete to either the course image, course attachments or chapter video, right? So that's how cool that is. Perfect. So you can now leave upload thing. If you want to, you can keep it and go inside of the files and watch your files as you upload them. Great. So what I want to do now is I want to create a component called file upload. So let's go ahead and let's create a component inside called file-upload.tsx like that. Let's mark it as use client and let's import upload drop zone from add slash upload thing react. My apologies, not from here, but from our integration. So add slash lib slash upload thing like that. And let's import our file router from at slash API upload thing, a core like this. Great. Now let's write an interface, a file upload props, which is going to take on change, which is an optional URL, which is a type of string, which returns a void and an endpoint, which is a key of, key of type of our file router like that. And now let's export const file upload like that. Let's destructure this props on change and endpoint. And let's map them to file upload props like this. Now inside, let's just return a upload drop zone like that, which is a self-closing tag. Let's give it an endpoint of endpoint. And let me just show you this quickly. So as you can see, the endpoint is either a chapter video, course attachment or course image. So that's what I was talking about when I said strictly typed components against our very own integration of a file upload service. So we're just going to modify that to the prop. And our prop is also protected to only be able to have course image, course attachment or chapter video. So that's why it's matching these props here and no errors are here. Now let's use on client upload complete like that. Let's get the response and let's just do on change response question mark first in array dot URL like that. And below that, let's just add on upload. Error like that on upload error like this. let's get the error which is a type of error like that and let's just use the toast from react toast dot error something went wrong or you can actually use the error dot message like this right so if you want to you can even safely type it by adding back ticks so you ensure that it doesn't break your app accidentally if it's an object or something else. And we can add a question mark here just in case, you know, so it doesn't break our app if we get an error. Perfect, so we have that. I just wanna move this to the top. And there we go. We have our file upload component ready. So what we're gonna do now is we're gonna go back inside of the app folder, inside of dashboard, routes, teacher, courses, course ID inside of components and copy the description form and paste it inside and rename this to image form like this. And now go inside of page.tsx and just below the description form right here. And it seems like we have a small error here. So we're going to fix that. Let's also add an image form like this. And let's quickly head back into our newly copied image form here. And let's change this description form the same way we did in the title to image form. So image form like that. And now in our page, we can finally import this from .slash components image form. So when you save, you're going to have two descriptions here. It seems like I have not started my project. So let's go inside of the terminal here, npm run dev like this. And let's refresh the page. Great. So we are here. So before we start working on the image form, let's resolve the description form error inside. So let's go inside of here. And let's change this initial data to be a type of course from Prisma client like this. So let's move it to the top here. All right. And inside of this default values, let's change this to an object which has a description of either initial data description or an empty string like this. And now we don't have an error in the description form. So we're going to do the same for the image form. Let's go inside of the image form now. And let's start modifying this. So initial data is going to be a course, which is Prisma slash client. So I'm just going to move it to the top right here, like that. Let's change this from image URL. And let's write image is required like this. My apologies, change this. So image URL and change this to image is required. All right. And now inside of this default values, go ahead and write image URL to be initial data question mark image URL or an empty string like this. Great. So now let's go ahead and let's change this to not say course description. Instead, let's go ahead and say course image. And instead of edit description, let's say edit image like this. And let's use this plus circle. Actually, let's leave it to pencil like this. So now we have a course image and we have an edit image button. But I wanna modify it just a bit. So if we never uploaded an image here, I want it to say add an image. And if you already have an image, then I want it to say edit an image. So let's just modify that quickly. So I'm going to go ahead and I'm going to change this back to the previous one. So I'm going to modify this to use the end end option like that. And then I'm going to go ahead and I'm going to write if it's not editing. And end like this. Let's see. And I'm missing this here. Okay. And what I'm going to write now is I'm going to add if it's not editing. And if it doesn't have initial data dot image URL, in that case, go ahead and render a fragment with plus circle from Lucid React with the class name of H-4, W-4 and margin write 2. So make sure you import plus circle from Lucid React like that. and we're going to write add an image, right? And then I'm going to modify this clause to say if it's not editing, but if it has initial data.image URL, in that case, it's going to say edit image. Great, so you can see how this looks now. So since we never added an image, it says add an image. And if we already have an image and we want to modify it, it's going to say edit an image. So just a little bit good user experience here. All right. Now let go inside of this if it not editing And I going to go ahead and remove everything inside And I going to write so if we don have initial data in that case, go ahead and render a div with a class name of flexItems-Center, Justify-Center, H-60, BG-slate-200, then rounded-md, and inside, render image icon from Lucid React. So make sure you import the image icon from Lucid React. Give it a class name of H-10, W-10, and let's go ahead and let's write text-slate-500 like that. All right, and then let's write a column here. and what I'm going to do here is render a div and I'm just going to write current image. But it seems like I did something. I removed my pencil here. Okay, so I did this. You probably didn't do this. Let's see what we're missing. We have pencil circle and we need image icon here, I think. And yeah, I somehow added a bunch of stuff in my imports here. Okay, so pencil plus circle and image icon. All right. And as you can see now, since I never added an image here, It looks like this, right? It's empty. Perfect. So now let's modify this and let's give this a class name of relative aspect-video and margin top 2. And inside, we're going to render an image from next slash image. So let's go ahead and give it an alt of upload, fill option and class name of object-cover and rounded-md like that. And source is going to be initial data question mark. Actually, it's certainly going to have image URL because it's inside of this ternary right here. Great. So what we have to do now is we have to modify this form right here, but it's actually going to be much easier. So we can remove this entire form element here. We're not going to need it. So the moment we upload an image, we're going to submit it, right? So open a div inside and add a file upload from as slash components file upload, which we just created, give it an endpoint, of course, image like this, and go ahead and call on change, get the URL like this. If there is a URL, in that case, on submit and passing image URL to be the URL, just like that. Great. And below this file upload, go ahead and add a div here with a class name of text dash extra small text dash muted dash foreground and empty dash four and write 16 colon 9 aspect ratio recommended like this. Perfect. So now if I go ahead and click on add image. Oh yeah, one important thing I forgot to do was add the styles for upload thing. So one thing that we have to do now is we first have to go inside of our Tailwind config. So let's go inside of the Tailwind config like this. Let's go ahead and let's use const with UT short for upto thing is equal to require upload thing slash tailwind or TV for short with UT and wrap the entire thing in parentheses. And just remember to end the parentheses all the way to the bottom here like that. All right. So now we have that. And now what you have to do is you have to go inside of your globals. So app globals.css where you have the tailwind direct types. Go all the way to the bottom and just add at import. Add this curly little line here at upload thing slash react slash styles.css like this. And there we go. as you can see now, I have the styles for my upload thing. Perfect. So let's see if we can already do this. So I'm going to click here and I'm going to add some image here from Unsplash. I'm going to click upload file and let's see if that's immediately going to update my route. And there we go. It actually works, right? So it successfully rendered the image. One last thing we have to do is add the host name inside of our next config, right? So whatever is written for you here, I'm pretty sure it's going to be the same as mine, right? So I'm going to zoom in so you can see. So just find the host name and whatever is written here, you have to add it inside of the next config. So I'm going to go ahead and close everything. I'm going to go inside of next.config.js. Inside of this object, I'm going to write images, domains, an array, and paste in the host like that. And after you change nextconfig.js, you have to run your app again. So just npm run dev, shut down your app and restart it. And let's go ahead and refresh this entire thing. And if I am correct, we are now going to see our image here. There we go. So a nice little image from unsplash right here. Perfect. So what I want to do next is I quickly want to revisit our image form. so app dashboard routes teacher courses course id components uh where is it image form right here and you can probably know this there's a lot of stuff that we're actually not using here so let's remove all of those first of all we don't need any of the form elements we don't need cn utils we don't need the text area we can move text image to the top here because it's not inside of this type of imports. You can actually remove the form schema. We're not going to need it. Actually, my apologies, we are going to need the form schema, but we are not going to need the form, right? Because we're not using this is submitting anywhere. So we can remove this. And now, as you can see, we don't even need the form. So you can remove this as well. So we only need the on submit function in this simplified version, right? Because it's auto submitting. So you can remove use form and Zod resolver. And I think that should be it. There we go. So now our form is clear. And as you can see now, it says edit image. So if I click again, and go ahead and upload something else, for example, you can see how it works. So the moment it uploads an image is immediately going to make a patch request, and it's going to refresh the entire server component. And there we go, we have a new image here. And if you try in a completely new course, you can see that it's not going to say edit an image. Instead, it's going to say course image, add an image here. So from now on, I'm going to keep working here just because I changed it now. So let me go ahead and add a new image here like this. Just one more time to confirm. And there we go. So it was that easy to create our own upload service using UploadThink. Great, great job. So now let's go ahead and let's create a similar box like we did for title, description, and image, but this time let's do it for the category selection. So in order to do that, we're going to have to add a new component from ShatCN called ComboBox. But for that, I'm going to advise you to visit the documentation yourself, because we are going to have to copy this from this actual documentation. So go in the components tab right here, and let's go ahead and let's find the ComboBox. So this is what it's going to look like. As you can see, we're going to able to search for a specific one and then we're going to be able to select it. So we're going to go ahead and have to install a popover and a command component first. So let's go ahead inside of our terminal here. So I'm going to go right here in the terminal, I will shut down my app and I'm going to write npx chat cn dash ui at latest add popover like this. and after you press this, just make sure you confirm the installation like this and after the popover has been installed, we're going to have to go ahead and install the command. So do the same thing and let's go ahead and write npx chatcn-ui at latest add command like this and do the same thing. So just confirm the installation and after that is done, you're going to be able to do npm run dev inside of the terminal again. Great. So make sure you have those two and npm run dev. And now let's go back inside of the documentation here. And as you can see, now we have the usage, right? So combo box doesn't come as a separate component. Instead, it's a combination of popover and the command. So we have to copy this entire thing here. So make sure you're in the combo box here and just copy this entire usage right here. In case this has changed from when you're watching this, you can always visit the source code for my combo box and just copy it in your project. So make sure you copy this code here and let's go inside of components. Let's go inside of UI this time and let's go ahead and create a new file called combo box.tsx like this and you can paste the entire thing inside. And just make sure you don't have any errors so you confirm that you have all the necessary components like button, command, and popover, right? And what I'm going to do now is I'm going to modify this. So as you can see, this is hard coded to this frameworks example, but we don't want that. We want it to be dynamic so that we can pass it any options we want. So first thing I'm going to do is I'm going to remove this array frameworks, right? So I'm going to remove this entire thing like that. And now of course, we're going to have some errors, but don't worry, we're going to resolve them. One more thing I want to do is I want to rename this, it's not going to become the box demo except instead, I'm going to write export const combo box like this and just turn it into a nice little arrow function here. Now I'm going to change this to add some interface to it. So interface combo box props like this. It's going to have options, which are going to be an object and an array. And inside of the object, we're going to write a label, which is a string. And we're going to have a value, which is a string like this. Then below that, we're going to have the default value, which is a selected option, which is going to be a optional string. And we're going to have the on change function where we are going to pass the current value, which we selected from this object, right? So we don need to work with labels We only work with unique values And that just going to be a void So the user will decide what it going to do with that value Great And now we can extract those props here So options, value, and on change like this. And let's map them to combo box props like that. And now inside, you're not going to need this value and set value. So you can remove it. But just leave the open and set open. Now let's go ahead and let's find where we have this error, cannot find frameworks, right? Because we're no longer working with frameworks. Instead, we're going to be working with this prop called options. So replace this with options like that. And then it will be nice to also change this. So we're not finding an individual framework. Instead, we're finding an individual option. And then we look for option dot value to match the value like this. And let's change this from select framework to select option like this. So it's more standardized. And we can also change this text where it says no framework found to be no option found. So if user searches for a specific option, we're going to display no option find. And let's also change this placeholder in the command input to search for an option. And we did the same thing here. So we map our options like this, and then we work with an individual option. And then the key is option dot value. And then we don't no longer have the set value. Instead, we're going to do something different. We're going to call our on change prop, which we passed in here, and we're going to write option dot value is equal to value. So if user clicked on the already selected option, in that case, we're going to clear the input. Otherwise, we're going to select that value that the person clicked on. So option dot value. And we actually don't need to work with the current value at all like this. Great. And let's go ahead and let's also change this to option dot value. And let's change this to option dot label like that. And that's it. Just like that, we created a nice little combo box component for us. Perfect. So now what we have to do is we have to create the category form. So let's go back and save this file. And let's go inside of the app folder, dashboard, routes, teacher, courses, course ID page.vsx right here. So we're going to need to copy one of these forms here. And we're going to have to modify just a bit. But before we do that, first, let's go ahead and let's actually fetch the categories. So let's go below this course here. And let's write const categories to be await db.category.findmany, like that, order by name ascending like this, right? So now we have the categories. And one thing that we have to do is we actually have to seed these categories, right? Because there are no way they can be created from this form right here. So we're going to create a script that is going to put it in the database for us. So let's go ahead and do that. So one thing I'm going to do first is I'm going to go inside of my terminal, and I'm just going to open the NPX Prisma Studio, right? So I can keep track of my models and all the other stuff. So let's go inside of the Prisma Studio here. And now if you try and search for, so I'm going to click on this plus button here, you can see that I have nothing inside of my category, which means that user will not be able to select any category here. So just make sure you have your categories open and that they are empty. And now we're going to go ahead and we're going to create a script to seed our categories. So let's go ahead and close everything. And let's go ahead and create a new folder called scripts like this. So it's outside of the app folder all the way to the root here. And inside, create a new file called seed.ds like that. Inside, let's go ahead and let's write const prisma client is equal to require at prisma slash clients like that const db is equal to new prisma client like that and async function main is going to have a try and catch block and let's get the error first we're going to do console log and we're just going to write error seeding the database categories like that. And let's also log the actual error. And in the finally block, let's do await. And let's actually rename this from DB to database, right? And let's here write await database dot. And now we have to add this dollar sign disconnect. Like this. And now in the try block, let's go ahead and write await database dot category. So don't worry that autocomplete doesn't work here, right? This is outside of our runtime. So create many. And let's go ahead and write data, which is an array. And let's go ahead and write a name to be computer science, like that. And let's copy and paste this sometimes. So the second one is going to be music. And then the one below that is going to be fitness. Below that we're going to have photography. And we're then going to have accounting like that. Then we're going to have engineering. And let's add one more filming like that. And after this, let's go ahead and let's console log success like this. So we know everything is correct. Great. And you can already try this out. So let's go inside of the terminal now. And I'm going to shut down my app. And I'm going to write node scripts slash seed dot DS, right? So that is the location of our file right here. As you can see, it's in scripts folder, seed dot DS. So that's what we write. Node scripts dot cd dot yes uh and well i think i just forgot to run this actual uh function yes that's why nothing happened so we defined this main function but we forgot to run it so at the bottom just run main like this and now let's try the script again so i'm gonna use node script cds let's see there we go it says success which means our categories were successfully uh filled inside if you got an error. So you can visit my source code and you can copy this file just to confirm for yourself that you wrote everything exactly as it should be. But if you're still getting errors, make sure that you have this in your schema Prisma, right? So you need to have the category model and make sure you did the NPX Prisma push and DB push. Sorry, NPX Prisma generate and NPX Prisma DB push. And now if I refresh my categories here in the Prisma studio, there we go. Fitness, engineering, accounting, beauty science, music, photography, and filming. So exactly what we needed. And now we can go back inside of our app folder, dashboard, routes, teacher, courses, course ID, page. And these categories will now make sense because you can console log the categories here. So this is going to be logged on the terminal, right? Because this is a server component. So I'm going to go ahead and run npm run dev now. I'm going to go inside of my app and I'm going to refresh this page. And if we watch closely here, we should see an array of our categories. There we go. You can see how we have engineering, filming, fitness, music, photography. Great. So our categories are loading in this server component, which means that we are ready to create the category form. So let's go ahead and let's copy the description form from the components right here. Let's paste it and let's rename it category-form like that. Let's go inside category form and let's do the little trick to just rename all instances of description form by using command D or control D. And then we selected all three of them. And let's just change it to category form like that. Great. Now inside, let's go ahead and let's add another option to this interface called options like that. And that's going to be an array of options. It's going to have a label, which is a string, and a value, which is a string. So it's going to match our combo box options. Great. Now let's change the form schema to use the category ID. So just write category ID. It's going to be z.string.min1 like this. No need to change anything further. Now let's also extract the options from here like that. And let's quickly go ahead and change this use form here. So in default values, we're going to work with category ID. And then we're going to check for initial data dot category ID like that. And now let's go ahead and let's see some errors that we have here at the bottom. So let's manually change this to category ID like this. There we go. And we can leave that text area for now. What I want to do is just quickly, I want to change this from course description to course category like this and edit category. There we go. Now let's go back to our page.tsx and let's go ahead and let's copy the description form below the image form, rename it to category form, import the newly created category form. So make sure you have this right here. And we are now missing the options. So you might be thinking, well, we can just pass in the categories, right? Well, not. So look at these categories and look at the object that is being received. It has an ID and a name because that suits our database. But in here, we work with value and a label. So just write categories.map. And let's go ahead and get individual category like that. And go ahead and immediately return an object where label is going to be category.name. But value is going to be category.id like this. And there we go. No more errors. Perfect. And now let's just take a look at how this looks like. Okay, there we go. So we have the course category here at the bottom. And if I click, we are, of course, working with text area. So we're going to modify this now to actually show the category options and selection. So let's go ahead. And first thing I want to do is I want to go inside of this is editing option right here. So we're not going to work with description. So we can change the initial data description here to category ID. Meaning if there is no category ID, we're going to display this muted text and italic. And we're going to write no category. Right. Otherwise, we're going to select selected option, but we don't have the selected option. So let's just quickly go ahead and let's write something outside of this return function here. So let's write const selected option. To be options that find get individual option and let just write option that value to be identical to initial data dot category ID like this right So we look throughout all of our options and we look if initial data has selected a category ID. And that's how we know, okay, this course already has a selected option. And then we can use this constant selected option back inside of here where we still have initial data dot descriptions. Instead, we're gonna write selected option, question mark dot label, right? So the question mark is important, right? Because there is a chance that we don't have a category like we don't. So if you take a look at our little box here, it says no category, right? Great. And now what I want to do next is I want to go into this form field right here and we're going to modify it. So we're not going to use the text area anymore. Instead, we're going to use the combo box. So let's add the combo box from at components UI combo box, which we actually created. And let's go ahead and let's pass in the options like that. And let's pass in all the other field props like this. So that's going to include the on change option. So now if we try, and if we are everything is correct, there we go, I can select for computer science for filming for accounting. One thing I don't like is that this is not using its full width. So let's go ahead back inside of our combo box component, which is located in components UI combo box. And let's just change this. Let's remove this W200, right? We don't need that. And let's see if that will resolve this. It seems to not resolve it, but let's add instead W-full. So I'm adding that in the button prop class name and in the popover content, W-full, right? And let's go ahead and let's see. There we go. This looks better now. Great. And if I select and save, if I'm correct, this should now say filming and it does. And if I refresh, it says filming. And if I go into my Prisma Studio, filming options should now have a relation with a course. And it does. Look at this. It says one course in its right head. So we have a proper relation between the course and the film. Great. Perfect. So we just finished the category model. Sorry, the course category option. And now we can slowly go ahead and start developing the second section right here, which is going to be used to set the price, to add the chapters, and to add some attachments to the course. Great job so far. So let's go ahead and let's create this section to the side here where we're going to sell the course, add attachments and add chapters. So I'm going to go ahead and I'm going to close all of these fields right here. And let's go inside of our course ID inside of the teacher folder page where we rendered the category form and all the similar things. So go outside of this div, which is wrapping the category form and the image form and the title form and go ahead and open a new div. And let's go ahead and let's give it a class name of space dash y dash six like this. And inside, let's go ahead and let's write a new div, which is going to have another div inside. And this one is going to have a class name of flex items dash center and gap dash x dash two like this. Let's go and add an icon badge, which is going to have an icon of list checks like this. And make sure you import that from Lucid React like that. And then if you look to the side, you're going to have this icon badge here. And if you collapse, it's going to be below the customize your course. and in here let's add an h2 element saying course chapters like that and let's give it a class name of text dash xl like this so there we go now we have the course chapters which are going to be in this area right here and we're not actually going to write a form for this immediately instead i'm just going to add a div saying to do chapters like this outside of this div which is wrapping the icon badge and the h2 and now you can see it says to do chapters so we're gonna later turn this into a similar box like this but first I want to do the price and the attachments because they're way easier to actually do so let's go ahead outside of this div right here and let's create a new one so outside of this div which is wrapping the div for the badge and title and the div for the to-do So outside of that and inside, we're going to go ahead and we're just going to add the exact same thing, right? So we're going to add a class name of flex items-center and gap-x-2 like this. We're going to add the icon badge, which is going to have an icon of circle dollar sign from Lucid React. So just make sure you import the circle dollar sign like that. And an h2 element, which is going to say sell your course like that. An h2 element is going to have a class name of text-xl like this. Great. So now if you go ahead and there we go. So we have the course chapters, which we still have to do. And then we have the sell your course option right here. Perfect. So now we're actually ready to create our price form. So let's go ahead and let's copy the description form. Let's paste it and let's rename it to price form like that. Let's go inside of the price form component and let's just quickly change the description form items like this. So this three description form, description form and description form to price form like this. Great. And now we can go back inside this page here and outside of this, they're wrapping the icon badge and the H2 element. You can just add the price form like this. And let's go ahead and let's give it initial data, which is the current course. And let's give it the course ID, which is course.id like that. And now there we go. We have sell your course and we have course description. Of course, we're going to change that now to actually match our price form. So let's go ahead. And first things first, inside of the price form, let's just change the titles here. So it's going to be a course price like this. And we're going to have edit price in here like that. So let's see how that looks like. There we go. Course price and edit price like this. All right. Now what I want to do is I want to change my form schema. So we're no longer working with description. Instead, we're working with price. So let's go ahead and write price z.curse.number like this. Great. And now let's go ahead and change this as well. So this is going to be initial data.price. And let's go ahead and mark this as undefined like this. There we go. So either the initial data price or undefined manually like that. Great. Now let's go ahead and let's go inside of this form field and let's change this name to price like that. And let's remove this text area and add an input from add slash components UI input. So make sure you import this and you can remove the text area from here. All right. And now let's go ahead and change this placeholder to be set a price for your horse like that. let's go ahead and write a type to be number and a step to be 0.01 like this great and now that we have that one more thing that we have to change is this is editing stats right here so let's go ahead and change if we are not looking at description but instead is price right and it's the same thing with this initial data.price or no price. Like that. Great. So let's take a look at how that looks like. There we go. No price. And if I set the price like 123 and click save, it says 123. One thing I want to do is I want to format this price with a dollar sign, right? So the way we're going to do that is we're going to create a new lib code format. So let's go inside of our lib folder and create a new file format.ts like that. And let's export const format price to be a price which takes in a number. And it returns a new intl.number format like that with an option of n-us and an object of style currency like that. The Currency is going to be US dollars and a format price like this. And just don't misspell currency like this. There we go. And now let's go back inside of the price form and let's wrap this initial data dot price using the format price, which we just created. So imported from atlib slash format and passing initial data dot price like this. but then it's actually it's going to have to be a bit different so let's go ahead and let's remove this all together and let's write it like this so initial data dot price and then let's go ahead and let's write the question mark in that case format price which we have imported so initial data dot price otherwise it's going to be no price like this there we go so make sure you have format price imported from atlib slash format like that and there we go now it says one two three like this perfect and if i try uh in a new course for example it should say no price here great so only after i add a price like 99 99 dollars and click on save there we go it has a price here so i'm just gonna go ahead and test all of my existing stuff so So this is a description like that. Let's save this. Let's add an image as well. Something like that. And let's also go ahead and let's select a category once this is finished. Okay, it's finished. And the category, well, it's actually music, isn't it? And click save. So all of this seems to be working.\nNext thing we're going to do is we're going to create the attachments form. And then we're finally ready to create the chapters. And we are soon nearing completing our course setup and actually publishing it and then rendering it on the page. Great job. So now let's go ahead and let's create our attachment form. So I'm going to go ahead back inside of my page.asx. That is rendering the description form, the price form, and all the other elements here. So let's go ahead and let's go outside of this div, which is rendering this icon badge and sell your course and the price form and create a new div. And inside, you can just copy this, which wraps the icon badge and H2 element and paste it inside like that. So I'm going to indent it and this is what it looks like. So now if you expand, you should have a duplicate sell your course with an icon badge inside. So I'm going to go back inside And I'm going to change this to an icon file from Lucid React. So make sure you imported the file from Lucid React like that. And now let's go ahead and let's change this to resources and attachments like this. And now let's take a look at how that looks. And there we go. So we have course chapters, which we are yet to do. We have sell your course. And now we have resources and attachments. So I'm going to go ahead and I'm going to copy the image form because it's the most similar one to what our attachment form is going to look like. So go outside of this div, which we just copied and replaced a bit and add an image form. And now you should have a coarse image here, but of course we're going to modify it a bit. So now let's go ahead and let's go inside of our underscore components folder and let's copy the image form and paste it inside. And I'm going to go ahead and I'm going to write attachment dash form like this. So let's go ahead inside, make sure you're inside of the attachment form. And let's quickly replace all the instances of image form into attachment form. So there's only three of them, the interface, the export const, and this one props right here. And you can save this. And let's go ahead and let's change this to course attachments like that. And let's go ahead and remove this option at the bottom like that. So it's always going to say add a file like this. And then you can actually remove this end end. So it's just going to be is not editing like this. There we go. So that's what we want. Let's go back inside of the page.vsx and let's import the attachment form from dot slash underscore core components attachment form. And now you should see course attachments and add a file here. Great. So let's go back inside of the attachment form right here. And let's just quickly modify this right here. So it's not going to work exactly like this. So first thing we're going to have to change is go inside of the interface, right? So the initial data is going to be a course, but it's going to be extended, right? Because we're going to include the attachments inside. So write attachments and attachment from Prisma client like this. Great. And now what we have to do is we have to go back inside of page.tsx right here. As you can see now, there is an error here in the attachment form because the course that we have, let's go ahead and find it. So this course right here, which we have does not include attachments. So let's go and write include attachments like that. And let's write order by created at to be descending like this. Great. So now we have attachments. And as you can see, no more errors in the attachment form right here. Perfect. So what I want to do now is I want to go back inside of the attachment form. And let's change this form schema to not use image URL, but just a URL like this. And it's going to be z.string and a minimum value of one like that. Great. Now let's go ahead and let's go all the way to this is editing, right? Where we have the initial data and stuff. And what I'm going to do is I'm going to change this entire thing inside. So let's remove it like that. So if we are not editing. Let's go ahead and let's open a fragment like this. And let's write initial data dot attachments dot length is equal to zero. In that case, go ahead and render a paragraph, no attachments yet, like this. And let's go ahead and give this a class name. Text dash small, empty dash two, text slash slate dash 500 and italic like this. And let's just quickly see what error do we have here. Looks like we have an extra parenthesis right here. All right, so let's save this. And now it should say that we have no attachments yet, just like that. Perfect. And now let's go ahead and let's go inside of this is editing right here. So we're going to change this endpoint to be course attachment like that. And we're going to change this URL to just send a URL instead. And instead of this aspect ratio, we are going to write add anything your students might need to complete the course like that. Perfect. And now that we have that, we're also going to go ahead inside of this on submit function. And we're actually going to modify it a little bit. So it's not going to be patch. It's going to be a post method. And it's going to go to slash API slash courses, course ID, but slash attachments. So we're going to have a separate route for creating attachments like this. And we're also going to create a new route, which is going to be used to delete attachments. Great. So now if you go ahead and try, and add an attachment. So let's go ahead and I'm just gonna, you can add an image, video, audio, PDF, right? Everything we wrote in core.ds. And what I'm expecting to happen now is an error. There we go. So something went wrong, right? So it shouldn't work right now. That's because we don't have the route to create an attachment. So let's go ahead and do that. So let's go inside of our app folder. So I'm just gonna close everything. Let's go inside of the app folder. let's go inside of the API courses course ID and create a new folder called attachments like that. And inside, oh, it looks like I misspelled it. So attachments like this and go inside and create a route dot TS like this. And let's go ahead and let's write export asynchronous function post, which has a request of type of request and params, which hold our course ID. So they are an object with course ID, which is a type of string param. Great. And let me just remove this. All right. And like this, let's open a try and catch block. And in the error here, I'm going to console log course underscore ID underscore attachments like that and an error like this. All right. So now that we have that, let's return new next response from next slash server saying internal error like that. And let's pass in a status of 500 like this. Great. Now inside, let's go ahead and let's extract a user ID from the out method from clerk next JS like that. And let's go ahead and let's extract the URL from await request dot JSON like this. So if there is no user ID, in that case, we can return new next response, unauthorized with a status of 401 like this. Great. And now we're going to have to check whether the person who is trying to create this attachment for this course is actually the owner of the course. So let's write const course owner like that is await DB from add slash lib DB. So I'm just going to separate this imports. Make sure you have that DB dot course. Don't find a unique like that where ID is brands dot course ID and user ID is user ID like that. Great. And if there is no course owner, in that case, it means that we don't have access to editing this course, right? So we write unauthorized again with a status of 401 like this. great. And now let's go ahead and let's write const attachment is equal to await db.attachment.create beta, which has a URL, name is going to be url.split with a slash and pop like that. And course ID is going to be params.course ID like this. Great. Now we have our attachment and all left is to make a return function. So return next response dot JSON attachment like that. Perfect. And now let's go ahead and let's try this again. So I'm going to go ahead and upload a random image inside. And let's see if we're going to get an error or if this is going to work. So it seems it's working. And there we go. So now we don't have the text, which is saying no attachments, right? But we don't have anything as well. So we have to add a display, which is going to map over all the attachments we have for this course. And they're going to be listed right here. So let's just do a double check using Prisma Studio. So I'm going to go ahead and let's see, okay, I have Prisma Studio running. If you don't, you can run this command npx Prisma Studio. And let's go inside. And now let's go and find the attachments. So I'm going to refresh this. And there we go. It seems like I have one attachment right here with upload thing URL and it's linked to a specific course. So just confirm that you have that once you upload a file. Perfect. All right. And now let's go ahead and let's go back inside of our attachment form. So inside of the dashboard routes teacher courses course ID components attachment form right here And well what we have to do now is we have to create an iteration over all of our attachments So let's find where we're going to do this. So it's right here. So if we are not editing like that, great. So let's go ahead and let's go just below this paragraph here. and let's go ahead and write if initial data.attachments.length is larger than 0. In that case, open a div with a class name of space-y-2 like this and inside let's run initial data.attachments.map. let's get individual attachment like this and return a div which is going to have a key which is attachment dot id like that we're going to have a class name which is flex items dash center p-3w-full bg-sky-100 border-sky-200 border- so just border my apologies text-sky-700 and rounded dash md like that and inside we're going to have an icon for a file so make sure you import that from lucid react right here all right so we have that and let's go ahead and let's give this file a class name of h-4 w-4 margin right to and flex that shrink shrink dash zero like that and below the file we're going to have a paragraph which is going to render attachment dot name like that. So let's see how that looks like now. There we go. So you can see we have a little name here and a file. So let's go ahead and let's go and give this paragraph a class name of text-extra small and line-clamp-1. So we truncate the line if it's too long. There we go. Now this looks much better. What we have to do now is we have to add a little delete button here so we can remove this attachment from the course. So let's go ahead. And first thing we have to do is we have to create a new state here, const deleting ID and set deleting ID to be use state from react. And by default, it's going to be null. So we have to modify this state type to be either a string or a null like this. Great. And now that we have that, let's go ahead and let's go below this paragraph, which has the attachment name like that. And let's write if deleting ID is equal to attachment.id. In that case, we're going to display a little spinner here. So add a div and import loader to from lucid react so make sure you add the loader 2 from lucid react like that and just give it a class name of h-4 w-4 and animate dash spin like this and you can copy and paste this below like this and just change this to if it's not equal if the current deleting id does not match the current attachment id in this iteration array that means that this is going to have to be a button where we're going to show the X icon from Lucid React. So make sure you have the X imported from Lucid like this. And let's go ahead and remove the animate spin from it. So we're not going to need the animate spin like that. And let's go ahead and give this button a class name. ML-Auto, Power, Opacity-75 and Transition like this. So let's see what that looks like. now. And there we go. You can see how I now have a little X button here, which is going to delete our attachment. So now let's create a function, which is going to call the on delete for this specific attachment. So let's go below the on submit right here. And let's write const on delete, which is going to take is going to be in a synchronous function, which takes an ID, which is a type of string. It's a try and catch block. So let's go ahead. We can remove this parenthesis. And in here, we can just toast error, something went wrong, like this. And in the finally block, just add set is deleting, whoops, set deleting ID to be back to null like that, which means in the try block, the first thing we do is set deleting ID to be the ID passed from this params right here. And then we're going to do await axios.delete, open backticks, slash API, slash courses slash course ID slash attachments slash the individual attachment ID and after that we're gonna go toast our success attachment deleted like that and router dot refresh like that and then let's use this on delete inside of our button right here so find the button and go ahead and give it an on click, which is going to be an arrow function, which is going to call the on delete and pass in the attachment dot ID. Since we are inside of this dot map, we have access to the attachment inside of this scope. Perfect. So if I try now, I should get an error. There we go. Something went wrong, but you can see for a split second, it displays a loading animation there. So that's exactly what we want. So now what we have to do is we have to create the attachment ID route and delete route. So let's go inside of our API folder. Inside of courses, course ID attachments and go ahead and create a new folder in square brackets, attachment ID like that. And inside create a new file route.ts like this. Great. And inside let's export asynchronous function delete, which is going to have a request, which is a type of request. And it's going to have rams, which of course is an object which has our course ID in a string, but it also has the attachment ID, which is also a type of string like that. Perfect. So let's go ahead and let's open a try and catch block. Let's console log attachment ID. So we know this is where the error is coming from. Let's log the error and let's return new next response from next server internal error with a status of 500. So like we do usually. Now let's extract our user ID from the out using clerk next JS. And while we are here, let's immediately import the database from at libDB like that. And let's immediately check if there is no user ID in that case, return new next response unauthorized like that with a status of 401 beautiful and now below that while we have to do the same thing as we did with creating an attachment and that is to check if we are the owner of this course if we created it right we don't want someone to abuse our api so const course owner is going to be await db.course.findunique, where id is rams.courseid, and userid is equal to userid, like that. If there is no course owner, in that case, we can just return new next response unauthorized with a status of 500, read whoops 401 like that great and now let's finally write const attachment is equal to await db.attachment.delete where course id is equal to params course id and the id of the attachment we want to delete is params.attachment id and then we can just go ahead and write return new whoops next response.json attachment, which we deleted. Great. So let's just double check that the attachment ID, which we are accessing in the params and defining right here is not misspelled inside of this folder. And let's also go back inside of the attachment form. And let's confirm that our on delete function is properly targeting the attachments. So let's go slash API slash courses, the individual course ID slash attachments slash ID. I think this should work just fine. So let's go ahead and try this. So if I click X right here, it's loading, it's loading, and attachment has been deleted. And this says no attachments yet. Great, great job. So you completely finished the course attachments form. What we have to do now is finally get onto creating our chapters. Great job. So let's go ahead and let's create this little form for creating our chapters. In order to do that, the first thing I want to do is create, I mean, finish our Prisma schema. So we started something in the Prisma schema right here, where we are able to have categories, attachments, and a course, right? But now we need to create a model for our chapters. And while we are here, I just want to add everything that we're going to need for the rest of this tutorial. So let's go ahead and let's start with the model chapter here. So model chapter is going to have an ID, which is a type of string, at ID and at default UUID like this. Then we're going to have a title, which is a string as well. A description, which is an optional string and DB.text like that. video URL, which is an optional string and db.text as well. We're going to have a position, which is an integer like that, is published, which is going to be a Boolean and is going to have a default value of false like that. And we're also going to have an option is free, which is also a Boolean with a default value of false like that. Then we're going to have the Max data, which is just going to be a relation to the model Max data. So we're going to use this to process the video, but don't worry about this error. Now we're going to create this model in a second. So now let create a back relation with the course So each chapter is going to have its course So course ID is a type of string like that And a relation course course at relation fields like this course ID references ID And on delete, we're going to have cascade like this all in one line. Great. So now that we have that, let's go ahead and let's also create a relation with the user progress, right? So users are going to have specific progress on each chapter. So let's write user progress to be user progress like that. And it's going to be unarray, right? So ignore these errors for now. And let's just not misspell the progress here. Progress like that. And let's add the created at, which is a date, time, and the default value of now. and updated at, which is also a date time with a decorator updated at like this. Great. Now let's go ahead and let's create this max data model, which is throwing an error here. So I'm going to write model max data. It's gonna have an ID, which is a type of string at ID and default value of UUID like that. Chapter ID is gonna be a string and it's gonna be unique like this. It's going to have an asset ID, which is a string, and it's going to have a playback ID, which is also an optional string. All right. And now let's create a proper relation with the chapter. So I'm just going to move it here, and then I'm going to write a chapter, chapter at relation, fields, chapter ID, references, the ID, and on delete, cascade like this. And now, as you can see, we no longer have an error with the max data in our newly created model chapter. Great. And now let's go ahead and let's create our user progress model, which is throwing an error right here. So let's go here and let's write model user progress like that. It's going to have an ID with the default value of UUID like that. it's going to have a user ID. So it's going to be limited to one user and chapter ID, which is a string and the back relation to the chapter. So chapter, chapter at relation. So at relation fields, chapter ID, references ID, and we're going to have on delete cascade like that. Great. And while we are here, let's also add some other options here, like is completed, which is a Boolean and the default value of false like that. And now let's add created at, which is a date time at default now and updated at, which is also a date time with a decorator updated at like this. And now let's just fix this warning real quick by adding an index to the chapter ID like that. And let's ensure that there needs to be a unique combination of a user and a chapter ID. So let's just write at that unique like this, user ID, chapter ID like that. Perfect. And now let's see if we have any errors here. Right, so we just resolved the user progress error and we resolved the max data error. But now we have an error because we are creating a relation with the course for the chapter. But inside of our course model, the chapter is nowhere to be found. So let's go ahead and below this category, let's write chapters. So multiple chapter, which we just created and make sure to put an array at the end like that. So we can actually keep chapters and attachments together like this. And now in here, we no longer have an error for this line course, but we have to create an index to fix this warning. So let's go ahead and add, add, add index course ID like this. Great. and while we are here we resolved all the errors here but I just want to add two more models which we're going to use to purchase our courses right so we resolved all of those and now let's go all the way to the bottom and let's add a model purchase which is going to have an id which is type of string default value of uuid like that user id which is also a string course id which is a string and a back relation to the course using the course ID referencing to the ID and you guessed it on delete cascade like that. Great and while we're here let's also add the created at field which is a date time and the default value of now and updated at which is also a date time with the decorator updated at like this. And let's go ahead and add an index here because we already know we're gonna need it, course ID like that. And now we have to create a back relation to the course. So the same way we added the chapters, we now have to go back to the course model and below attachments add purchases like this, purchase and like this. There you go. So just make sure this is purchases, all right? All right. And now let's just write our last model, which is going to be a Stripe customer. So model Stripe customer like this is going to have an ID, which is a string default UUID like that is going to have a user ID, which is a string and unique. So only one Stripe customer per logged in user. And we're also going to store the Stripe customer ID to be a string and unique like that. And let's add created at to be a date time at default now. And let's add updated at to be a date time with the decorator updated at like that. There we go. So we finished our entire Prisma schema here. And now what we have to do is go inside of our terminal here. I'm going to shut down my Prisma Studio from here. I'm going to shut down my application. And I'm going to write npx Prisma generate. So we add all of those new models inside of our dbutil. And now npxprisma db push like this. And there's a chance that we might have to drop our database because we just added a lot of stuff and we have some existing items. And it might be that we don't have to do that at all. Great. So we finished that. And now we can safely npm run dev our application again. And now we are finally ready to create this chapter's model, sorry, this chapter's form right here. So now that we have our Prisma schema completed, we are ready to create this little to-do, which we've written right here. So let's head back inside of our page.tsx, which holds all of these sections right here. So inside of the dashboard routes, teacher courses, course ID, page.tsx right here. Let's go ahead and find this to do chapters. And what I want to do is I want to copy the description form like this. I'm going to copy the description form and I'm going to replace this div with chapters like this. So just paste it here. And now you see that we're going to have the course description on their course chapters after you save this file. So let's go ahead. And let's go inside of underscore components here. And let's copy the description form and paste it inside. And let's rename it to chapters-form like this. And let's quickly do the thing we always do by renaming description form. So one, two, three instances to chapters form like that. And then let's go back inside of page.tsx, find this description form, which is in the course chapters section and rename it to chapters form from dot slash underscore components chapters dash form like this. So make sure you have this import. Great. So now that we have that, one more thing that we have to do is we have to go back inside of our course model. So here where we fetch the unique course, we are including the attachments. So what we're going to have to do now is also include the chapters like this. So let's include the chapters and let's order by position, right? So we want to order them in the position which the user has defined. And while we're here, I'm also noticing that we're fetching this only by ID, but it would be good to also use the user ID. So let's go ahead and add user ID as well. So only the creator of the course can fetch this course. Even though our API is protected, let's not even use this at all. So it redirects if user is not the owner of this course. And while we are here, we can actually update our required fields. Because if you look now, it says five out of five, because I have a title, I have a description, image and course category, and I have the price, right? But we also gonna have to create a required field that we have at least one chapter which is also published. So let's go ahead and let's add course.chapters.sum, get the individual chapter and extract chapter.isPublished like this. So we are looking at at least one chapter that is published. And now if you save, you can see that I have five out of six completed fields. So I still should not be able to publish this course. Great. So now we can finally go inside of our newly created chapters form. So make sure you are inside of the chapters form right here. And let's go ahead and let's change our form schema to just have a simple title like this, z.string and a minimum value of one, right? So our form schema inside of this form will just be used to create a chapter using a title. But then we're going to have to add a video and a description to the chapter in a separate view, which will enable us to publish that chapter. Until those fields are not filled, we're not going to allow the user to publish the chapter, same like we are doing in the course setup. So we're going to have some required fields All right And now what I want to do is I want to replace this use states right here So let go ahead and let add const is creating set is creating use state false like this And let's replace this to not be is editing and set is editing. Let's use is updating and set is updating. So the reason I switched to is creating and set is creating is because I just want to be correct with our naming. So in this form, we're only going to be able to create a new chapter or we're going to be able to reorder the chapters. Right. So there is no editing of the chapters. The editing will be done in a separate view, which we are going to create. So now let's change this from toggle edit to toggle creating like that. And let's just change this. Let me just open this in a separate arrow function like this so you can see it clearly. So let's just change this to use set is creating and just do the reverse of the current value like that. And now let's go ahead inside of this form right here and let's change the default values to always be a title and an empty string, right? So no need for initial values in the form here because as I said, it's only going to be used to create a new one. All right. And now let's go inside of this onSubmit function here. So we're going to modify this a bit. Instead of Axios.patch, what it's going to do is Axios.post slash API slash courses course ID. But similar to what we did with attachments, this is going to be slash chapters like this. And then we're going to go ahead and we're going to say chapter created like that. And we're going to toggle creating like this. Great. And now let's go ahead and let's start working with this right here. So this button is going to toggle creating like that. This is not going to say course description. Instead, it's going to say course chapters. and instead of using is editing, we're going to work with is creating like that. Instead of pencil, it's going to be plus circle from lucid react. So make sure you import plus circle and you can remove the pencil. Actually leave it here. Perhaps we're going to use it. And instead of edit description, it's going to be add a chapter like this. And now let's change the remaining instances of is editing to is creating like that. So right here, and right here, and this form field is going to be a title, right? So just so we get rid of that error right here. So there we go. Now we have course chapters, this is a description is still here, we're going to modify that. And we have a add a chapter here, which is working by triggering the text area. All right. So now let's go ahead and let's modify this a little bit. So inside of this form right here, we are not gonna use a text area. Instead, we're gonna be using the input from at slash components UI input like this and you can remove the text area. So we're just gonna be working with the input and let's go ahead and change this to say example introduction to the course. Like so something that the user should name their, should name their chapter. Great. So inside of here, we can actually remove this div wrapping a button, no need. It's only one element inside like that. And instead of save, we're going to say create, right? So we indicate that this is only for creating like that. Perfect. And now let's go ahead below this, a conditional which renders the form. And let's go ahead and add If we're not creating like that, let's go ahead and let's add a div, which is just going to say no chapters like that for now because there's nothing else to do here. Otherwise, sorry, also another one is creating. So a separate here below. Which is just going to say a paragraph, drag and drop to reorder the chapters like that. And the class name is going to be text.extrasmall, text-muted-foreground, and mp-4 like this. Great. And now what I want to actually do is I want to find this, right? So where we have the initial data.description and no description. You can remove this entirely like this, right? So no need for that. so the only thing I wanted to write is no chapters like this great and now let's just style this a bit so it makes more sense when a user sees it so let's go inside of here and I'm going to go ahead and give this a class name to be dynamiccn so make sure you have a cn we should already have it imported and we do and let's give it a default value of text dash small and margin top two like that. And then if there is no initial data, data.chapters.length. So if it is empty, in that case, text-slate-500 and italic like that. And don't forget to put a comma here. And now we have an error because we're trying to access the chapters inside of the initial data, which we defined in the props to just be a course. So let's extend it the same way we did with the attachments to use chapters, which is a chapter from Prisma Client like that. So make sure you import the chapter right here, the same way we did with course from Prisma Client. All right, so now this error should not exist here. Great. And now let's go inside and let's just write if there is no initial data.chapters.length, in that case, we're going to write no chapters like this. And I'm just going to write a comment here to do add a list of chapters like that. So now let's take a look at what this looks like. There we go. So now it says no chapters like that. Perfect. And let's go ahead and click on add chapter. And there we go. You can see that now we are just able to quickly create a new chapter using this input. And let's go ahead and try that immediately. So I'm going to write test and I expect an error because we have not created an API route, which is able to submit this chapters right here. So let's go ahead and do that. So let's go inside of our API folder. So right here inside of courses, course ID, and create a new folder, not new file, but a new folder called chapters like that. And inside, create a new file route.ts like this. And let's export asynchronous function post, which is going to have a request, which is a type of request like that. And it's also going to have params like this, which is an object which has a course ID, which is a string like that. Let's open a try and catch block. Let's get the error and let's console log chapters with an error like that. And let's return new next response from next slash server internal error with a status of 500 like this. Great. Now let's go inside and let's extract our user ID from the out and we get that from clerk next JS. Now let's go ahead and let's extract the title using await request dot JSON. If there is no user ID, in that case, return new next response unauthorized with a status of 401 like that. Now let's check if we are the owner of this course. So course owner is await DB. So import DB from at lib slash DB right here. I'm going to separate from this imports. All right. Awaited db.course.find unique where id is params.courseid and userid is userid like that. If there is no course owner constant, that means we are not the owner. So return new next response unauthorized with a status of 401 like that. Perfect. So before we can create a new chapter, we have to attempt to fetch a new chapter, to fetch the last chapter, so we know what is the position we have to add for this newly created chapter. So const last chapter is going to be await db.chapter.find unique, sorry, find first where course ID is params.course ID. So we're attempting to find a last chapter which was created inside of this course. And we're going to order by position, but this time descending like this. Great. And now we're going to write const new position last chapter. in that case last chapter dot position plus one otherwise we're going to start with the position of one and now we can finally create our chapter so const chapter await db.chapter.create like this data we're going to pass in the title of this chapter the course id which is params.courseid and a position which is new position like that. There we go. And now we can do return next response.json chapter like this. And I'm just going to go ahead and prepare my Prisma Studio. So I'm going to write npx Prisma Studio and that's going to open on localhost 555. So let's go ahead and just refresh this. And let me show you that right now we have all of these new models here. But what I'm interested in is a chapter. So I'm going to select a chapter here. I'm going to close everything else. So I'm just focusing on the chapter here. And now let's go ahead and let's try and create a new chapter. So I'm going to write test. Let's click create. And there we go. It looks like it's working and we no longer have a text that there are no chapters.\nwhich should mean that if I refresh this Prisma Studio, where my chapters are, there we go. I have a chapter with a title with no description, no video URL, and a position of one. And is published, as you can see, is false. Great. And is free is false. Max data doesn't exist. And we have a relation to the course. Perfect. There are no user progress on it. So no one marked this chapter as complete or anything like that. There we go. So that is exactly what we need. What we have to do next is we have to create a list inside of this course chapters, which we are going to use to drag and drop to reorder them. And then we're going to start creating the individual screen, which is going to look very similar to this for editing and publishing a chapter. So now let's go ahead and let's create a list to reorder our chapters and to display them. So I'm going to go back inside of my chapters form right here. And I'm going to find this to do which we've written right here, add a list of chapters right below the no chapters text. So let's remove this and let's write chapters list like this, which is going to be a self closing tag, we're going to have it an option on edit, which is going to be an empty arrow function, we're going to give it an option on reorder, which is also going to be an empty arrow function. And we're also going to have items, which are either going to be initial data.chapters or an empty array like this. And if you save, we're going to get an error because chapters list does not exist. So let's go ahead and let's find the chapters list. So let's create one inside of the underscore components, chapters-list.tsx. Let's mark this as use client like that. and let's export const chapters list like this. And let's return a div chapters list. So it doesn't matter. We're going to change this. All I want to do is go back inside of the chapters form and import this from dot slash chapters list because we are in the same folder. So we can do that. And if you see, we have now an error here because we have not added an interface inside of the chapters list. So let's do that now. let's write an interface chapter list props chapters list props like that it's going to have items which is going to be a chapter from prisma client and make sure you add a little array at the end because there's going to be multiple of them on a reorder it's going to have update data which is going to be an id which is a string and a position which is a number and that's going to be an array and then just use a void at the end and on edit which is going to be a very simple function which accepts the id which is a type of string and returns a void as well and now let's go ahead and let's assign those props so chapters list props like that and let's extract them so items on reorder and on edit like that and now inside of our chapters list we no longer have this errors. Sorry, inside of the chapters form component where we render the chapters list, there are no more TypeScript errors. Perfect. So what I want to do now is I want to install a package called React Beautiful Drag and Drop. But one thing with that package is that it's been discontinued, meaning it's no longer maintained by its original creators. So there is a fork called hello pangia dnd so let's go ahead and let's go ahead and install that so i'm going to write npm install at hello dash pangia or pangia i don't know how to pronounce it slash dnd like this so this is the same package called react beautiful drag and drop but the original is no longer maintained so uh these people created a fork and they are actively maintaining it Great. So let's do npm run dev. Let's refresh our local host and let's go back inside of the chapters list right here. So what I'm going to do is I want to prepare a couple of states and a couple of functions here. So const is mounted, set is mounted to use state false. Const chapters, set chapters is going to be use state, which accepts the items as the default value. And now let's do a little mounting trick here. So we're going to write use effect from react like this. So make sure you've imported use effect from react. Let's just add an empty dependency array and set is mounted to true. So what is this doing? Well, when you have the use client directive, that doesn't mean that the server side rendering is completely skipped, So this is definitely a client component, but use client is still run once on the server side rendering and then again executed on the client. And that can cause hydration errors if what was rendered on server side and then what was rendered on client side doesn't match. So what we have to do is a little trick here that we don't display anything if we're not mounted. So I'm going to display return null here, which means that in server-side rendering, this entire component is not even going to be displayed. And only when it comes to client-side rendering, this one is going to be displayed. And that's going to fix our hydration issues. But why do we even get hydration issues? Well, it's going to be because of that drag and drop component, which is not very optimized for the server components and server-side rendering, right? So that's why we have those hydration errors similar to models. We often have those in models as well. Great. And now one more use effect that I want to add above is use effect, which is going to rehydrate our items, right? So I'm going to go ahead and write items inside. And all I'm going to do is set chapters to be those new items like this. So just make sure you have use effect, use state and chapter and in your imports. And now let's go ahead and let's actually import everything we need from the HelloPengeaDND. So let's go ahead and import from at HelloPengeaDND. Let's import drag drop context. Let's import droppable. Let's import draggable. And let's import drop result, which is a type. All right. And while we are here, let's also import cn from add slash lib slash cn. Is it? Utils, my apologies. And let's also install one chat cn component called badge. So let's write npx chat cn dash ui at latest add badge like this and just confirm the installation of this component and then you can npm run dev your project again and just refresh your local host. Great, so we have all the imports we need and now we are ready to start drag and dropping. So let's go ahead and let's remove this div and let's add drag drop context like that. Let's add on drag end for now to just be an empty arrow function. Let's add a droppable like this. Let's give it an id. So droppable ID is going to be chapters like that inside. Let's go ahead and let's open this function like that, which is immediately going to return a div in which we're going to spread provider. So it's not going to be provider. This is provided. So provided dot droppable props like that. And the ref is going to be provided.innerRef like this. And inside, we're going to map over our chapters. So chapters.map. Let's get the individual chapter and the index like that. And let's go ahead and let's add draggable like this. Let's give it a key of chapter.id. Draggable.id is going to be chapter.id. And index is going to be index. So I'm just going to collapse this element like this. All right. So we have our draggable now. And inside of the draggable, let's go ahead and again open a function which says provided like that. And what it does, it returns another div, which is going to have a class name, which is going to be a CN library, which we have imported. The default values are going to be flex items-center, gap-x-2, bg-slate-200, border-slate-200, border, text-slate-700, like that, rounded-mv, margin-bottom of 4, and text-small, like that. And then we're going to have chapter.isPublished. In that case, it's going to be bg-sky100, border-sky200, and text-sky700, like that. All right. And let's go ahead and pass in the ref, which is going to be provided.innerRef. And we're also going to drag provided.draggableProps, like this. So in here, we spread the droppable props. And inside of this, we drag the draggable props. All right. Inside of that div, let's go ahead and create a little handle where we're going to be able to move our cursor on and then drag and drop by that handle. So open a div, which is going to have a class name, which you guessed it is going to be dynamic. So default values are PX-2, PY-3, Border-R, Border-R-Slate-200. On hover, the background is going to change to BG-Slate300, Rounded-L-MD, and Transition like that. And then if chapter is published, in that case, it's going to be border-r-sky-200, like that. And hover is going to be bg-sky-200, like that. And let go ahead and let spread provided props So this is going to be our drag handle and inside add an icon bi grid vertical Sorry let add a grid from lucid react and then I gonna see if we have any other options So grid from lucid react, let's just move that to the top right here and let's see if this is already visible here. There we go, it is. You can see how I have a little space for my icon here, sorry for my chapter here. So instead of grid, I actually want to use grip. So I'm just going to scroll so we can see here and it's going to be grip like this. So change this to grip and import grip from lucid react. There we go. So you can see now how when I hover, I'm able to drag and drop this right. All right. And now let's continue developing this. But let's just go ahead and give this a class name. So class name is going to be H-5 and W-5 like that. So it's a bit smaller. And let's go now outside of this div and let's render chapter.title like this. There we go. So this is our chapter called test like that. And then below that at a new div. and let's go ahead and let's give it a class name of ml-auto, pr-2, flex, items-center, and gap-x-2 like that. And inside, if chapter is free, in that case, we're going to render a badge element, which we just added. So add slash components UI badge like that, and just write free like this. So make sure you have the badge imported from the components here. All right. And then let's go ahead and let's go below outside of this conditional here and add another badge. And inside, we're going to write if chapter that is published, is going to say published. Otherwise, it's going to say draft like that. And let's give this badge a class name so class name is going to be cn bg-slate-500 but if chapter is published in that case it's going to be bg-sky-700 like this there we go so let's take a look at how this looks beautiful so you can see how now i have a handle here i have the title of this chapter and it's written that it is a draft. What's missing is a little pencil icon here that is going to be able to open the on edit function. So let's go ahead outside of this badge and let's go ahead and just write a pencil icon from Lucid React like this. Let's go ahead and let's give it, so I'm just going to scroll so we can see, there we go. You can see a big pencil button here now and let's just write on click to call on edit chapter dot id like that and let's give it a class name of w-4 h-4 cursor dash pointer hover opacity dash 75 and transition like this there we go so now it's a small little uh pencil right here perfect and one more thing that we have to do is just go outside of this draggable right here and let's just render provided.placeholder like this. There we go. So that is our entire UI for the drag and drop thing. What we have to do now is we actually have to enable this functionality to drag and drop. So what I'm gonna do is I'm just gonna refresh this. I'm gonna add a couple of chapters. So this is gonna be an outro. Let's create that. And there we go. now we have an outro. Let's add test. All right. So at least three of them like that. So we can have some fun. So if you try now, you can see that it appears like we can do something, but it actually isn't doing anything. You can see the moment I finish, they go back to their initial positions. Right. So let's just refresh. So they are all right here. All right. And let's go and let's actually create this on drag and function. So that's actually going to be a function on drag and like that. So let's go ahead inside of this, but above this return null, write a new function called const on drag and, and it's going to have a result in its props, which is a type of drop result, which we have imported. So just make her make sure you have drop result imported from D&D right here. And let's go ahead and write if there is no result dot destination, we can just break the function. Otherwise, let's go ahead and create an array from our chapters. So const items is an array from chapters like this. Const open an array reordered item to be items dot splice result.source.index and one and then items.splice result.destination.index zero and reordered item like that. Great. And now let's go ahead and let's figure out which chapters were affected by this change so we know which one to send to the API to update their actual position in the database. So let's write const start index to be math.min result.source.index and result.destination.index. So like this. And course end index to be math.max result.source.index and result.destination.index like this. And then const updated chapters. So just make sure this is all in one line, all right? And then const updated chapters is going to be items.slice, like that. And let's go ahead and write start index and end index plus one like this. And then set chapters to be items, like this. And then let's go ahead and let's write const bulk update data is going to be updated chapters.map, get the individual chapter and immediately return an object with ID of chapter.id and position of items.findindex, get the individual item and search by the ID. So item.id is identical to chapter.id like that. And on reorder bulk update data like that. There we go. So this should be working now. Let's see if we can have it staying. There we go. So you can see how now when I reorder, they stay here locally, right? So I'm going to add a new one like this. There we go. And if I drag it to the top, you can see that it stays here, right? If I move test above it, it's staying, right? But when we refresh, it's all back to its initial position. So that's what we have to change now. So let's go ahead and let's go back inside of our chapters form function right here. So you can see that we did not pass anything in the on reorder nor in the on edit. So let's create this on reorder function. So I'm going to go just outside here. So just below the on submit function, let's write const on a reorder is going to be an asynchronous function, which takes in the update data, which is going to be an array of objects like this. And it's going to have an ID, which is a string, and a position, which is a number, right? Like that. So just make sure to write this. All right. And now inside, we're going to have a try and catch block. So let's toast.error. Something went wrong here. Let's add it finally, which is going to call set is updating to be false, like that. And inside set is updating to be true inside of the try block. And then await axios.put slash API slash courses slash individual course ID slash chapters slash reorder. So we're going to have a separate route just for reordering. Like I don't want to mix and match that with our update route. And let's go ahead inside and let's pass in a list, which is going to be the update data like that. There we go. and then a toast.success saying chapters reordered. Reordered. Okay, reordered like that and router.refresh like this. There we go. And now we can use this on reorder and pass it inside of this chapters list right here on reorder. So what I expect to happen is it to get a 404 error once I attempt to reorder. So I'm gonna drag this last one to the top and I should get an error. There we go. Something went wrong. So I got an error. That's exactly what I expected to happen. Perfect. So what we have to do now is create that API route. So let's go inside of our API folder. Let's go here. So API and we have the chapters. So API courses, course ID, and we have the chapters here. And inside, create a new folder called reorder like that and create a route.ds inside of it. And now inside, let's go ahead and let's write export asynchronous function put, which is going to have a request, and it's going to have params, which are going to hold the course ID, which is a string like that. All right, so we have that now. Let's go ahead and open a try and catch block. Let's console log the error. So I'm going to call this reorder and an error. So I know what's happening in the terminal. And then return new next response from next slash server internal error like that with a status of 500. There we go. Now let's go ahead and let's extract the user ID from out function, which we get from clerk next JS. While we are here let import database from at slash lib slash DB Let go ahead now and let write if there is no user id we can return new next response unauthorized with a status of 401 like that now let go ahead and let get the list from our request.json so wait request.json like that and let's check if we are the owner of this course. So const own course is going to be await db.course.findunique where id is params.courseid and userid is userid like that. If there is no own course, in that case, we can just return unauthorized the same way we did for the userid like that. All right. And now we're finally ready to update our chapters. So for let item off list await db.chapter.update where id is item.id and data is going to be position item.position. So just like that. And then let's go ahead and let's just return new next response success with a status of 200 like this. So let's see if this is going to change anything. So I'm going to refresh here. I'm going to drag this one to the top and let's see if we're going to get message. It says chapters reordered and there we go. This one is at the top. If I refresh, it stayed at the top. So they have the correct position. So let me refresh this so I don't have my Prisma Studio running. Let's go inside of my terminal and let's run npx Prisma Studio. Oops, npx Prisma Studio like that. And now we're going to see that our chapters all have different positions, right? So as you can see, this chapter 1, 2, 3, let me just zoom in. So this chapter 1, 2, 3, 1, 2, 3, 1, 2, 3 has position 0 in the database. but what happens if I go ahead and drag and drop this one all the way to the end so let's wait for this to update there we go chapters reordered and now if I refresh this I think that this one should have there we go a position three so it works and all the other positions have switched as well now this one has the position zero. Perfect. So that is exactly what we wanted. One thing that I want to modify now is that when we drag and drop, it seems weird that we don't know what's happening, right? So let's add a little loading state that is going to help us with that. So let's go inside of the chapters form right here. And let's go all the way to the beginning of the return function. So in here, let's go inside of this div and let's write if is updating. We have that state. Let's go ahead and let's return a div with a loader2 from Lucid React. So make sure you have loader2 from Lucid React and you can remove the pencil. We're not going to need it. So loader2 is going to have a class name of animate-spin h-6w-6 and text-sky-700 like that. And this div is going to have a class name of absolute like this. H-full, W-full, BG-slate-500-20, top-0, right-0, rounded-md, flex-items-center, and justify-center. And let's also give this main parent div a relative class name like this. So we use this updating in the on reorder function. So if we did this correctly, when I drag and drop, there we go. You can see how now the entire form is blocked because we are doing the reordering. Perfect. So let's move this test to the top. There we go. It's reordering. It's loading. And if I refresh, it stays in the exact same position. Great. So we did an amazing job here. What we have to do now is enable this little pencil, right? So if I go ahead and find my onEdit function here inside this props, you can see that it's an empty array. Sorry, it's an empty function. So let's go ahead and let's create the onEdit function, which doesn't exist. So let's go just below our onReorder function. So right here. And it's going to be a very simple one. So const onEdit is going to take the ID, which is a string. and it's gonna just do router.push backticks slash teacher slash courses slash individual course ID slash chapters slash ID like this. And we get the ID from this props right here. Perfect. So now if I click on the edit button, I should get a 404 page. There we go. So that's what we're gonna create next. We're gonna create the screen to actually add a description for our chapter and to upload a video for our chapter. Great, great job. So let's go ahead and let's create the chapter ID page, which is currently a 404 page. So I'm going to go ahead inside of my app folder, inside of dashboard, routes, teacher, courses, course ID. And inside of here, create a new folder called chapters. And inside, create another folder in square brackets, chapter ID, like that. Then inside, create a page.dsx element. And let's just quickly go and write chapter ID page like this with a div chapter ID. And there we go. I no longer have an error. So if I go back to my customized chapters, you can see that I can click on individual pencil button, and I'm in the chapter ID, and my URL is correct. Perfect. So it's going to be a very similar screen to our course ID page. So let's go ahead and let's mark this as an asynchronous function like this. And let's go ahead and let's extract the params. And let's give our params a type. So params are gonna have a course ID, which is a string, and a chapter ID, which is a string as well like that. Perfect. And now let's go ahead and let's get our user ID from out. And then we can check if there is no user ID, we can just return redirect like that from next slash navigation to the root page. So just make sure you have those two imported. All right, and now we can load our chapter using the database util. So I'm going to import DB from as slash lib slash DB like that. And let's go ahead and let's write const chapter to be equal await DB dot chapter dot find unique like that, where ID is params dot chapter ID and course ID is params dot course ID like that. And let's also include max data to be true if we have it. All right. So you shouldn't get any errors here. If you are, make sure to confirm that in your schema, you have all the necessary fields in your chapter model. So you should have the max data like this. That's why I can include it. All right. Now that we have that, let's go ahead and let's just write if there is no chapter. In that case, we can return redirect to the root page again. All right. And now let's do a very similar thing like we did in the course ID page. So const required fields to publish this chapter is going to be chapter.title, chapter.description, and chapter.video.url. Like that. And then we're going to write const total fields to be required fields.length. like that. Then we're going to have const completed fields, which are going to be required fields dot filter Boolean with a capital B dot length. So we're going to get those fields, which return it true after they run through this filter. Con completed completion text is going to be backticks, open parenthesis, completed fields slash, and then we're going to write total fields like that. There we go. And let's also just render that inside. So let's go ahead now that we have this, let's give this a class name of padding six like that. And let's go ahead and write a div inside the class name of flex items dash center and justify dash between like that. Let's go ahead and let's write a div here with a class name of W dash full. And let's add a link component from next slash link. So make sure you import link from next slash link. I'm just going to move to the top. All right. And let's go ahead and let's give this link and href backticks slash teacher slash courses slash params dot course id so basically back to where we came from right and let's give this link a class name of flex items dash center text dash small power opacity dash 75 transition and margin bottom of six and let's just give it an arrow left icon from lucid react which is going to have a class name of h-4 w-4 and margin right dash two and let's also write back to course setup like this there we go so we have a nice little button that when we click on it goes back to the course where we came from right so i can click on this one and i can go back now perfect so we just have that little action here that is great and now that we have that what we have to do is we have to create the title, right? So let's go ahead below this link. And let's create a div with the class name of flex items dash center justify Oops So flex items dash center justify dash between and W dash full Like that. Let's create another div with the class name of flex flex dash call and gap. Whoops. flex flex dash call gap dash y dash two like that and now let's go ahead and let's give it an h1 element of chapter creation like that and the class name of text dash to excel and the font dash medium like that all right and below that we're gonna have a span which is gonna say complete all fields, and then completion text, like that. And let's give it a class name of text dash small and text dash slate dash 700, like this. There we go. So now you can see that we have a very similar screen to what we do here in the course setup, which is exactly what we want. We want our user experience to be familiar throughout the creation of the course. All right. And now that we have that, what I want to do is I want to create a similar grid like we have right here and we're going to create these sections and we're going to create the chapter title because it's the easier one to create. So let's go right here to the bottom almost at the last div like that and let's create a new div and let's give it a class name of grid. So class name grid grid dash calls dash one MD grid dash calls dash two gap is going to be six and a margin top is going to be 16 like that. All right. And now inside, we're going to go ahead and create a new div with class name space dash Y dash four. Let's go ahead and write a new div inside and then a div, which is going to hold our icon badge. So class name flex items, dash center and gap dash x dash two. And inside, let's go ahead and write icon badge from at slash components icon dash badge. Icon is going to be layout dashboard like that. So we're going to have layout dashboard, make sure you import that from Lucid react. I'm just going to move it up here. All right. And below that, we're going to have an h2 element, which is going to say customize your chapter like this with the class name of text dash XL like this. Great. And now outside of his, I'm going to write a to do chapter title form. So let's just quickly see what we have. There we go. So we have this section right here. And now we have to create a form similar to title form, but this time for chapter, right? So let's go ahead and do that. So what I'm going to do is I'm going to copy from the components inside of course ID, I'm going to copy this title form, and I'm going to create a new folder inside of the chapter ID folder called underscore components as well, because these ones are only going to be used for chapter ID. And I'm going to paste the title form here. And I'm going to rename it to chapter dash title form like this. And now let's also rename this as well. So it's no longer going to be title form. Instead, it's going to be chapter title form for these three instances like that. All right. So we have that ready. We can actually remove the message. We can just write like this simpler. All right. And besides having course ID, we're also going to have chapter ID. So chapter ID is a string and it's required in the chapter title form right here. So you can also extract the chapter ID inside. All right. So we're going to use that obviously inside of the on submit function that you're going to modify in a second. So let's change this from course title to chapter title like that. Everything else can actually stay the same. All right. So we have this except this is editing. Well, in fact, I think you can say the same. We're going to see in a second. And let's change this from Advanced Web Development Placeholder to Introduction to the Course. Like that. All right. And let's go ahead inside of our page where I added the comment chapter title form. So just below this div, which is wrapping the icon badge and the h2, which says customize your chapter. This is all inside of the chapter ID folder page.tsx. So in here, we're going to add our newly created chapter title form like that, which is expecting the initial data to be a chapter, which I forgot to actually pass. And let's go ahead and give it a course ID, which is params.course ID and chapter ID, which is params.chapter ID like that. All right. and let's just see for the initial data. Aha, we write it like this. That's why it's working. All right. So there we go. As you can see, now I have my chapter title and it says test right here because that is the name of my chapter. So if I go outside and pick this one, it should say one, two, three, one, two, three. Perfect. So let's see if this is working when I click. There we go. And one thing that we have to change now is we have to change where the submit function is going. So let's go inside of the chapter title form right here. And let's modify this Axios badge to go to slash API slash courses, a course ID, but then it's going to go to slash chapters slash individual chapter ID like that. So make sure you have this and this is going to say chapter updated like that. Perfect. So we did that. And now if you try, obviously, we are going to get an error because this API route doesn't exist. So let's go ahead and create it. So let's go inside of the API folder. Let's go inside of the courses, course ID, chapters, and inside create a new folder in square brackets, chapter ID like that, and create a new file route.ds. All right. Now let's export asynchronous function patch. which is going to have a request, which is a type of request. And we're going to have params, which is an object, and which holds the course ID, which is a string, and a chapter ID, which is a string. All right. Let's open the try and catch block. Whoops. I got ahead of myself. All right. So let's open the try and catch block with the error, and let's console.log courses chapter ID. So anything for you to easier, to easily recognize this in the terminal. And then for the actual client, return new next response from next slash server internal error with a status of 500 like that. Perfect. Let's go ahead and let's get our user ID from the out function using next clerk, sorry, clerk next JS. Let's go ahead and let's get all the values using a weight request dot JSON. If there is no user ID in that case, return new next response. Unauthorized with a status of 401 like that. Let's go ahead and let's import the database from s slash lib database like that. Let's go ahead and let's find own course to be await DB dot course dot find unique where ID is params dot course ID and user ID is matching. And we can copy this clause for if there is no user ID and just check it for if there is no own course is the same response unauthorized. Great. And then we can just do const chapter to be await db.chapter.update like this, where ID is params.chapter ID like that. Course ID is params.course ID like that. Perfect. And let's just go ahead and write in the data where we're going to spread the values like that. And then let's return new next response. We're going to write next response dot JSON chapter like that. All right. And one more thing I want to do is I want to add a little to do comment. Handle video upload, right? So when we get a video URL, we're going to handle that separately here. And one more thing that I want to do is I want to change this. So it's not just going to say values like this. Instead, I'm going to go ahead and I'm going to extract is published. And then I'm going to spread the values separately. So the reason I'm doing this is so that users cannot accidentally set is published true. And then we immediately pass that in the values and the chapter is published, right? So chapter publishing will be controlled by a separate API route, which is going to check whether we have all the necessary fields and requirements to publish a chapter. So that's why I'm doing this is published is separated from the values. All right. And I think this should already work. So if I go ahead and try inside of my chapter title, so I'm going to say this is edited chapter. And if I click save, let's see, there we go. This is edited chapter. If I go back, this is edited chapter. Perfect. So it's working. Great. And now what we're going to do is we're going to create a nice little rich text editor using React Quill. So we can upgrade our description for this chapter, which is going to be written in HTML. So it's nicely presentable inside of the individual course page. Great job. So now let's go ahead and let's create the chapter description box right here. So in order to do that, I first want to install a package called React Quill. So let's go ahead inside of our terminal right here. I'm just gonna exit this terminal. I'm gonna shut down my app and I'm gonna write npm install react-quill like this. Let's wait a second for this to install and then we can run npm run dev again. Great. Now let's go inside\nof our components folders, our global components, and inside create a new file called editor.tsx, like this. Let's mark this as use client. Let's import dynamic from next slash dynamic, like that. Let's import react-quill slash dist slash quill.snow.css, like that. Let's create an interface editor props, which is going to have an on change option. We're going to have a value and we're going to return a void. And we're going to have the current value like that. Now let's write export const editor like that. Let's go inside of the props. Let's assign them to editor props like that. And inside, let's extract on change and value like that. Great. Now let's write const react quill to be use memo like this. So make sure you import use memo from react. And what we're going to do is we're going to return dynamic, which we imported right here. and then we're gonna go ahead and execute dynamic and inside we're gonna return an import function for React-Quil like this and let's go ahead and let's add an object SSR pulse like that and let's add the dependency array. So this is the entire function right here. So what we did is we imported React-Quil without server-side rendering. So as I said, use client is not enough to disable server-side rendering. So yes, this is a client component, but it is still rendered on the server once. Sorry, it runs on the server once, and then it runs again on the client side. And that won't work with React Quill. You're going to have hydration errors, so we can import it like this to avoid that. All right, and now that we have this, we have to do a very simple return function with a div and the class name of bg-white. And let's go ahead and let's add a theme, which is snow. My apologies, let's write React Quill, which we just imported dynamically. So let's give it a theme of snow. Let's give it a value of value and on change, on change like that. And that's it. That's our rich text editor. So very simple using React Quill. What I want to create now is an equivalent component called preview. So let's go ahead and let's copy this entire thing. And let's create a new component called preview.tsx like this. Let's paste the editor here and let's change this from editor props to preview props. So I'm going to change these three instances to preview props. and component name is preview and this is preview props, right? But we're not going to have an on change here. So you can remove it from here as well. And you can rename this from quill.snow.css to quill.bubble.css like that. And let's go ahead and let's remove this div like that. And instead, let's give this a team of bubble. Let's change. Let's remove this on change and let's add a property read only like this with capital O. All right, so now we have those two components. And now what we have to do is we have to go inside of our, where are we? So the chapter ID, where we have the chapter title form, what we're going to do now is we're going to create, we're going to copy the description form from the course ID components and paste it inside of chapter ID components. So let's rename this to chapter-description-form like that. Now let's rename that component. So it's not going to be description form. It's going to be chapter-description-form like that. All right. And now let's go ahead and extend the props to also accept a chapter ID, which is a string like that. Let's go ahead and let's just simplify this by just having it like that. Let's extract the chapter ID in the props, which we just added. All right. And this can stay the same. And let's rename this toast.success to chapter updated. And let's create, let's modify this Axios patch to go to slash API slash courses course ID slash chapters slash chapter ID. So the same way we did with the title creation. So this is what it looks like in one line. All right, let's save that. So this is now going to be chapter description like that. And instead of having a text area here, we're actually going to use the editor component from add slash components editor. So we can remove this too. So no need for that. We can just spread the field. So make sure you have the editor and you can remove the text area. We are not going to need it. All right, and now that we have that, let's go ahead and let's go inside of the page.tsx where we are rendering the chapter title form and let's render chapter description form like that so make sure you have chapter description form imported so I'm just going to separate this relative imports here all right let's go ahead and let's pass in the initial data which is a chapter let's go ahead and let's add course id to be params.courseID and chapterID to be params.chapterID like that. So it seems like we have some type errors inside. So let's go ahead inside the chapter description form. It's right here. And since we're including mux, I think, oh, okay. So we have the course here, but what it should be is chapter from Prisma Client. So go inside of a chapter description form and make sure that you assign initial data to be the chapter type from Prisma Client and not the course type. Since we copied this from the course component, that's why we had the type error. All right, so make sure you save this file where you have the chapter description form. Make sure you have your app running. I'm going to refresh my local host here and let's see how that's going to look like. So below this chapter title, I should have the chapter description. And if I click here, There we go. Look at this. We have a nice little rich text editor. We can do notations, right? We can do lines. We can do a bunch of stuff here. Let's see. We can add it like this. We can even add a link to something else. Great. And we can save this. And let's take a look at what happens now. So as you can see here, I am rendering HTML. Well, we don't want that, don't we? So we're going to use that preview component here instead. So let's go ahead and let's find where we render the chapter description. So let's go inside of chapter dash description dash form. All right, so I'm just going to collapse this. And let's find where we do that. So it's right here where we do this initial data dot description stuff. So first thing I want to do is I want to change this from a paragraph to a div. So I'm going to change this to a div and the ending HTML tag as well. All right, so we did that. And then let's just go ahead and let's change this. So if there is no initial data description like that, in that case, we're going to write no description like this. And below here, we're going to write if there is initial data dot description. In that case, we're going to go ahead and we're going to go and render the preview component from add slash components preview like this. So make sure you have the preview component imported. And all we're going to do is pass in the value to be initial data dot description like that. And let's see. And there we go. We have a nice little rich text editor. One thing that I want to show you how to do. So it might be that this text is a bit too small for you in comparison to every other text which we have, right? That's something I noticed while I was developing this. It just seems like this text is too small, especially when it's shown in a big course preview, right? So let me show you how you can change that. So you can change that by going inside of your app folder, inside of your globals.css. And just above the upload thing import, let's add a class name .ql-container, font-size, and you can hard code it to 16 pixels and add an exclamation important sign. And there we go. You can see how the text now is a bit bigger, which I think looks even better. Great. So I'm going to do that. And there we go. We have a nice rich text editor right here. We have working URLs here. We have underlines, a bunch of useful stuff. Great, great job. So before we go on to creating and uploading a video, what we are going to do is we're going to create a section called access settings. So in here, the creator of the course will be able to check whether this chapter is going to be free for preview or not. So let's go ahead and let's go back inside of our page where we render the chapter ID. So that's in the dashboard routes, teacher courses course id chapters chapter id page.tsx right here so let's find where we have the chapter description form that's the last thing we added here and let's just go ahead and go outside of this div and open a new div and inside we're going to create a little flex box compartment so flex items dash center and gap dash x dash two and let's add the icon badge which we already have let's give it an icon of i from lucid react so just make sure you have that for imported and below that an h2 element where we're going to say access settings like that and as you can see we have our icon right here and let just give this h2 element a class name of text like this great now what i want to do is I want to go ahead and copy the description form So let copy the description form from here Let paste it and let rename it to chapter-axis-form like that. Let's go inside of chapter-axis-form and let's just quickly rename this. So it's going to be chapter-axis-form like that. Great. And now let's go inside of page.tsx outside of this flexbox div and let's just render the chapter axis form like this. Let's go ahead and let's pass in the chapter, sorry, the initial data, which is going to be chapter. Let's pass in the course ID, which is params.courseID and chapter ID, which is params.chapter ID like that. There we go. And now we have duplicate description here. So let's go inside of that chapter access form. And what we're going to do is we're going to start modifying our form schema right here. So we are no longer modifying the description in here. Instead, we're only going to work with that field called is free. So if you take a look in our schema, in the model chapter, we have a form for your title, we have a form for description, and we can already change the position with drag and drop. What we're going to do now is the is free field right here. And then we're going to do the video URL and max data. So let's go ahead and change this. So make sure you're in chapter access form, right? So change the description to is free. And that is going to be a Boolean. So z.Boolean like that with a default value of false like this. And let's just not misspell Boolean. All right, let's go inside of the form right here. And the default values are going to be a bit different. So I'm going to write is free is going to be initial data that is free. But we're going to turn that into a definite Boolean. So just in case it's undefined or something, you can just add double exclamation points and that turns it into a Boolean. Or you can use this. You can mark it as a Boolean like that. And that's either going to be true or false then, right? But I like this way of doing it. All right. So now that we have that, what I want to do is just quickly fix our onSubmit function. So it is still a patch, but it's going to go to slash API Coursers. Oh, we already have it. I forgot that we copied that from the chapter description form. So nothing to do inside of the onSubmit. Great. But one thing that we have to do is we have to add a checkbox component from chatCN. So let's go inside of our terminals here. And I'm going to write npx chatcn-ui at latest add checkbox like this. So just confirm this installation. And after that is done, we're going to run the project again. And now we have to add that checkbox inside of our form. So let's go ahead and let's import the checkbox. So import checkbox from add slash components UI checkbox like this. All right. And now let's go ahead and let's find our form element right here. And what we're going to do is we're going to go inside of this form field. We're going to change the description to be is free like this. And then inside of this render, let's remove everything like this. And let's start all over again with form item. Let's give the form item a class name of flex, flex dash row, items-start space-x-3 space-y-0 rounded-md and border with a padding 4 like that. Then inside let's add a form control then let's add a checkbox component which we just imported which is going to be a self-closing tag. Let's give it a checked value of field.value and let's give it unchecked change to be field.onChange, like that. All right, so we have that now. Let's go outside the form control and let's create a div and let's give it a class name of space-y-one and leading-none. And let's go ahead and render the form description. So we have to import that from addComponent.com slash UI form. So make sure you have this imported right here. All right. and let's just write check this box if you want to make this chapter free for preview like that. All right so let's take a look at how that looks. So right now we're still rendering the description here so yes I forgot to change that but let's just see if when I click on edit description here there we go it turns into a checkbox so that's what we want and now let's edit this so it shows not chapter description, but, you know, axis settings and stuff like that. So we fixed the form. So let's now find this where we are not editing and see what we have to change here. So I'm going to go ahead and go even up and find the chapter description title where I have, and I'm going to change this to chapter axis settings like that. And let's change this to edit axis like this. and maybe perhaps we can just write chapter axis like that all right so i mean this is just copy you know you can adjust it to what makes more sense to you and now let's go inside of this is editing right here and let's just modify it so it matches uh what we need so i'm gonna go ahead and i'm gonna remove this and i'm gonna modify this from uh looking for description instead it's going to look for is free like that. And then let's go ahead and let's write initial data question mark, sorry, dot is free. In that case, we're going to render this chapter is free for preview like that. Otherwise, we're just simply going to render this chapter is not free like that. And we can then turn this div into a paragraph, right? So let's take a look at now. And there we go. So axis settings, it says chapter axis and this chapter is not free. And if I edit this, if I mark this as free, and if I click save, let's see if that's going to work now. There we go. Now it says this chapter is free for free view. And if I refresh, there we go. It's right here. Great. So we finished the axis settings. And what we have to do now is we have to add the section to upload the video. So let's go ahead and let's just prepare that section. So we have to go back inside of our page where we render the chapter access form right here. And what we're going to do is we're going to go outside of this div where we hold this icon badge outside of this entire div, which holds all the other elements and then create a new div, right? And inside, we're going to go ahead and add a flex box. So flex items-center and gap-x-2 and we're going to go ahead and render an icon badge with an icon of video from lucid react. So make sure you import the video from lucid react like that and let's go ahead and let's add an h2 element which is going to say add a video like that and a class name of text dash excel like this and if I expand now there okay you can say that it says add a video now so what we're going to do next is we're going to add a box which is going to work similarly to our image form in the course but we're also going to have to create a mux account and we're going to have to add some environment keys great job so far so now let's go ahead and let's create this video form right here So I'm going to go ahead inside of my app folder and I'm going to find the image form. So we have that inside of the teacher courses course ID underscore components. And in here we have an image form. So I'm going to copy that and I'm going to paste it inside of chapter ID components right here. And I'm going to rename it to chapter dash video dash form like that. All right. And now let's rename this props, this component name and the props reference here to chapter video like that. All right. Now let's change the initial data to be a chapter from Prisma client and let's extend it to also add max data here because we're going to be rendering the uploaded video inside of this form as well once we have it. So max data is going to be max data like that. All right. And we're also going to have a chapter ID, which is also going to be a string like that. And mux data can be optional. So let's also give it a question mark and a null option like this. Great. Now, instead of having... Actually, we don't need the form schema because we're not going to use a form. But let's use video URL like this. And we're not going to need any message here. like this. All right. Let's also extract the chapter ID in this props right here. Perfect. And in here, we're going to modify this to not go to this API, but to go to slash chapters slash chapter ID like this. So this is what it will look like in one line. And it's going to say chapter updated like this. All right. And in here, we're not going to change for we're not going to check for the image URL. Instead, we're going to check for the video URL like that. And in here for a video URL as well. And in here, we're going to say add a video and in here, edit video like that. So in here, we're going to add video URL as well. And instead of image icon, we're going to use the video icon from Lucid React. So make sure you have video imported and you can remove the image icon and you can remove the course import from the Prisma client. All right, so we have that. And now let's go ahead and instead of rendering an image here, we're just going to say video uploaded, right? So we're going to replace this later with an actual video player inside. And let's change this to be video URL, like that. And inside of here I just gonna modify it so that we don write this aspect ratio Instead what we gonna write is upload this chapter video like that And now we have an error to be escape the back tick with a Unicode like this There we go Perfect. So now that we have that, let's go ahead and let's go outside of here and let's write, If there is initial data, oops, if there is initial data dot video URL, and if we're not editing like that, let's go ahead and let's open a div in which we are going to write videos can take a few minutes to process. like that. Refresh the page if video does not appear like that. And let's give it a class name of text dash extra small text dash muted dash foreground and empty dash two. And I just misspelled this for ground like that. And now we are ready to use this inside of the page DSX right here. So outside of this div which renders the icon badge, let's go ahead and render chapter video form. It looks like I named this chapter video. So I just want to quickly go inside and change this. So not chapter video, but chapter video form. So chapter video form like this. and now I can use chapter video form like that. Let's give it a course ID of params.courseID. Let's give it a chapter ID of params.chapterID and initial data of chapter like that. So just make sure you have chapter video form imported right here, all right. And inside, one more thing that we have to change is this file upload. So the end point is not going to be course image. Instead, it's going to be chapter video like that. Perfect. So now you can see that in here, you can see, oh, it still says course image. Let's quickly change that. So let me just find where do we write course image right here. Let's change that to chapter video like this. There we go. So as you can see now, when I click, there we go. It only accepts a video. As you can see, only MP4 is allowed. I cannot select any images here. Perfect. So we can try and upload. So just find a very small video, right? This is just for a demo. I'm using a video of four megabytes, right? So something very, very small. So it uploads quickly. And let's just see how it looks once we upload this. So right now, we're not actually processing this video or anything. And there we go. So it says video uploaded. And we have a nice message that videos can take a few minutes to process. So we're going to replace this now by actually processing this video and creating a proper MUX asset, right? So what we have to do is we have to prepare a couple of environment variables, right? So we're going to have to go inside of our dot environment right here. and let's go ahead and let's add mux underscore token underscore id and mux underscore token underscore secret like this so let's add this to right here and I want you to go ahead and visit mux.com website and just go ahead and find the sign in button so I'm gonna go ahead and log in using Google and just fill out this form. So I'm going to name this tutorial like that and let's click create an account here and just go ahead and fill out this form if you want to and there we go. So you can use MUX for testing without adding any credit card, right? But if you want to get out of test mode and have no limits, then you have to add a credit card and then you have a free tier where you can learn more about that by reading the documentation of MUX, right? But for the tutorial, you don't need to add a credit card. You can get MUX up and running. So what I want you to do is I want you to go inside of settings right here and click on access tokens like that. And in here, let's click generate a new token and select MUX video. So let me zoom in so you can see. So select MUX video and make sure that the right is selected. So read is pre-selected, but make sure that right is selected as well. Make sure all of this is in production and click generate token. So don't close this. Let's just copy this immediately and let's paste it inside. So this is the MUX token ID and let's go ahead and copy the secret key as well and let's paste that right here. There we go. So now we have both of them and now you can click continue and you can close this. Great. And now you can go ahead and click on this assets right here. And right now there are no assets here. So we're going to go ahead and create those now. Great. So you don't have to close the MUX tab already because I want to show you how that's going to look once we actually process the video. So now what we have to do is we have to install some packages. So let's go inside of terminal. I'm going to shut down my app and I'm going to write npm install at mux slash mux dash node like that. After that has installed, we're gonna have to install a package called npm i mux slash mux dash player dash react like this. And once you have two of those libraries, you can go ahead and run your project again. So let's just wait for this to install. There we go. npm run dev again. And now what I want to do is I want to go back inside of the route where we update our chapters. So go inside the API, courses, course ID, chapters, chapter ID, route.ts right here. So as you can see, right now we separate these values in here. And that's great. So we save the video URL inside of the chapter. But I wrote a to do here that we have to handle video upload. So let's go ahead and let's do that. So I'm going to go and import mux from at mux slash mux dash node like that. And then I'm going to go ahead and write const and I'm going to destructure a video to be new mux and I'm going to pass in process dot environment dot mux underscore token underscore ID as the first argument and process dot environment dot mux underscore token underscore secret as the second one. And you can just put exclamation points at the end to get rid of these errors. And when you write this, always make sure that they match what's written in your environment file. So mux token ID, make sure you have that. All right. And mux token secret, make sure you have that. Great. So now that we have that, let's go down to where we actually have to check for our video upload. So I'm going to remove this comment right here. And I'm going to write if there are in values that we have, we have a video URL. In that case, let's begin the video creating process using MUX. So first thing I want to do is I want to find existing MUX data, right? So if the user changes the video, we want to delete the asset from MUX if it already exists and then create a new one. So we don't want to fill up our storage with MUX. So let's write const existing mux data to be await db.mux data.find first where chapter id is equal to params.chapter id, like that. And then we're going to go ahead and write if we have existing mux data. In that case, let's await video.assets.delete. So just del existing max data dot asset ID like that. And then let's do await DB dot max data dot delete where ID is equal to existing max data dot ID like that. So this is our cleanup function if user is just changing a video. And now we're going to write the code if user never uploaded any video whatsoever. So let's write const asset to be await video.asset.create like that. Input is going to be values.videoURL. Playback policy is going to be public and test is going to be false. All right. And now let's go ahead and let's write await db.maxdata.create like this and let's give it data of chapter id to params.chapter id, asset id to asset.id, playback id to asset.playback underscore ids, question mark, pick the first one in the array and dot id of that one. So there we go. We just created our very own function to create an asset. So let's see how that looks like now. So in here in my assets, currently I have nothing, right? But if I go ahead and edit my video and if I select a new video and I'm going to upload it now, what should happen is that when this video gets uploaded and we have a video URL in the patch request, a MUX video should get created. So let's see, there we go. Seems like no errors on this side. And there we go. You can see that I have one asset and you can see that it only lasts 10 seconds. That's because it is a test asset, right? So if you're wondering how to get rid of this thing, which says MUX for video API purposes only, well, you have to add a credit card for that, right? But if you just wanna try it out without credit card or anything, then you can just play around with this. as a proof of concept, right? Or for your portfolio. But if you actually wanna use this for a client, well, you're gonna have to upgrade to actually use MUX, but even then you have a free tier. Great, so we have that working now, which means that we are ready to actually display that little video instead of this text video uploaded. So let's go ahead and, oh, looks like I have not saved my environment file, but all right. And let go now back inside of our app folder dashboard routes teacher courses course ID chapters chapter ID components and find the chapter video form like that So what I want to do is I want to import a file. So import mux player from at mux slash mux player react like this. Great. And now what we have to do is we have to actually render this. So let's find where we render our video icon. It's right here. All right. And now in here, let's go ahead and let's write mux player, which is going to be a self-closing tag. Let's go ahead and let's give it a playback ID, which is going to be initial data, question mark dot mux data, question mark dot playback ID, or just an empty string like that. and metadata like that. Actually, we don't need metadata. So I think we can just use this. Let's see if that is gonna work. So if I refresh here, there we go. You can see that we have our video player right here. Perfect. So we are now finally ready to publish this chapter because we have all the fields. And when this chapter is published, we can finally publish our course. Great. So let me just show you how this looks one more time. from zero. So I'm going to go back to course setup here and I'm going to create a new chapter called test like this. Now I'm going to go ahead and edit it and I'm going to add a new video. So let's see how that looks like when I add an asset without there being anything before. So it's uploading, it's uploading and once it's processed in here we're going to have that max video and what will happen is that for a second it's going to say that the video is still processing, right? There we go. So it says that video is not currently available. So just like on YouTube, if you ever uploaded a video, it takes some time to process that video, right? But you can see since it's a small video, the moment you refresh, I can see my video right here. Then again, if you want to get rid of this badge right here, you have to upgrade MUX. Great, but for tutorial, you can keep it. It doesn't really matter. All right, great, great job. So what we're going to do now is create some little actions here to actually publish this chapter. What we have to do now is we have to create our actions, which we are going to use to publish both the course itself, but also an individual chapter. But before we do that, I want to create a little banner on top here, which is going to tell the user whether this chapter is published or not. So let's go ahead and do that. So I'm going to go inside of my components folder, so my global components, and I'm going to create a new file called banner.tsx, like that. And let's go ahead and let's write export const banner like this and return a div saying banner. So we're going to do that for now. And now let's go to the top and let's add some imports. So let's import alert triangle from lucid react and check circle check circle item like that. And now let's go ahead and let's import cva and variant and type variant props from class variance authority like that and let's also import cn from lib utils now let's write const banner variance which is going to be cva let's give it the default classes of border text dash center p dash four text dash small flex items dash center and w dash full like this. And now let's go ahead and let's open an object. Let's define our variance. So our variance is going to be a variant like that. And they're going to be warning, which can be bg-yellow dash 200 slash 80, border dash yellow dash 30, and text dash primary like that. And we're going to have a success variant, which is going to be bg dash emerald dash 700, border dash emerald dash 800, and text dash secondary like that. And outside of variants, let's write default variants. And that's going to be a variant called warnings. That's going to be the default one. All right. And now that we have that, let's create an interface. So interface banner props extends variant props type of banner variants. Like that. And a label of string. Great. And now inside of here, let's go ahead and let's map those banner props and let's extract the label and the variant like that. Perfect. So what I want to do now is I want to create an icon map. So I'm going to do that outside of the component. So const icon map, warning is going to be alert triangle, which we imported and success is going to be check circle icon like that. Great. So now that we have that, let's go ahead and let's create a constant for a specific icon we want. So const, icon with a capital I is going to be icon map. And we're going to pick from a variant. If we don't have a variant, we're going to use warning like that. Great. And now let's go ahead and let's write class name for this div, which is going to be cn. Inside of, we're going to write banner variants. and then we're going to pass in the variant which user selected like that. And inside, let's render the icon, which is going to have a class name of h-4, w-4 and marginWrite2 and a label like this. Great. So now we have our banner component and now I want to add it to the page.vsx which renders the chapter. So let's go inside of the teacher, courses, course ID, chapters, chapter id page.tsx right here and I'm gonna go ahead and I'm gonna wrap this entire thing inside of a fragment like this so let's go ahead and wrap this thing in a fragment like that let's indent it and now I'm just gonna write if chapter is not published so exclamation point, right? In that case, we're going to render the banner from add slash components banner. So just make sure you imported the banner. And we're just going to move it with this components right here. Great. So we have the banner now. Let's give it a variant of warning. And let's give it a label of this chapter is unpublished. It will not be visible in the course like this. There we go. So you can see how this looks now. So it gives us a warning that this chapter right here is not published. Great. So now we're ready to create our actions. Perfect. Before we do that, after completion text, let's go ahead and let's add another constant const is complete. And that's going to be required fields dot every Boolean like that. So we're going to check that every item inside of this array is true, right? So if we don't have a title, description or video URL, this is not going to be true. Perfect. So now that we have that, let's go ahead now and let's find this text which says complete all fields and find the end of this div which encapsulates that span and inside we're going to write chapter actions like that. So it's a component that we don't have yet, but we are going to create it. So you can feel free to save this and you're going to get an error, but let's already prepare some stuff. So disabled, if it's not complete like that, course ID is going to be params.course ID. Chapter ID is going to be params.chapter ID and is published is going to be chapter.isPublished like that. Great. So now let's go ahead and let's go inside of the underscore components and let's create a new file called chapter-actions.tsx. Let's mark this as use client and let's export const chapter actions like that and return a div. So just write something inside. Let's go back inside of page and let's just import this from dot slash underscore chapter actions. Great. And now let's create an interface that's going to match those props. So interface chapter actions props is going to have disabled, which is a boolean. It's going to have course ID, which is a string chapter ID, which is also a string and is published which is a boolean like that great now let's add those props here so chapter action props like that and let's extract all of them so disabled course id chapter id and is published like that great so now that we have that let's go ahead and let's create a, so we have a div here. Let's go ahead and let's give it a class name of flex items that are center and gap dash x dash two. Let's add a button from components UI button. So make sure you import that. All right. And inside, let's go ahead and let's write is published question mark unpublish. Otherwise, the action is going to be publish like this. And let's go ahead and give this on click. It just be an empty arrow function. Disabled prop is going to be disabled. Variant is going to be outline and size is going to be small like that. Great. And now let's go ahead and let's add another button in here which is just gonna render the trash icon from Lucid React. So make sure you import the trash from Lucid icon and I'm gonna separate these two imports. Alright, let's go ahead and let's give this a size of small and disable off.\ndisabled. Actually, no, this one doesn't have that disabled. And let's give it a class name of h-4 and w-4 like that. And there we go. You can see how now we have chapter actions so we can publish or we can delete. Great. So the first one I want to do is the delete one, right? So I want to show you how to delete your chapters, right? Because it's easier to do and because we have to add another component called a dialogue. So let's go inside of the terminal and let's add a new chatcn library. So npx chatcn-gi at latest add alert dialogue like this. confirm the installation and after this is installed you can run your app again all right and now let's go inside of our components folder let's create a new folder called models and inside create a new file confirm dash model.dsx let's mark this as use client and let's import everything we need from add slash components slash UI slash alert dialogue. So we're gonna have a alert dialogue itself. We're gonna have a alert dialogue action, alert dialogue cancel, alert dialogue content, alert dialogue description, alert dialogue putter, alert dialogue header, alert dialogue title and alert dialog trigger like that. Great. Now let's create an interface. So interface confirm model props is going to have children, which are react.react node, and it's going to have on confirm, which is going to be an action which just returns a void. Great. Now let's create our constant confirm model like that. And let's go ahead and let's map this props to confirm model props. So let's extract children and on confirm. Great. And now what we're going to do is we're going to return the alert dialog, then alert dialog trigger like this and let's give it a prop as child and inside we're going to render the children now outside of the alert dialogue trigger we're going to write alert dialogue content let's give it an alert dialogue header like that whoops so let's end the tag alert dialogue header All right. Inside of that, alert dialogue title, where we're going to write, are you sure, like this. And then alert dialogue description, where we're going to write, this action cannot be undone, like that. Outside of that, sorry, outside of header, we're going to add alert dialog footer like this. Alert dialog cancel, which is just going to say cancel. And alert dialog action like that, which is going to say continue. And on click, we're going to call on confirm like that. So now we created a reusable alert model with a continue or cancel button. Great. So now let's go inside of chapter actions and let's wrap this button which renders our trash with our new confirm model like this. So I'm going to go ahead and wrap this. And what I'm missing is an on confirm, which for now is just going to be an empty arrow function like this. so now if you click on this you should get a pop-up so just refresh your page if you haven't and let me just wait for this to load there we go so now when I click here there we go we have an are you sure right here and I can cancel it I can also continue right but nothing is happening so now let's create an action to actually a delete a model so let's go ahead in the chapter actions here and let's write const on delete like this and let's go ahead and let's add const is loading set is loading set is loading to be used state from react and give it the default value of false so i'm gonna move that to the top let's go ahead and let's mark this as a an asynchronous function. And let's open a try and catch block. In the catch block, I'm going to go toast.error. Something went wrong and I forgot to import toast. So toast from react hot toast. Make sure you have that. I'm going to move it to the top. All right. And in the finally block, we're going to say set is loading to be false like that, which means in the try block, we're going to write set is loading to be true like that and then let's just do await axios from axios so make sure you import axios i'm gonna move it to the top all right and i'm just gonna write axios.delete open backticks slash api slash courses slash course id slash chapters slash chapter id like this great. Let's write those.success to be chapter deleted like that and now let's go ahead and let's add our router. So const router is going to be user router from next slash navigation. So make sure you imported that as well. All right and now let's just write router.refresh and then router.push slash teacher slash courses slash course id like this great and now let's use this on delete to be the on confirm of this one like that and let's give this a disabled prop of is loading like that and let's modify this disabled to be disabled or is loading right so we haven't yet created the publish action and what we will in the future. So now if I try and click this, I'm going to get something went wrong because we don't have an API route to delete a chapter. So let's go ahead and do that. So we already have a file for that and that's where we update our chapter. So we're going to extend that file to delete it as well. So courses, course ID, chapters chapter id route.ds so let's go above the patch function and let's write export asynchronous function delete which has a request of type of request and it has params so you can just copy the params from the patch function it's exactly the same right there we go let's go ahead and open this. Let's open a try and cache block. Let's console log chapter ID delete with an error so we ourselves know what's going on and let's return new next response internal error like this with a status of 500. Perfect. Let's go ahead and let's extract the user ID from the out which we already have imported because of the patch function which we are using here and now let's go ahead and And let's write, if there is no user ID, let's return new next response, unauthorized, with a status of 401. Perfect. Now, let's attempt to fetch this course using this user ID. So, const own course is going to be await db.course.find unique, where ID is params.course ID, and user ID is user ID. Great. I'm just going to copy and paste this function. And I'm going to check if there is no on course. That means we are unauthorized. Perfect. So now let's go ahead and let's find this chapter. So const chapter is going to be awaited db.chapter.findUnique, where id is params.chapterId. And courseId is params.courseId, like this. and first thing I'm going to do is if there is no chapter found we can return new next response not found with a status of 404 like this great now let's check if the chapter we're trying to has a video url if it has we're going to have to delete max data so if chapter.video url like this Let's write const existing mux data to be await db.mux data.find first where chapter id is params.chapter id like that and if we have existing mux data in that case await video.assets.delete existing max data.assetID like that and then await db.max data.whoops max data.delete where id is existing max data.id like that so we have our cleanup function for the assets and after that is done let's go ahead and let's just write const deleted chapter to be await db.chapter.delete like that where id is params.chapter id like that all right so we have to check for something else so I told you that a course cannot be published if we don't have at least one active chapter right so at least one chapter needs to be published so what I'm going to check if after we delete this chapter is there still a chapter in this course that is published if there isn if this one which we deleted was published that means that we going to have to to unpublish the entire course So let write const published chapters in course to be await db where course ID is rams.course ID, and we're also going to write is published to be true, like this. And then we're going to go ahead and write if there is no published chapters dot length. So if the array is empty, if we haven't found any in that case, let's update the course. So await db.course.update where id is params.courseid and data is going to be is published false like that. Great. So we have that working and let's just now return new next, whoops, return next response dot JSON deleted chapter like that. Great. Let's go ahead and let's try this now. So I'm going to click delete. I'm going to click continue and you can see the buttons are disabled now and let's see if something's going to happen. So it says chapter deleted and I should get redirected. There we go. And you can see I only have one now. So let's attempt to delete this one as well. So I'm going to click delete. I'm going to click continue. Let's wait. Let's wait and see if it happens. Chapter deleted. And there we go. Now it says no chapters. Perfect. So now we have to create an API route for publishing the course. Sorry, the chapter. Let's go back inside of our chapter actions. So we can close everything now. And let's find chapter actions. So we have that inside of teacher, courses, course ID, chapters, chapter ID, underscore components right here. And we have this empty on click for the publish button. So let's go ahead and let's create that. So const on click is gonna be an asynchronous arrow function like this. Let's go ahead and open a try and cache block. And let's write toast, error, something went wrong like that. In the finally block, set is loading to be false. In the try block, set is loading is going to be true like that. And now let's go ahead and write if is published. We are going to do await axios.patch. open backticks slash api slash courses slash course id slash chapters slash chapter id like this slash unpublish right so this is going to be for unpublishing and then write toast.success chapter unpublished like that perfect else you can copy and paste this like that but we're gonna change it to write to slash publish and this is gonna say chapter published like that and let's copy this on click now and let's give it back here on this on click great so now let's go ahead and create a new chapter test publish like this. Let's create it. Let's go inside of it and let's go ahead and fill what we need. So this is a test information here. I'm going to go ahead and upload a video. So remember we cannot publish a chapter if we don't have the description, the chapter title and the chapter video. So you can see it says complete all fields two out of three. So let's wait for a second for this to refresh. There we go. As you can see my publish button is now available and if I click I'm going to get something went wrong because we have not created an API route for that. So let's go ahead and let's do that now. So I'm going to go back inside of my app API folder, courses, course ID, chapters, chapter ID, and I'm going to create a new folder called publish like that. And inside, I'm going to create route.ds file. So let's go ahead and let's write export asynchronous function patch, which has a request of type of request and it has params which are an object and which hold a course id which is a string and a chapter id which is a string as well. Let's open a try and catch block here. So we have an error. Let's do console log chapter publish like that and let's do an error and let's return new next response from next slash server internal error like that with a status 500. There we go. Let's go ahead and let's get the user ID from the out function using clerk next JS. So just make sure you have this imports of course. And now let's write if there is no user ID, let's return new next response unauthorized with a status of 401 like this. Great. So now let's go ahead and let's attempt to fetch our course. So const on course is await DB from at lib DB. So make sure you have this input as well. I'm just going to separate it. So await DB.course.findUnique where ID is params.courseID and user ID is matching. I'm just going to copy and paste this if clause here. And if there is no own course, we're going to return unauthorized like that. Perfect. So now let's attempt to find this chapter, which we are trying to publish. So cost chapter is going to be await db.chapter.findunique, where ID is params.chapterID, and courseID is params.courseID, like that. Now let's find the max data. So const max data await db.maxdata.findunique again, where chapter ID is forams.chapterID. All right, and now let's write if there is no chapter or if there is no max data or if there is no chapter.title or if there is no chapter.description or if there is no chapter.video URL. In any of those cases, we're gonna return new next response, missing required fields with a status of 400. So all of these are required in order for us to publish the chapter. Perfect. And now let's go ahead and let's write const published chapter to be await db.chapter.update where id is params chapter id sorry course id is params.course id like that and data is very simply is published true like this and return next response.json published chapter like that. Perfect. So let's go ahead and try this now. So I'm just going to refresh this. I'm going to click publish. It seems to be loading. It seems to be loading and it says chapter published. And let's see if this caused a refresh. It seemed like it didn't cause a refresh, but I think if I manually refresh, there we go. You can see that the banner is gone. So two things we have to do. we have to go back inside of our chapter actions. And in here, it looks like we forgot. Where is it? It's right here, right in the is published. So let's just at the end, add router dot refresh like this. So we forgot to do that. So once we publish or unpublish, we need to refetch the server component so that it's updated. But if I try to unpublish now, we're going to get an error because unpublish does not exist. So let's go ahead and create that route now. So we can actually reuse most of the stuff from the publish route. So I'm going to focus on that one here. And I'm going to go ahead and copy this publish folder, paste it inside of the chapter ID route. And I'm going to call it unpublish like this. And I'm going to go inside and it's pretty much gonna be the same, but we're not gonna need max data, so we can remove that, and we're not gonna need this check at all, right? Instead, what we're gonna do is we're just gonna check for if there is no own course, and we don't even need to check for this chapter like this, so we can remove that, and instead of publish chapter, it's just going to be unpublished chapter, because we're going to change this data is published to false like that and of course that's the chapter we're going to remove but it's not just that we're also going to have to check if the chapter we just unpublished was the only published chapter in this entire course which means that if we unpublish this chapter we also need to unpublish the entire course so let's also rename this error chapter unpublish and now let's write const published chapters in course so very similar to the thing we did in the delete function right so wait db.chapter.findmany where course id is params.course id and is published is true like that and let's go ahead and let's oh so just that all right and if there is no published chapters in length so like this so if there's none of them then await db.course.update where id is rams.courseid and data is is published false like this there we go so we have our unpublished function now let's try it out so I'm gonna go ahead and click unpublish here and let's see if that's gonna work there we go it says this chapter is unpublished it will not be visible in the course so let's you can see how that this now says it's a draft but if I go ahead and publish this chapter now and if I go back there we go now it says published which is exactly what we want So we completed the entire chapter creation process What we have to do now is we have to create equivalent actions but for publishing a course. And one more thing I want to show you, just if you haven't noticed. So we have a batch here which says published, right? But if I go ahead and select this free access and save this file. And then if I go back, you're going to see that now I also have a free badge right here. So just in case you haven't noticed that. Great. So now we're finally ready to create our actions to publish this course. And then we're going to create a table, which is going to display all of our courses together. Great, great job. So what we have to create now is the actions to publish our course. So we just finished the actions to publishing our chapter. And now let's do the same thing, but this time to publish the course itself. So let's go back inside of page.tsx, which is rendering the entire course. So I'm just going to expand my screen here like that. And we're going to go ahead inside of the app folder, inside of dashboard, routes, teacher, courses, course ID page.tsx. So right here. And inside where we have the completion text, we're going to go ahead and add a new constant like we did in the chapter page to be const is complete, which is just going to say required fields dot every and a boolean inside. So that way, we're going to know that all these fields are true. All right, and now let's go ahead and let's wrap this entire thing inside of a fragment so we can add a nice little banner. So same thing with it with the chapter field, right? Sorry, with the chapter ID page. And now inside, let's go ahead and let's add a conditional. So if course is not published, so I'm using an exclamation point right here, right? And let's go ahead and let's render a banner from add components slash banner. So just make sure that you have the banner imported and I'm just going to move it to the top with the other imports. All right, we have the banner. And now let's give it a label of whoops, let's give it a label of this course is unpublished like that it will not be visible to the students like that. So just make sure you have that. And let's take a look at how that looks like there. So it says this course is unpublished, it will not be visible to the students. Perfect. So let's go ahead now and let's create our actions component, right? So we're going to do that outside of this div right here. So I'm going to go ahead and write a comment, add actions here. So find this span, which says complete all fields, find the closing div. And then below that is where we're going to put our actions. So we can actually copy the actions from the chapters, chapter ID underscore components. So copy the chapter actions like that and paste it in the underscore components of the individual course ID. And inside, I'm just going to rename them to just actions like that. Let's go ahead and let's rename the instances of props and interfaces inside to just be actions like that. And let's go ahead inside of page right here. and let's just render it. So it's going to be actions like this and let's import it from dot slash underscore components actions. So make sure you have this import like that. And let's pass in the disabled prop, which is complete. I mean, exclamation point is complete, right? We're going to have a course ID, which is params dot course ID. and we're going to have is published, which is going to be course.is published like that. And now, as you can probably see, is that we are missing a prop saying chapter ID is missing, right? But we're not going to have that actually inside of our actions. So since we copied this from the chapter actions, that's why we have the chapter ID, but we actually don't need it. So make sure you're inside of course ID components, newly copied underscore components actions file and remove the chapter ID. We're not going to work with it. So you can go ahead and also change this API routes as well. So let me just expand this like this. So we're not going to go to slash chapters. We're immediately going to go to slash unpublish or slash publish. So this is going to be the entire route. Slash API, slash courses, course ID, unpublish, and courses, course ID, publish. And let's just change this messages to course unpublished or course published like that. Great. And now we also have to change this inside of the on delete. So it's just going to be a delete method like this. So just a course ID and we're going to say course deleted like that. And we're going to push not to the individual course ID, but to just slash courses like this. Great. So I think that's pretty much enough for us to already see these actions. There we go. And as you can see, the publish button is disabled because I didn't add all the fields, but let's try to delete one. There we go. We have a model and if I click continue, I should get an error because we don't have this page for deleting the course. So now let's go ahead and let's create this API route. So we already have a starting point, which is our route for updating the course. So let's go ahead and find that route. inside of the API courses course ID, and we have route.ts right here. So we have a patch route, and now we're going to create an equivalent delete route. But before we do that, I actually want to import mux because we're going to have to check if any of our chapters have existing mux data. So if we delete an entire course, we also have to delete the assets from the mux so we don't fill up for no reason. Right. So import mux from at mux slash mux dash node like that. And let's add a const video like this to be new mux. So new mux like this process dot environment dot mux underscore token underscore ID. And below that, we're going to have mux underscore token underscore secret. So whenever you're writing environment variables, please just double check them in your .environment file that they're matching like this. So max token ID and max token secret. You can copy and paste directly because sometimes typos can slip in. Great, now let's go ahead and let's write export asynchronous function delete, which has a request. And it also has the params like this. So you can just copy the params from the patch function. and let's open a try and catch block inside. So let's write console log course ID delete and an error like that. And let's return new next response internal error like this with a status of 500. Great. And now let's go ahead and let's extract the user ID from out, which we already have imported. If there is no user ID, return new next response unauthorized like that with a status of 401. Whoops. Let's see. So I'm right here. Okay. And now what we have to do is we have to fetch the course. So const course is await db.course.find unique, where id is params.courseid, and userid is params is userid. My apologies. All right. And we're going to include chapters, and inside of them, we're going to include max data, like this. All right. And now let's first check if we haven't managed to load this course at all. So if there is no course to be deleted, let's return new next response not found like this with a status of 401. Sorry, 404. All right. And now let's go ahead and let's write for const chapter of course.chapters. if chapter.muxdata?assetId await video.assets.delete chapter.muxdata.assetId like this. Great. So we have this cleanup function and now we can safely delete the course and all the muxdata is going to get deleted because we put ondelete cascade inside of the relation in Prisma. But we have to manually delete the assets from mux. So const deleted course is await db.course.delete, like that, where id is params.courseid, like that. Whoops. And what we have to do now is we have to return new next response.json deleted course. it's not new so it's just next response.json like this all right so i think that should do it so let's just go ahead and try so i'm going to delete this continue it's loading it's loading and let's see if it's going to redirect us back to slash courses it says course deleted and there we go we are redirected back so uh let's go ahead and try the new one now so advanced web development like this. And great, we have these actions right here. Perfect. So what we have to do now is we have to enable our publish button. So I'm just quickly going to do this. I'm going to add a description. I'm going to add the price like this. I'm going to select a category. So you can see that every time we add a new field, the completed fields increases, right? very cool. Let's go ahead and let's add an image like this. And then last thing that we have to do is we have to add a chapter which is going to be published. So I'm going to go and add a chapter, I'm going to write introduction like this. And as you can see, still, it's five out of six. But if we go inside of the chapter, and if we add the proper description to it, and if we add an actual max asset to it. So I going to upload this file and then we will be able to publish this chapter And then we going to have our publish button enabled inside the course actions So let's just wait for that. All right. Let's click publish for this chapter. There we go. This chapter is published. And now I can finally publish this. But if I click publish, we're going to get an error because we don't have a route to publish the courses. So now let's go ahead and let's create that one. Let's go back inside of the API folder. So we're right here, all right, in the course ID. And inside of the course ID, create a new folder called publish, like this. So the same way we did for the individual chapter. And let's add route.ds inside. Let's go ahead and let's start writing our patch function. So export asynchronous function patch is going to have a request, which is type of request. We're going to have params. which is going to hold the individual course ID, which is a string like that. Let's go ahead and open our try and catch block. Let's log this error. So console log, and we're going to write course ID publish like this with an error. and let's just return new next response from next server internal error like this with a status of 500. All right now let's extract our user id from the out so I imported clerk next js right here at the top and if we don't have the user id we can return new next response unauthorized like that with a status of 401. Great. So the thing we usually do, right? And now let's go ahead and let's fetch the course we were trying to publish. So for that, we're going to have to add our DB package. So import DB from atlib DB like that. And let's write const course to be await db.course.findunique where id is params.courseid and we have a user id matching and let's include like this chapters and let's include max data inside of each chapter like that let's first check if there is no course to be found in that case let's return new next response not found with the status status of 404 like that great now let's go ahead and let's check if we publish this uh course uh so sorry let's check if we have a published chapter so const has published chapter is going to be course not chapters dot some chapter and we're going to just search for chapter that is published. So at least one chapter needs to be published. That's where we are using some, right? If we wanted every chapter, we would write every, but we are using some, right? Because only one is needed. Great. So we have that. And now let's do our check if we can publish this course or not. So if there is no course.title, if there is no course.description, or if there is no course.imageURL or if there is no as published chapter, right? If there is no published chapter. And let's also add before that, if there is no course.categoryID. So we need all of these things. So course title, description, imageURL, categoryID and published chapter. So all of those are required, right? So if any of that is missing, return new next response missing required fields like that with the status of 401 like this. So required like this. All right. Otherwise, we are finally ready to publish the course. So const published course is going to be await db.course.update where id is params. Params.courseid and user id is matching. And data is very simply going to be is published finally to true. And then we can return next response.json published course like this. There we go. And while we are here, let's also create our unpublish function, right? So I'm just going to copy and paste this. And I'm going to create a new folder called unpublish. Like this and create a route.ts inside. And let's paste this entire thing here. So what we're going to do is we're just going to, we'll do the normal user ID here. And we can simplify this. So we don't have to include anything when finding this initial course, right? So we just want to find the course that we're trying to unpublish. If it doesn't exist, we can just write a 404. And we don't have to do these checks at all. We just have to write unpublished course. Because we are turning the isPublished to false, like this. And then we have to send back the unpublished course, like this. And let's change this error to unpublish, like this. So a much simpler version of what we just had, right? So what we did was we copied this function from publish, right? Which was a bit more complicated. We left the user ID authorization. We simplified finding of the individual course because we don't need to include individual chapters and their max data. So we simplified that. If we haven't managed to find the course using the ID and the user ID, we responded with the not found and 404. Otherwise, we updated the existing course setting is published to false. Whereas in the publish route, we set is published to true. So make sure this one is false and make sure you have unpublished in your errors. So it's easier for you. There we go. So I think we have all the routes we need now to test this out. So if I click on publish here, let's see if that's going to refresh this page by saying by hiding this banner. There we go. So we are officially published. And if I click unpublish, what should happen is I should get the banner back. Great, great job. So you officially finish this, but let's just try it out if it truly works, right? So I'm going to publish this course. So make sure your course is published. And now what we're going to do is we're going to go inside of a chapter and we're going to unpublish a chapter, right? So I want to see what happens now. What should happen is that our course is unpublished as well. There we go. Now our course is unpublished. Perfect. so that is officially working and if I publish my chapter again we're gonna have to manually publish our course again right I think that's good user experience great great great job one more thing I want to do is that when we click on publish and we publish the entire course so not the chapter but the entire course I want nice little confetti to throw I think that's I think it looks cool. So let's go ahead and do that now. So first thing I want to install is to stand. So let's get inside of the terminal here. And I'm going to write npm install to stand. So we're going to use that as our global state manager to trigger this confetti where wherever we want, because we're going to reuse it right also when when a student finishes a course. So install to stand and then run npm install react dash confetti like this. So let's just wait for that to install and then you can npm run dev your app again. And I'm going to close everything and I'm going to go ahead and create a new folder. So whenever I create a new folder, you know, I click on a file just so I'm focused outside of any of the folders. And then I create it like this. So it's outside of the app folder, cooks, right? And inside let's create use-confetti-store.ts, like that. So we're going to have that. and let's quickly import create from to stand. So from to stand like this, let's create an interface. I mean, a type confetti store, which is going to have is open to be a Boolean. And I think we need an equal sign here. We're going to have on open, which is just a void and on close, which is also just a void like this. And now let's export const use confetti store to be create in pointy brackets confetti store type, right? Let's go ahead and let's get the set function and let's immediately return an object where we're going to set is open to be false. On open is going to be a function which is going to call set and it's going to change is open to true. and on close, which is going to work very similarly, but it's going to set is open to false like that. So we're going to use this as our global controls for the confetti. Perfect. Now let's go ahead and let's go inside of the components folder inside of providers and create a new file called confetti-provider.tsx like this. Let's mark this as use client like that. Let's import React confetti from React dash confetti like that. And let's import use confetti store from adhooks use confetti store. And now expert const confetti provider like this. So just make sure this is marked as use client, right? Const confetti to be use confetti store. If there is no confetti is open. So if confetti is closed, we don't display anything. We just return null. Otherwise, we are going to render React Confetti. So it's just a self-closing tag like this. Let's give it a class name of pointer events none and Z index of 100. So it's just above all of our other elements. And let's give it a number of pieces to be 500. Let's give it a recycle option of false. and let's write on confetti complete to be an arrow function, which very simply just calls confetti on close. So we're going to call that little action inside of our confetti store to close itself like that. Perfect. So we have that. And now we just have to add the confetti provider inside of our app folder inside of our layout.tsx. So let's go ahead and let's add that above the those provided, for example, confetti provider like this. So make sure you import\nConfetti provider from ads slash components providers. Confetti provider like this. And now where I want to go is inside of actions.tsx. So not our chapter actions, but our course actions. So that's located in routes. So in dashboard, routes, teacher, courses, course ID, underscore components, actions right here. And what I want to do is inside of this publishing thing. So here where we say course.ispublished, I want to add the confetti store first. So let's do that. Const confetti is equal use confetti store like that. So make sure you import this from adhooks use confetti store. And now when you have the confetti, so here where we write course published, we're going to write confetti dot on open like that. So just make sure that that's here where we await a publish action, right? And let's try that out. So I'm just going to refresh this entire page. let's wait let's wait first thing I'm gonna have to do is I'm gonna have to unpublish the course so let me unpublish it and that should have no confetti and it should have this banner correct and then when I click publish what should happen is a nice little confetti and there we go we have a beautiful confetti here amazing amazing job you finished the entire course creation we are finally ready to modify this courses page to not just be a plain button saying new course, but instead a useful table where we can see as a teacher all of our courses. Great, great job. So first thing we're going to have to do is install and set up our data table. So I'm in Shadzian UI, I clicked on components here and in the sidebar I found data table right here. So let's take a look at everything we have to do to install this. So first thing we have to do is we have to add the regular table inside of our project. So I'm going to copy this command and I'm going to go ahead inside of my terminal and I'm going to paste npx chat cn-ui at latest add table. So just go ahead and confirm this installation. Next thing we're going to have to do is install the dependency 10 stack react table. So copy this like that. And let's go ahead and paste it. So npm install at 10 stack slash react dash table like this. Great. So once we have this, what we're going to have to do is we're going to have to set up our project structure. So we're going to have columns, data table and a page dot TSX. So first thing I want to do is I want to create this dummy columns right here. So scroll to columns definition and just copy this. And now let's find where we're going to do this. So I'm going to run my app like that. And I'm going to go ahead and I'm going to go back inside of our page where we only render a button to create a new course. So that's inside dashboard routes, teacher courses right here, where we render a button new course. So let's go inside of this folder and create underscore components here. And let's create a new file called columns.tsx like this. And we can paste this inside. So we are going to mark this as use client, we're going to import column depth from n10 stack slash react table like this, I'm going to remove this, we're going to have a type payment here. So we're going to obviously change everything here. But this is just so we have something in our columns like that. Great. Next thing we have to do is we have to create a data table component. So go to step two and find the data table component. And let's copy this entire thing. And let's go ahead inside of this underscore components and create a new file data dash table dot DSX like this and paste it inside. So the entire data table component like that. Great. Now that we have that, we're going to have to render that inside of our page.dsx like this. So I'm going to go ahead. So this is where we're going to do that in this page where we have rendering our button, right? So just for demonstration, I'm going to copy this asynchronous function, get data, right? From this snippet. So this is what it's going to look like. So just like this. And I'm going to change this to any because it really doesn't matter, right? Let's save this. what we're going to have to do next is we're going to have to add this component data table in here. So I'm going to go ahead and copy this line. And I'm going to change my button. So I'm going to remove my button entirely and link like this. And I'm going to render the data table. So let's import data table from dot slash underscore components data table. And we can remove these two imports now like this. And we're also going to have to import the columns. So let's go ahead and let's import columns from dot slash underscore components columns, because we have both of those inside of our underscore components like that. And the last thing we have to do is get this dummy data. So let's mark this courses page as asynchronous function. And let's just write const data to be await get data, which we wrote right here. So it's a dummy function, right? So now if you go ahead and refresh this page, just make sure you have your app running. You should see a table with some dummy data. There we go. So you can see we have some dummy information right here. So now we're going to modify this to actually fetch our data. So the first thing I want to do is I want to go inside of the columns and modify it a little bit like that. So we're not going to need this export type payment at all. We can remove that. Instead, inside of our column depth, we're going to work with a course type from Prisma client like this. And it's going to be an array, of course. The first thing we're going to add is accessor key title. And the header is just going to be a title like this. After that, we're going to go ahead and render the price. So this is going to be price and header is going to be price like this. Then we're going to go ahead and we're going to set the accessor key to is published. and we're going to have a header published like this. And the last thing we're going to add is our actions to edit the individual course, but we're going to do that later. So let's just leave it like this for now. And now let's go back inside of our page.tsx and we can remove this get data. So we no longer need it. Instead, we're going to work with courses. So in order to do that, first thing I want to do is get the user ID. So const user ID from out from clerk next JS. And I'm just going to move this to the top like that. Then I'm going to write if there is no user ID, we're going to return redirect from next slash navigation to back like that. So let's just move it here to the top. And then let's go ahead and let's attempt to fetch our course. So const courses is going to be await db.course.findmany like this. And we don't have db. So let's import DB as well. So I'm going to do that here. Import DB from add slash lib slash DB like this. All right. And let's write where we have a matching user ID. And let's order by created at descending like this. All right. And now we can use those courses to pass in as data like that. And there we go. You can see I have all of my courses inside. So advanced web development with a price, published, which is true, published, which is false, all of these things which we are going to use. Great. So what we have to do now is go back inside of the data table here. And let's take a look at everything we're going to do here. So I'm going to go scroll all the way down. And first thing I want to add is pagination. So scroll inside of this data table documentation to find the pagination. Right. And let's see what we have to do. We have to go inside of our data table and we have to add get pagination row model. So let's go ahead and add that. So I'm just going to keep this side by side. And I'm going to go inside of my data table component. And right here, after get core row model, I'm going to add get pagination row model. Great. Next thing I have to do is I have to add that to the use react table hook right here. So I'm going to copy this line, which is highlighted here. And let's go ahead and find that. So it's right here. So below get core role model, I added this get pagination role model with get pagination role model and I save that. All right. So now we have to wrap our entire component inside of a div. So let's do that now. So I'm going to wrap this in a div and go all the way down and wrap it in a div. And I'm going to go ahead and indent that. All right. and now what we have to do is we have to find the end of this table so right here at the bottom and we have to add these two new components right here so let me just expand this like that and you can copy this entire highlighted field right so everything here to the end of this div right here just don't copy this div as well so that's duplicated because we already added it right here So just this div, which encapsulates two buttons inside. So let's go ahead and paste that in here. And what we have to do is we have to import the button from add slash components UI button. So just make sure you add this import as well. And if you save, there we go. Now we have pagination ready, right? It's not working currently because we don't have enough courses inside to actually enable the pagination. Great. So we added that. Now let's scroll even further. And let's make our header cell sortable, right? So we have to go inside of our columns.tsx, where we have the accessor key and the title. And what we have to do, where is it? Whoops, sorting. Whoops, so I got ahead of myself. So first thing, we are going to do something in the columns. But first, we have to go back inside of data table. And let's import everything as React right here, like that. So we do that. Then we add a sorting state after the column def. Then we add getSortedRowModel below getPaginationRowModel. Then we add this state for sorting and setSorting inside of the component above the useReact table like this. So sorting and setSorting from react.useState like this. All right, we have that. And now we have to go ahead and we have to add this too. So on sorting change and get sorting role model. So I'm going to copy this too. And I going to paste it after get pagination role model like that So now we have set sorting get started role model and we have a state like this Great And as you can see that all we have to do inside of the data table component And now we have to go back inside of our columns right here. And let's go ahead now and let's import these two icons here from Lucid React. So arrow up and more horizontal from Lucid React. Let's go ahead and let's import a button component from add slash components UI button. like this. And let's go ahead and let's modify this header to be a function like this. So we're going to extract the column inside. And we're going to return a function. And we're going to return a JSX element, which is going to be a button with a variant. So it's going to be a button with a variant of ghost like that. And on click is going to be an arrow function, which is going to go column dot toggle sorting like this. Let's see if that's all it takes. Oh, so we also have to pass in column get is sorted is equal to ascending like this inside of that function. So let me expand that so you can see it in one line. There we go. So button on click column, which we extract from the header, toggle sorting, and inside we pass column get is sorted is equal to ascending like this. Great. and now that we have that let's see what else we have to do so we did this we are working uh where is it uh right here okay and then inside of that button we're gonna render uh a title text right and then we're gonna render arrow up down from lucid react and let's give it a class name of ml-2, h-4, and w-4 like this. Or you could just basically copy the header and then just change the email to title, right? So we just did that step by step here. Perfect. And now let's take a look at how that looks. So right here, there we go. You can see the title is now sortable, right? So I can sort by alphabet right here. Perfect. And now let's do the same thing for price and for published, right? So I'm going to go ahead here and I'm going to copy this header component and I'm going to change the price to do the same thing like that. And let's change this to be price. And let's do the same thing for published. And let's change this to be published like this. There we go. So you can see that now the price is sortable. So you can go by empty first or we can go by highest first, right? And same thing is published. We can sort by false and we can sort by true. Perfect. So we have enabled sorting. And one last thing that I'm going to add is our actions, right? So we don't have to do this visibility. I'm pretty sure we are done with the entire data table. So we can close this now. And now we're going to add a third action here to actually trigger our actions to edit a specific course. So let's go back inside of our columns. And before we do that, we're gonna have to install another ShatCN package called dropdown menu. So let's go inside of our terminal and let's write npx ShatCN-UI at latest, add dropdown-menu like this. Go ahead and confirm this installation. and then you can go ahead and npm run dev your app again. All right. So let's go ahead and we can remove the, well, actually let's leave more horizontal. Let's leave it here. And let's go ahead and let's import drop down menu, drop down menu content, drop down menu item and drop down menu trigger from add slash components slash UI slash drop down menu like this. Great. Now let's go all the way to the end where we have our is published. And what we're going to do is we're going to go ahead and add a new one. So like this, open a new object and give it an ID of actions like that and give it a cell like this. to be a row, open a function, and inside extract the ID from row.original. So this is going to be a course ID, right? And let's return a dropdown menu inside dropdown menu trigger, which is going to have a prop as a child inside we're going to render a button which we already have imported we're going to write a span where we're going to say open menu but this is only going to be for screen readers so class name sr only so a little accessibility on our side here let's give it a variant of ghost and the class name of h-8 w-8 and padding zero so this is a class name for the button right and below the span inside of the button we're going to render more horizontal which we have imported from lucid react like this now outside of the drop down trigger let's open drop down menu content like this let's give it an align prop of end let's give it an end tag so and the drop the menu content and inside we're going to add a link component from next slash link. So make sure you import that from next slash link. I'm just going to move it to the top. All right. And let's go all the way back down. And let's go ahead and let's render a drop down menu item, which we have imported. And let's just render a pencil icon from lucid react. So make sure you imported the pencil icon. All right, let's give it a class name of h-4, w-4 and margin write to like that. And let's write edit. And let's give this one an href to be backticks slash teacher slash courses slash the ID, which we extracted right here from the original. And let's just see what's going on with the pencil. Oh, it looks like I did a mistake here. So yours is probably working. So pencil from Lucid React. All right. So now we should have the actions. So let me just refresh this part right here. Let's just wait a second for this to load. And there we go. We now have the actions right here. Perfect. Let's see if we can modify this a bit right here. So I'm going to go ahead and find this more horizontal right here. And let's give it a class name of h-4 and w-4, right? There we go. So I want them to be much smaller like this. And we have an edit. And if I click on edit, I should get redirected to that specific course. And I am. Perfect. So now from here, we can easily manage our courses individually. Great. So what I want to modify now is I want to change this price to be formatted. So it has a dollar sign. And I want to change this published to be in a badge, right? Instead of a true and false. So a bit of a prettier view. All right, so let's go ahead and let's do the price first. So in here, we have a header ready. And after the header, let's add a sell option. And we're going to extract the row here. So I'm working this inside of, I'm working this inside of the is published, right? So fine, make sure you're doing it here inside of that object. And let's write const is published. The row dot get value is published. like that, and give it a default value of false, just in case it's undefined. And then let's write return badge from add slash components UI badge. So just make sure you imported the badge like I did here. All right. And inside, go ahead and conditional render is published, question mark, published. Otherwise, draft. And go ahead and add a class name to the badge. let's add cn from at slash lib slash utils. All right, let's just open parenthesis. Let's give it a default value of bg-slate-500. And then let's go ahead and let's write is published and end bg-sky-700. And I think I did another thing here. Yeah, my apologies. So this is just supposed to be cn. All right. and if you save that there we go so now it looks much nicer right we can see which is a draft and which one is published perfect and now let's do the same thing for formatting our price here so let's go ahead and find the cell for price there we go so accessor key is price we have a header and below the header again add a cell option go ahead and extract the row like this find the price So const price is equal to parse float like this row dot get value price or is going to be a string zero like that. Then let's format this price. So const formatted is going to be new intl.numberformat and dash US style currency. Currency is going to be US dollars dot format and pass in the price. and then what we have to do is just return a div with price inside sorry with formatted inside like this and there we go you can see how now we have a nice price here set perfect you can see how we can sort by everything here we have nice little badges here so everything we need for a functional table, right? And you can also go ahead and try and delete one. So let me just refresh this entire page. And let's go ahead and I'm going to delete a draft one, right? So I'm just going to delete it like this. And I should now get redirected back. And this one test one to three should be gone from the table. And there we go, we cannot see it anymore, you can see how one is missing. What I want to do now is I want to modify my data table to also search right And to create a new course So what we have to do is we actually have to go back to ShatCN UI So back inside of the data table in ShatCN because I forgot the filtering aspect And let's go ahead and let's find filtering right here. Perfect. So let's go ahead and let's import column filter state. So I'm going to collapse this and let's find our data table back. So after column def, let's add column filters state like this. Let's add get filter the row model below the get core row model like that. Let's go ahead and let's import our input component just below the button like this. Perfect. Let's add a new state for column filters below our sorting state like this. So column filters and set column filters. So this is what it looks like in one line. oh whoops so let me just zoom this in and i'm just going to collapse this because i want it to be in one line there we go so column filters and set column filters use state column filter state and an empty array or i mean it's exactly what i'm copying from this tutorial where it says to update the table right perfect now we have to go ahead and add something to our use react table function here. So let's go ahead and let's add on columns filter change. So just copy that from here after the get sorted row model like that. And then get filtered row model also pasted inside. And then in the state, we also have to add column filters like this. Perfect. And now let's go ahead and let's just copy this entire div and paste it inside like this. So right here, we paste that. there we go so let's go ahead and refresh and there we go but the only problem is it says filter emails right so i just want to change that to say filter courses like this and the value is going to be a title get filter value and inside on change we also change this to title so we're going to search for title so if i go to web development there we go now it's just showing web development And if I go with one, two, three, it's just showing this edited title, one, two, three. If you're having any troubles with this, you can always visit my source code and just copy the entire data table component, right? So one thing I wanna add when we already have this div here is outside of this, so still inside of this div, but outside of the input, add a new link component from next slash link and add a closing component like this. so make sure you imported the link from next slash link i'm just going to move it to the top like that and let's go ahead and let's render a button inside with an icon plus circle from lucid react so make sure you import that plus circle from lucid react i'm just going to move it to the top with the others all right and let's give it a class name of h-4 w-4 and margin right of two like that. Let's give this link an href of slash teacher slash create like that. And let's also write new course like this. And let's change this div, which encapsulates both the input and the link with a button to also be justify dash between like this. So they are all on their own side. Perfect. So now I can click new course here. And there we go. If I go, this should be in the table. and create this course like that. It says course created. I'm going back and let's just refresh. And there we go. This should be in the table. And from here, I can edit it back. I can rename it to, I don't know, test edit like this. And I can save this. Let's save. I can go back to courses. Now it says test edit. Perfect. I can go back. I can delete it. And now I'm going to get redirected to the table. And that is nowhere to be found. Perfect. So we finished the entire displaying of our courses. What we're going to do now is we're finally going to display this for our students in the browse or the search page right here. Great, great job. Let's continue and let's develop this search page right here. So the first thing I want to add are little crumbs of categories right here that user can click on and then filter the courses by that category. So in order to do that, we have to install two packages before we start. So let's go inside of the terminal and let's write npm install qs, sorry, query-string like this, but we are going to import it as qs, so my apologies for that. So just do npm install query-string and npm install react-icons like this. Great. after that is installed, you can go ahead and run dev your application again and just refresh your localhost if you shut down your app. Perfect. So I'm going to go ahead and clear everything here. And I'm going to go inside of the app folder, dashboard routes, search right here. And I'm going to go in the page.dsx. So let's go ahead and let's mark this as a synchronous function like that. And let's go ahead and let's write const categories to be await DB. So import DB from add slash lib DB dot category dot find many like that. And let's just order by name ascending like that. Perfect. So now we have our categories. And what we have to do next is we have to create a little component, we're just going to handle these categories, right? So before we do that, let's give this div a class name of p-6 like that. So we give it some spacing and then let's write categories like this. So categories like that. If we save, of course, we're going to get an error, but let's just pass in the items to be categories like that. Perfect. So now let's go ahead and inside of this search folder, create a new folder, underscore components, like that. And inside, create a file, categories, dot dsx. Let's go ahead and let's mark this as use client and let's export const categories like this. And let's return a div saying categories like that. So let's go back inside of our search page. And now we can import that from dot slash components categories. Perfect. We no longer have an error and we have a text saying categories here. Now let's go ahead and let's create an interface. So interface categories props is going to have items, which is a type of category from Prisma client and it's an array. And now let's go ahead and let's map that here. So category props and let's extract the items from the props like that. Perfect. So now let's go ahead and let's import the icons we're going to need from the React icons because I want each category to have a nice icon. So let's go ahead and let's import. So you can prepare from React-Icon slash FC like this. Let's import FC engineering. Let's input FC film reel. Let's import FC multiple devices, FC music, FC old time camera, FC sales performance, FC sports mode like this. So this right here, perfect. And now what I want to do is I want to create an icon map. So const icon map is going to be a record, which is going to have a category option, but a specific field inside of a category called name. And it's going to return an icon type from Lucid React. So let me just go ahead and import that from, sorry, not Lucid React, from React icons. So icon type from React-icons, like this. Now we can go ahead and open this and let's write, if it's music, it's going to be FC music like this. So let's just go ahead and add the equal sign here. I forgot to do that. So we can copy and paste this. If it's photography, in that case, it's going to be FC old time camera. If it's fitness, it's going to be FC sports mode. If it is accounting, it's going to be FC sales performance, like that. If it's computer science, it's going to FC multiple devices. If it's filming, it's going to be FC film reel. And if it's engineering, it's going to be FC engineering, like that. and let's just add a column here. There we go. Perfect. So just make sure that this names, which I've written here, so only this in annotations, right? The icons are fine, but music, photography, fitness, those things need to match what you have in your script seed.ps. So computer science, music, fitness, photography, accounting, make sure it matches that right here. Otherwise, your category is not going to have an icon. Perfect. Now let's go ahead And let's give this div a class name of plexitems-center, gap-x-2, overflow-x-auto, and padding bottom of 2. Like this. Perfect. Now inside, let's iterate over our items. So items.map, individual item. And let's return category item like this. So if we save, we're going to get an error. But let's already prepare the props. So key is item.id. Label is item.name. Icon is going to be icon map. And we're going to pick by name. So item.name. And value is going to be item.id. Like that. Perfect. So now let's go ahead and let's create a new component called category-item inside of the underscore components where we already have the categories.tsx like this. And let's go ahead and let's mark this as useClient. And let's export const categoryItem like this. And let return a this same categoryItem So now we can go back to categories and you can import category item from category item like that I'm just going to separate it. There we go. And now we can see how we have repeating category item here. So now we're going to modify it to actually be a nice little batch. So first let's do an interface for it. So interface category item props is going to have a label, which is a string is going to have optional value, which is a string and optional icon, which is icon type from react icons. So let's import icon type from react icons like that. Perfect. All right. And now let's go ahead and let's extract those. So let's map them to category item props and let's extract the label value and icon and let's remap the icon to icon with a capital I so we can use it as a component inside. Perfect. So let's go ahead and instead of rendering a div let's render a native HTML button element like that and inside let's go ahead and write if we have an icon let's go ahead and let's render that icon like this and let's give it a class name. sorry let's give it a size of 20 like that there we go you can see how each of my category now has an icon here perfect and below that let's just write a div and let's write a label there we go and let's give this label a class name of truncate like that and now let's go ahead and give this button a class name so it's going to be cn so import cn from lib utils like this i'm just going to separate those all right and let's go ahead and open this and let's give you default classes of padding y2 padding right padding x3 like this text dash small border border dash slate dash 200 rounded dash full like that flex items dash center gap dash x dash one on the hover we going to have border-sky-700, and we're going to have a transition class like that. And then add, well, we cannot add anything yet, because we didn't create any functionality for it. But now we're going to create selectors, which are going to read which category is active. And then we're going to be able to change this button style if it's active. So I'm going to write a comment for now saying change style if active. And I'm just going to prefix it with to do so we don't forget it. And let's also give this button a type of button like that. And there we go. Now you can see how I have my, you can see how I have my categories here. So if you don't have them, or if they don't have icons, it's probably a culprit inside of your categories right here. So just confirm that your icon map has the exact same names that you have in your database, right? So you can confirm that in this seed script right here. So just make sure it matches, right? Perfect. So now that we have that, let's go ahead and let's add some functionality when we click on this category item. So first thing I want to do is I want to add a couple of hooks. So const path name is going to be use path name from next navigation. So make sure you just add that. I'm going to move it to the top. We're also going to have use router from it. So let's import that while we are here. And then we can write const router to be use router. All right. And let's also get search params. So const search params to be use search params from next navigation as well. So we have three items from use search params like that. Great. And now let's go ahead and let's write const current category ID to be search params dot get category ID. So this is the current category which is selected if there is one. And below that we're going to have current title. So current title is going to be another thing that we're going to store inside of our URL. But right now, it seems like a mystery to what it is. Well, in the future, we're going to create a search input for the courses. And we're going to store what the user searches inside of the URL. So if we change the category, we don't want to accidentally remove what the user searched for. So that's why we're going to preserve it. But in order to preserve it, we first need to fetch it. So search params. Whoops. So search params. dot get title like this great and now const is selected is going to be current category id is identical to value like that perfect so let's go ahead now and let's write const on click which is going to get the url stringified so in order to do that we have to import the package query string. So import qs from query string like that. And then we can create a stringify URL, const URL to be qs.stringifyurl like that. URL is going to be the current path name, which we have from the hook. And the query is going to be, well, first we have to preserve the title. So title is going to be current title. And then we're going to select the category ID. But if a user has clicked on a category which is already selected, we're gonna assume that the user wants to remove that category from being selected. So we're gonna create a kind of like a toggle functionality here. So is selected, in that case, null. Otherwise, we add a new category, like that. And let's go ahead and add some properties here to skip null and to skip empty string to be true, like that. And then we can write router.push URL like this. Perfect. And now we can go back inside of this button and let's give it an onclick function of onclick. So this function which we created right here. And now we can remove this to do and we can actually write if is selected. in that case change the styling to border-sky-700 bg-sky-200-20 and text-sky-800 like this. So let me just zoom that out so you can see there we go and let's take a look at that now. So if I click on engineering there we go. So if you look at my url I now have a query of my category id and engineering is selected here. So even if I refresh, since that is in my URL, it stays selected. And we're going to use that to query the courses, which are going to be filled right here. And you can try it out. You can change in different category. And if you click again, there we go. Now it's removed. Perfect. Great, great job. Now let's go ahead and let's create the search input right here in the navbar. So I'm going to go ahead and I'm going to find a component called navbar routes. So let's go inside of our components folder. And in here we have navbar dash routes like this. So in here, we have two of this constants is teacher page and is player page. Let's add another one called const is search page, which is going to be path name question mark includes. Actually, we can just do path name is identical to slash search. There we go. And now that we have that, let's go ahead and let's wrap our entire project here inside of a fragment like this. and now let's write if is search page let's go ahead and let's render a div and inside let's render a search input like this and if we save we are going to get an error because search input is not defined but before we add it let's just give this parent div a class name of hidden on small devices, but on medium devices, it's going to be block. Perfect. Now it's time for us to create this search input. So let's go inside of this components folder and create a new file, search dash input dot dsx. Let's mark it as use client. And let's export const search input like this. And just return a div saying search input. Now we can go back to navbar routes and we can import from the same component just by doing dot slash search input like that. And there we go. And if you expand a bit, so you are in desktop mode, you're going to see a text search input in the navigation bar. So while you're developing this, just zoom out or expand your screen so you can see the search input while you're developing it. All right, let's go inside of the search input. And before we start doing anything here, I actually want to create a hook called use the So let's go ahead inside of our hooks folder and create a new file, use-debounce.ds, like that. Let's go ahead and let's import useEffect and useState from React. And let's go ahead and write exportConst, useDebounce, sorry, exportFunction, useDebounce, which is going to be a T generic like that. it's going to accept a value, which is also a T, and a delay, which is an optional number like that. And it's going to return the very same generic. Let's go ahead now and let's write a state, debounced value, set debounced value to be use state and passing the value. And use state is going to have a type of T as well. Now let's go ahead and write use effect. let's give it an empty dependency array for now. And let's write const timer to be setTimeout, an arrow function where we say setDebouncedValue to be value. And the amount the timeout is going to take is going to be a delay, which we either pass as props here, or we're going to default to 500 by adding a pipe pipe 500 like that. or you can use, you can write it like this so it makes more sense. There we go. So now you can see it without zooming out. All right. And now we have to clear this timeout to prevent any overflow. So in the mount, unmount function, which is the return function in use effect, we're going to say clear timeout\ntimer like that. And in the dependency array, let's go ahead and let's add a value and delay like that. And all we have to do is return the bounced value like that. There we go. So we have our function, which is going to debounce our search. So why are we writing that? Well, we don't want to exhaust our database with queries for every single keystroke that the user writes in the search input. We want to delay it or we want to debounce it. So only when the user stopped typing for at least half a second, we're going to consider that as if user finished writing what they want, right? So that's how we're going to optimize so we don't have too many database queries. Now let's go inside of this search input right here and let's go ahead and let's style it first. So I'm going to give it a class name of relative. And I'm going to go ahead and I'm going to render a search icon from Lucid React. So make sure you add this import right here. Give this search icon a class name of h-4, w-4, absolute, top-3, left-3, and text-slate-600 like that. Now below that, let's add an input from .slashtui input. and I'm just going to replace this import to use components because I like it more like that. And let's go ahead and let's give this input a class name of W-Fool. On medium, it's going to be W-OpenSquareBrackets and hard-coded to 300 pixels. Let's give it a padding left of nine so our search icon fits inside like that. Let's give it a rounded-Fool property. Let's write bg-slate-100, focus-visible, ring-slate-200, like that. And a placeholder is going to be search for a course, like this. And there we go. Let's take a look at how that looks now. You can see in my navbar, I have a nice little search bar right here. Perfect. So now we're going to have to write some functionality that when the debounce ends, it adds it to the URL. So let's go and do that now. So I'm going to go ahead and I'm going to write, I'm going to add a couple of states, couple of hooks here. So const value set value to be use state. And it's going to be empty in the beginning like this. Let's go ahead and let's add debounced value. So const debounced is going to be use debounced from at slash hooks use debounced and passing the value from this state right here. So I'm just going to separate this imports like that. Let's go ahead and let's add search params. So const search params are going to be use search params from next slash navigation. Okay. And let's also import use router. And let's also import use path name like that. So three of those from next navigation. And now we can add const router, use router. and const path name to be use path name as well. Perfect. And now let's go ahead and let's get the current category ID so we don't accidentally override it. So const current category ID is search params, search params.get category ID. I like that. And now let's write a use effect here. So use effect, which we are going to import from React. Let's go ahead and let's add the empty dependency array here. All right. And let's write const URL is QS, which we have to import. So let's add import QS from query dash string like that. And let's write QS dot stringify URL like that. The current URL is the current path name. The query is going to be category ID. It's going to be current category ID like that. And title is going to be the debounced value like that. And let's give it the options of skip empty string to be true and skip null to be true as well. And then let's do router.push URL like that. And now in the dependency array, let's add the debounced value, the current category ID, the router, and the path name. like that great and now let's go back to our input let's give it a value of value and let's give it an on change to take the event and calls the set value with event dot target dot value like this great and now let's go ahead and let's try and search for something and after 500 milliseconds, I have a new thing inside of my URL, which says title equals something. And if I try and click on a category, I now have both the title something and I have the category ID. And if I change my title, now the category ID is preserved and I have a title. So we can now combine queries with both the category type and the type. Perfect. And one more thing I want to do is I want to enable showing the search bar on mobile mode because right now we have no way of searching on mobile. So in order to do that, let's go and find the page, the search page. So app dashboard routes search page right here. Let's go ahead and let's mark all of this as a fragment like that. And let's go ahead and let's give this a div with a class name of px6 like that and tt-6. six. On medium, it's going to be hidden. On medium, it's going to have a margin bottom of zero and also a block on small devices. And inside, let's render the search input from add slash components slash search dash input like that. So I'm just going to separate these two like that. And there we go. You can see now on mobile mode, I have the exactly the same working search, right? But if I go ahead and expand my screen, it's back in the nav bar. Perfect. So now we are finally ready to use these queries to fetch our courses and load them right here. Great job. Now let's finally go ahead and let's show our courses with the thumbnails right here. So first thing I want to do is I want to create a function called get progress. So since we already have our user progress in our schema, we can already start adding it in our queries to combine with the courses, right? Because if user has purchased a course, we don't want to display the price of that course. Instead, we want to display the progress that user has made on that course, even if that is 0%. And otherwise, we're going to display the price of the course. So first thing I'm going to do is I'm going to go and create a new folder. So I'm just going to click on a random file. And I create a new folder called actions like that. And inside, I'm going to create a new file called get dash progress dot DSX like this. Perfect. And let's go ahead and let's import the database from s slash lib db and now let's write export const get progress which is going to be in a synchronous function which takes in the user id which is a string it oops which is a string course id which is a string like that and it returns a promise with a number inside right because it's going to be the percentage progress. Now let's open a try and catch block and let's resolve the error one first, just like we're doing our APIs. And let's go ahead and write console log and I'm just going to go ahead and write get progress and I will log the error here and I'm just going to return default to zero like this. Great. And now let's go ahead and let's get all of the published chapters for this course. So const published chapters is going to be await db.chapter.findmany like that, where course ID is course ID, is published is true like that. And we are just going to select the ID from them because that's all we need. Now let's go ahead and let's create an array of those IDs. So const published chapter IDs is going to be published chapters dot map. We're going to get the individual chapter and we're just going to return chapter dot ID like this. Perfect. Now let's go ahead and let's write const valid completed chapters to be await db dot user progress dot count. like this, where user ID is equal to user ID. So we're looking for progress of this logged in user. And we're going to load all chapters from this list of published chapters, right? So chapter ID is going to be in published chapter IDs like that. And we're also going to add an important query is completed to be true like that. Perfect. And then we can calculate the progress percentage. So const progress percentage is going to be valid completed chapters divided by published chapter IDs dot length. So let me just zoom this out. And we're going to multiply that by 100 like this. And then we're just going to return progress percentage like this. So we're going to revisit this later when we actually purchase a course, but we have to write it now because we're going to use it inside of another action, which I'm going to create now, which is going to be called get courses, which is going to load the courses for this page. So in the same folder, create a new file called get-courses.ts. And this was supposed to be a .ts file, not tsx, like this. So go inside of get courses now and let's go ahead and let's import category and course from at Prisma. Slash client like that. Let import get progress from dot slash get progress But I going to change it to add slash actions get progress like that And let also import database from add slash lib database like that Let's create a type, a custom type, which is going to combine each progress, sorry, each course with the current progress that user has made inside of that course. So type course with progress with category. So that's going to be a type of course. And inside, it's going to have a category, which is either going to be a category or null like this. Chapters are going to be ID, string, and an array. And progress is going to be a number or null like that. Now let's create a type for this function to accept. So type getCourses function like that. it's going to have, it's going to accept a user ID, which is a string, it's going to accept an optional title if we want to search for something, and an optional category ID if we want to search by category ID, like that. And now we can finally write our function get courses. So export const get courses is going to be an asynchronous arrow function. And let's extract the user ID, title and category id and let's map that to get courses like this let's go ahead and let's return this and let's also write the promise type which we expect so we expect a promise to be returned and we expect course with progress with category to be returned in an array like that perfect so let's open a try and catch block inside. Let's get the error like this. Let's go ahead and let's console log get courses. And let's add the error like this. And let's just go ahead and return an empty array. So if something goes wrong, we're just not going to load any courses. And now let's go ahead and let's fetch the courses. So const courses is await db.course.findmany like that, where is published is true. So we are only going to load courses which are published. We're going to write title contains title like this. So if we search for something, we're going to attempt to query by that. And we're also going to query by category ID like that. Perfect. Now let's write our include options here. So category is going to be true. Chapters is going to be true, but only those that are published. So where is published is true like that. And we're only going to select the ID from those chapters. We don't need anything else. Besides chapters, we're also going to include purchases in here, but only the purchase of this logged in user. So we're going to match user ID like that. Perfect. And now let's also just order them. So order by created at to be descending like that. Perfect. And one thing that I'm quickly interested in because I cannot remember if we did or not. So this title contains query is only going to work if we properly wrote our Prisma schema. So let's quickly revisit it just so I check if we did that or not. So let's go ahead. And it looks like we are missing some stuff here. So we need to enable full text search and we need to add an index to the course title in order to search it in MySQL. So let's go ahead and let's find this generator client here. And below the provider, go ahead and add preview features. So preview features. And it's an array of strings. And we're going to add full text search. and we're going to add full text index. So two of those like that. And now let's go inside of the course right here. And after the add, add index for category ID, let's add add, add full text array title like that. Perfect. And now let's go ahead inside of our terminal, shut down the app and run npxprisma generate like this and run npxprisma db push. So we modified our schema. So our courses are now searchable using title because we added an index. Perfect. And you can do npm run dev again and just refresh your local host. Perfect. So make sure you do that change in schema Prisma. Let's go back inside of our get courses function. And now what I'm going to write, once I have this courses and all of the included relations, let's write const courses with progress and that is going to be a type of course with progress with category and it's going to be an array so let's write await promise.all whoops so just add uh uh equal sign here so await promise.all and go ahead and open and write courses.map go ahead and open a function asynchronous course like that and now first we're going to check if the course purchases doesn't exist so if we never purchase this course we're going to display the price and not the user progress so if course dot purchases dot length is zero we're going to write, sorry, we're going to return the course, but progress is not going to be zero. Instead, it's going to be null. So we know not to display it, right? So if it's zero, we're going to display zero. But now we're going to hide it from the cart. Perfect. Now let's write const progress percentage to be await get progress from user id and course.id. Like that. Perfect. And now we can return, spread the course, and assign the progress to be progress percentage like that. Perfect. And now that we have that, let's just go ahead and let's return courses with progress like that. There we go. So we created an entire function, which is going to append progress that user has made in a specific course. And now we can go back inside of our page.tsx for search. So app dashboard routes, search page.tsx right here. and after categories let's add const courses we await get courses from add action get courses and i'm just going to move it with this three right here and let's go ahead now and let's also add the user id so const user id from out using clerk next js and i'm just going to add that all the way to the top like that let's write if there is no user id return redirect from next slash navigation to the root page so i'm also going to move next navigation to the top great and now let's go ahead and let's open this get courses and let's give it user id and we're gonna have to pass the title and the category ID. And everything that is inside of our URL can be accessed in server components props. So let's go ahead and let's extract this and let's write search params like that. And now let's create an interface which is going to handle the types inside of the search params. So interface search page props is going to have search params which have a title of string and category ID of string like that. And then we can assign that here. So search page props, and we no longer have any TypeScript errors here. And then we can just do as easy as spread the search params inside dot dot dot search params like this. There we go. And now we have the courses. So search params is an object which has the title and the category ID. And that is mapped to what is either selected like this or written like this inside of our URL. So we have the category ID in the URL and we have the title in the URL. So we can access that in a server component using search params prop. So we did not define this search params. That's something that comes with you in Next.js. And inside of our get courses, we accept the title and the category ID. So that's why it's working that we can just spread that as easily as this, perfect. And now what I want to do is I want to create a component courses list. So inside, I'm going to write courses list like this. And I'm going to pass in the items to be courses like that. Perfect. And if you save, you're going to get an error. So let's go ahead and let's go inside of the global components folder. And let's create a new file courses dash list dot DSX. Let's go ahead now and let's import category and course from at Prisma slash client. Let's import, sorry, nothing else to import, but let's just write export const course list like that and return a div course list like this. Now we can go back inside of the page and you can import this component. let's just see if we made an so courses list not course list courses list like that and now we can import it so courses list from add slash components courses list and i'm going to move it with the others now we have the type script error inside so let's create an interface and let's first create a type uh course with progress with category so type course with progress with category is going to be course, which we have imported from Prisma Client, which is going to have a category, which is either a category or null. And it's going to have chapters, which is an array of an object which has an ID like that. And we're going to have progress, which is number or null like that and then we can create an interface courses list props the items courses with progress with category so this one course with progress with category and an array like that And then we can use this to assign to props right here And then we can extract the items like that And let's do a very simple items.map. Let's get the individual item. And let's write a div item, item.title of the course. And let's give it a key of item.id. so we don't have any errors like that. And let's see if that's going to work or not. So it seems like we're not getting any items here. So first thing I'm going to check in my teacher mode if I have any and any courses published. So make sure you have at least one course published. And then in the search page, we should see, and there we go, we actually have it. It's right here, advanced web development. Perfect. So it's this course right here. So if you are not seeing anything, just make sure you refresh, check if you have any text here and confirm that at least one course is published. In order to have a course published, remember, you need to complete all the necessary fields in the course setup, such as an image, description, title, and category and price. And you also need to have at least one published chapter. So make sure you have all of that. And then you're going to see this on your search page. Perfect. So now we are ready to create our course card, which is actually going to display the thumbnail and everything else. What we have to do is we have to replace this div right here to render a course card. But before we do that, let's go ahead and let's add another div, which is going to wrap this entire component like this. and first thing I want to do is I want to add a option if there are no items in the array so we display an empty message so if items.length is equal to zero in that case we're going to render a div no courses found like this and let's give it a class name of text-center text-small text-muted-foreground and margin top 10 like this and you can already test this out by typing something that should not be in any title and there we go now it says no course is found or you can pick a category which you haven't selected anything so there we go now we are ready to create the course card so i'm gonna go and expand the code as much as possible here and i'm gonna give some classes to this div so class name is going to be grid on small devices is going to be grid dash calls dash two on medium is going to be also grid dash calls dash two like that on large is going to be grid dash calls dash three on excel is going to be grid dash calls dash four on two excel is going to be grid-calls-for as well and gap-for like that. Perfect. And now let's go ahead and let's replace this div with a course card component. So course card, which is going to be a self-closing tag. If we save, we're going to get an error because court card doesn't exist. So let's give it a key of item.id. Let's give it an ID of item.id as well. Let's give it a title of course, sorry, item.title. Let's give it a image URL of item.imageURL. Let's give it a chapters length of course.chapters, sorry, item.chapters.length like that. Let's go ahead and give it a price of item.price. Let's give it a progress of item.progress. and let's give it a category of item question mark category question mark name like that perfect now let's go ahead and let's create this course card component so i'm going to go inside of my global components folder and i'm going to create a new file course dash card dot dsx let's go ahead and let's mark this as use client and let's write actually we don't need to write it as use client what I want to do is just export course card here and return a div saying course card like that. Let's go back inside of courses list and now we can import that from dot slash course that card like that but I just want to change it to be add slash components, add slash components course dot card like that and there we go now it says course card instead of the name of my published course. So let's go ahead and let's create an interface for this. So I'm going to go ahead and I'm going to write interface course card props to be ID, which is a string title, which is a string image URL, which is a string like that. Chapters length, which is a number. Price, which is also a number. Progress, which is a number or null like that. And category, which is a string like that. And then we can extract all of those props here. So let's map them to a course card props and let's extract them. So ID, title, image URL, chapters length, price, progress, and category like that. Perfect. Let's go back inside of the courses list and let's see our errors. So we are saying that the image URL is required, that price is required, right? But if you remember, you know, Prisma schema, these things can be optional. But something that the TypeScript doesn't know, but we know is that this course can never be rendered if it's not published. And since it's published, we know that we're gonna have image URL. We know that we're gonna have the price and we know that we're gonna have the category. So what we can do here is we can just easily put an exclamation point at the end. So we override that. Same thing for price and same thing for category name like this. There we go. No more errors in TypeScript. Perfect. So now let's go ahead inside of the course card and let's go ahead and let's wrap this instead of a div in a link component from next slash link. So make sure you add this import like that and let's give it an href property of backticks slash courses slash individual ID of the course. Now inside let's open a div and let's give it a class name of group cover shadow-small transition overflow-hidden border rounded-lg padding3 and h-full like this. Great. Now let's go ahead inside and create another div which is going to hold our image. So give it a class name of relative, a w-full, aspect-video, rounded-md, and overflow-hidden, like that. And inside, render an image from next slash image. So just make sure that you have the image imported from next slash image. Let's go ahead and let's give this a option bill, a class name of object-cover. let's give it an alt of title and let's give it a source of image url like this and now there we go we have a the same image which we have in our course setup perfect so let's continue developing inside and let's go outside of this div which has an image and create a new div which is going to have a class name of flex flex dash call and padding top two like that and inside create a div which is going to render the title like that and let's give it a class name oops of text dash lg on medium devices text dash base point-medium group-over is going to have text-sky-700 transition and line-clamp-2 like that. Perfect. Now let's go ahead below that and add a paragraph which is going to render our category of this course like that. And let's give it a class name of text-extrasmall and text-muted-foreground like that. perfect now let's go outside of this div sorry outside of the paragraph and create a div which is going to hold the icon badge to display how many chapters we have inside of this course so give it a class name of my-3 flex items-center gap-x-2 tax-small and on medium we're going to have text-extra-small. And inside, go ahead and open a div, which is going to be a flex box. So flex items-center, gap-x-1, and text-slate-500. And inside, we're going to render the icon badge component, which we created during the course setup. So let's go ahead and change this to add slash components like that. And if you remember, our icon badge has size variants and color variants. So let's go ahead and give it a size of small and let's give it an icon of book open from lucid react. So make sure you import book open from lucid react. And there we go. We have a nice little icon here. Perfect. Below the icon badge, let's go ahead and add a span, which is going to render very simply chapters length like that. And then we're going to write If chapters length is one, we're going to need to render chapter. Otherwise, we're going to render chapters, so multiple. So now it says one chapter. So regardless, if I have 10 draft chapters, it's only going to count the published chapters in this course. Perfect. So now what we have to do is we have to go outside of this div with the MY3 padding. so outside of here and we have to check if we have any progress for this course. So let's go ahead and let's open a conditional here. So if progress is not equal to null, in that case, I'm going to open a daemon write to do progress component like that. Otherwise what we are going to do is we going to open a paragraph which is going to use format price from at slash lib format like that so just make sure you add this import right and we're going to pass in the price option like that and there we go now we have the price and let's just style it so class name is going to be text dash md md is going to be text dash small font-medium and text-slate-700. Like that. Perfect. So we just created our course card component. And when we click on it, we're going to get redirected to a 404 page, which we are going to build next. And when we get to that page, we're going to create the reusable progress component. So that's why I wrote to do here. So we're not going to make it just yet because we cannot see any progress. So we can't really see what we're building. But when we click on this and when we purchase a course, you're gonna be able to see the progress component. Perfect. So one more thing that I wanna do is I just quickly wanna go back inside of the search page. So let me close everything here and let me go inside of the search page.tsx. And I wanna give this div, which has a class name of p-6, also space-y-4. So it has a bit of more separation between categories and the course itself. There we go. So this looks much, much better now. And you can play around and you can create as many courses as you want, publish them, and they're going to be visible in the browse page right here. Great, great job. Now we have to create this page, which when we click on a specific course, currently yields 404. So the way I want to create this page is I want to break from the normal layout. So I don't want to have this navigation bar and I don't want to have this sidebar. So let's take a look at where we have that sidebar. As you can see, that is coded right here inside of our route group dashboard. So we have this layout right here, which renders that navbar and that sidebar. So in order for us to break from this design, we have to create another organizational folder. So I'm going to go ahead and create a new folder inside of the app folder like this. And I'm going to add parentheses and I'm going to name this course like this. And inside, of course, I'm going to create a new folder called courses. like that. And then I'm going to create another folder, course ID in square brackets. And let's create a new file inside page.tsx. And let's just export it. So course ID page with a div, watch the course. It doesn't really matter for now. And if you try and click now, what's going to happen is that as you can see, we just have a plain text, watch the course, and we completely broke from our layout because that's exactly what we want. We're going to create a custom layout for this course. So let's go ahead now and let's go inside of this course ID page and let's create a new file layout.dsx like this. So let's go ahead and let's just write the const course layout like that. And let's return. Sorry, let's write export default course layout, just so we don't forget that. And let's write a div. And we have to import the children from the props. So children, let's give them a type of children to be react.react node like that. And let's just render the children like this. There we go. So no more errors, but nothing has changed as well. Great. So now we can also extract the params from here. And since we are working inside of course ID folder, our layout is going to have that course ID inside of the params. So let's add params to have a course ID, which is a string like this. Perfect. So now what I want to do is I want to go ahead and fetch the course, which we are trying to load. So for that, let's mark this entire layout as an asynchronous function. and let's go ahead and let's import database from atlibDB. And while we are here, let's already prepare the out. So import out from clerk next JS. So first thing I'm going to do is I'm going to fetch my user ID. So const user ID is equal to out, which we imported. And if we don't have the user ID, we're going to return redirect from next slash navigation to the root page. So I'm just going to move this input to the top like that. Perfect. And now let's go ahead and let's fetch our course. So const course is equal await db. Whoops. db.course.find unique where id is rams.courseid like that. Include as chapters. And we're only going to include chapters which are published like this. Great. and we're also going to go ahead and include user progress inside of those chapters so include user progress but only the progress for the currently logged in user so where user id is matching like that and we're going to go ahead and we're going to order them let me just find where so not here uh oh right so we're going to order the chapters like this so outside of include order by position ascending. Great. So the order by is for the chapters object. So make sure it is inside of this chapters area. And we're going to order them by their position in which we reorder them in our form, if you remember. So we want them to display in the correct order. Perfect. Now let's go ahead and let's write, if there is no course we can return redirect to the root page like this perfect now let's go ahead and let's get the current progress so const progress count is await get progress from actions get progress so make sure you import that and we're going to load the progress user has made on this course using the user id and the course id like that now we have the progress count Perfect. So let's give this div a class name of h-full like that. Let's go ahead and let's create a main element for this children. And let's give it a class name of md pl-ad and h-full like this. And now above, create a div with a class name of hidden, but on MD, it's going to be flex h-pull w-80 flex-call fix inset-y-0 and z-50 like this. And now let's go ahead and let's render course sidebar inside like this. let's pass in the course and let's pass in the progress count which is going to be progress count like this and of course we are getting an error because course sidebar doesn't exist so now let's go inside of the course id and create a new folder underscore components like this and inside create a new file course dash sidebar.tsx like this let's go ahead and let's write export const course sidebar like that and return a div saying sidebar like this. Go back to the course layout now and import the course sidebar from dot slash components course sidebar. Now, of course, we're not going to be able to see anything unless we expand the screen and then we can see sidebar because it's hidden on mobile devices. We're going to have a navbar which is going to be able to open it just like on our usual layout. So if you want to see what you're working with, I highly suggest that you zoom out your code or expand the screen so you can see this text sidebar because we're going to style this now and render the chapters. So that's what I'm going to do. I'm just going to zoom out a lot. You probably can't even see how small this text is, but this is enough for me to know that I have a sidebar here and I'm going to start working inside. Perfect. So now we have the errors for the props we were passing to the course sidebar. Let's go and let's create an interface, course sidebar. Let's give it a course. Actually, let's name this course sidebar props. Let's give it a course option, which is a course type from Prisma Client. And we're going to extend it by accepting chapters. And that chapter is going to be open parentheses and write an array. And let's go ahead and write chapter, also from Prisma Client. and let's go ahead and give it user progress, which is a type of user progress from Prisma client like that. Add an array at the end or null. Great. So now that we have that, let's also append the progress count, which is a number. Great. Now let's add those props here. So course, sidebar props like that. let's extract the course and progress count. Great. So as always, let's try and fetch our user ID const user ID from out, import out from clerk next JS. If we don't have the user ID, return redirect from next slash navigation to the root page. Make sure you have the import right here. great now let's attempt to fetch the purchase so i'm going trying to find the purchase for this course const purchase is equal to await db.purchase and make sure you import db so i added that from add slash lib slash db and i'm going to separate the imports await db.purchase.findunique. And as you can see, I'm getting some errors here. That's because I have not marked the course sidebar as an async function. Make sure you do that. Now go back inside of the purchase, await db.purchase.findunique, where, and now I want to show you a little trick. So in our schema right here, if you find the purchase, let's find where it is. so it's right here and let's see if maybe I'm actually missing something here so what I'm expecting to see yes I'm missing something so in this purchase I want to make sure that we have a unique combination of user ID and\nand the course ID because I don't want it to be possible that user can purchase a course two times. So I'm going to add a new index here, sorry, a new decorator here at unique like that. And I'm going to pass in the user ID and the course ID like this. So I want a unique combination of those. Perfect. And let's also go ahead. So the same way I added it with the user progress, like I forgot to do that. So you want to do it the same as in the user progress. And that's all the places we need this at that unique. So I forgot to add it in purchase. So I'm just doing a quick sweep if I should add it somewhere else, but I think this is fine. So just make sure that in your purchase model, you add at that unique user ID and course ID and save schema Prisma. And then let's go ahead and let's run npxprisma generate. Make sure you shut down your app and then npxprisma db push. wait for a second to this to be sent to PlanetScale. And it seems like, all right, so if there are existing duplicates, this will fail. So we can just ignore those warnings. I mean, it doesn't really matter. We are not working with any production data here. We can always reset our entire database if needed. And once you do this, so you can just confirm if you get this warning, just press yes and do npm run dev. And you can just go ahead in your npx prisma studio just to confirm that you're not getting uh that you didn't lose your data so let's see if that happened perhaps we lost our data now so i'm just gonna go and paste this let's zoom in all right now it seems to still be here great so if you're seeing uh no information about your categories or about your chapters or courses you have to seed your categories So let me just show you an example. If you lost your data somehow, what you have to do is run node scripts seed.ts like that. And then you have to go and create your own course with a published chapter. So just in case that happened for you, but if you follow this tutorial exactly as I am, you will just probably get this warning and you are gonna continue to develop. Perfect. And now what we're able to do here is a unique query because we just added them, those two fields to be unique. So we can write user ID underscore course ID like that. So we can work with this unique query now and pass in the user ID and course ID to be course not ID like that. Great. And now let's go ahead and let's render this sidebar. So I'm going to give this div a class name of h-full border right flex flex-call overflow-y-auto shadow-sn. All right. Let me remove the sidebar text. Create a new div inside, which is going to hold the h1 element, which is going to render horse.title like that. So let me zoom back out. And I'm just refresh my local host. so make sure to refresh your localhost if you run npm run dev again and there we go now i have a text which says advanced web development in my sidebar let's give this div which is holding our h1 element a class name of padding 8 flex flexnash call and border bottom now to the h1 element give it a class name of font-semi-bold. Great. Now it says advanced web development here. Now we're going to skip here. I'm just going to write a comment here. Check purchase and add progress. And instead, I'm going to go outside. I'm going to create a new div and I'm going to write class name flex flex dash call w dash full and in here course.chapters.map get the individual chapter and i'm going to render a course sidebar item like this let's give this course sidebar item a key which is chapter.id id which is chapter.id label, which is chapter.title, is completed, which is going to be a boolean chapter.userprogress?firstinthearray? is completed. Course ID, which is going to be course.id and is locked, which is going to check if the chapter is not free and if we don't have a purchase of this course like that great now let's create the course sidebar item component so go inside of the same folder and create a new file course dash sidebar dash item dot psx let's go ahead and mark it as use client. Let's export const course sidebar item. And very simply, return a div saying course sidebar item. Now we can go back to the course sidebar and import it from the very same folder because they are together right here. All right. And now let's add these props. So go back to course sidebar item, create an interface course sidebar item props. Label is a string. ID is a string, is completed is a boolean. Course ID is a string and is locked is a boolean. Now let's map those props right here. Course sidebar item props. Let's extract the label ID is completed. Course ID is locked. Great. Now let's go ahead and let's add some path names here. I mean some hooks here. So const path name to be use path name from next slash navigation. Let's also import use a router while we are here. Now we can add that const router is equal to use router. Now let's define the icon. So const icon is going to be checked if the current chapter is locked. So if is locked, we're going to display the lock icon from Lucid React. So make sure you add this import for the lock icon. like that. Otherwise, we're going to open another conditional inside of parentheses, where we're going to check whether we completed this chapter or not. So if it's locked for us, we are not going to display any icon besides lock. But if it's unlocked, if we purchase this chapter, we're going to check whether we completed it. So it's completed, check circle from Lucid React. Otherwise, play circle from Lucid React. So make sure you have check circle, lock and play circle for most react and make sure you have this function where we dynamically change the icon okay let's go ahead now and let's write const is active to be path name question mark dot includes the current chapter id and now let's write const on click to be router.push open backticks slash courses slash course id slash chapters slash individual chapter id great and now let's finally style this so i'm going to go ahead and replace this div with a button like that and i'm going to go ahead and write a div inside inside of this div, I'm going to render the icon, which we define right here. And I'm going to give it a label below it like that. And then you can see I have a lock icon here. And I have the introduction text here. Great. So let's just modify this a bit. So I'm going to give this button a type of button, I am going to give it a class name of CN. So import CN from lib utils, I'm going to separate it from the other imports. Let's give it default classes here. So right here, I'm going to write flex items-center, jack-x-2, text-slate-500, text-small, font-500, pl-6, transition-all, hover text-slate-600, hover bg-slate-300-20. So those are our default classes. Now let's write our classes if we are actively selected on this chapter. So is active, text-slate-700, bg-slate-200-20, hover bg-slate-200-20 and hover text-slate-700. So we don't want any change in hover for the isActive route. Now let's write isCompleted. Text-Emerald-700 and hover text-Emerald-700. now let's write if we both completed it and if it's active in that case render a class name vg-emerald 200-20 great let me just expand this a bit so we can maybe see better all right so you can see how i have a gray little uh box here which i can click on now and now let's go ahead and give this div which wraps our icon and label a class name of flex items-center gap-x-2 mpy-4 let's give this icon size of 22 and a class name of cn default class name of texter slate-500 is active. Give it a class name of text-slate-700 is completed. Text-emerald-700. Great. And below that outside of this div encapsulating our icon create a new div with a class name C again and this is actually going to be a self div like this ML-auto, opacity-0, border-2, border-slate-700, H-full, transition-all, is active, is going to have opacity 100, is completed, is going to have border-emerald-700, like that. And now let's add this on click to the button on click, like that. Great. So now I'm expand this i'm going to zoom in and there we go you can see that now we have our chapter right here and if i click on it we get a 404 page perfect so what we have to create now is a nav bar which is going to open uh this uh this sidebar right here on mobile mode and it's also going to hold a little exit button so we can go back to the list of all courses let's go back inside of the course layout So it's right here. Let me just close everything so you can see. So inside of our parentheses course folder, courses, course ID, and in here we have a layout file. So go inside of here and above this div encapsulating our sidebar, create a new div with a class name of h dash in square brackets 80 pixels and the pl dash 80 fixed in set dash y dash zero w dash full and z dash 50 and let's also give our main element pt dash in square brackets 80 pixels now go back inside of this div and render course navbar pass in a course as a prop and pass in the progress count as a prop as well let's create the course navbar now so inside of underscore components create a new file course dash navbar dot dsx go ahead and export const course navbar and return a div saying course navbar. Go back to the layout and now you can import that from .slash underscore components course navbar. The same way we did with the sidebar but course navbar is visible on mobile mode as well. So now let's create an interface for the course navbar. Course navbar props. Course is going to be a course. Let's just import course from Prisma client. Great. And now we're going to write chapters, open parentheses, write an array and write chapter from Prisma Client and open an object, user progress to be user progress type from Prisma Client and an array or null. And also add progress count to be a number. Let's assign these props to the course navbar props. course, navbar props, and let's extract all of them. Course and progress count. Perfect. Now that we have that, let's create a class name here. P-4, border-b, h-full, flex, items-center, bg-white, shadow-sn. And inside, I'm going to remove this text. And instead, I'm going to render navbar routes from add slash components slash navbar slash routes. So make sure you have that imported. And there we go. But we have a problem now. So this is saying teacher mode. What I wanted to say is exit. So we're going to have to revisit our navbar routes. So let's go and find them. navbar dash routes. They're in our global components folder. What I want to do is I want to find where it says is player page. So it's right here. Oh, this is actually okay. So this is going to work for us. Technically, we can already add it to be to slash courses. We could technically do this and then it still works, right? So if you want to, you can change it to slash courses, or you can keep it a slash chapter. So I got confused. I got confused because what we're going to do with our courses page is that we're going to immediately redirect to the first chapter. But actually, it might be even better if we wrote slash courses, right? This actually makes more sense. So the player page is whenever we are on slash courses page. And now, even if we don't select our chapter, we have the exit button, right? So perhaps that is even better. Yeah, You can change this is player page to slash courses, right? Or you can also rename this from is player page to is course page like this. Perhaps it makes more sense to you. I call it player because what's going to happen here is, well, the player, right? We're going to immediately redirect to the first active chapter and display the player. But if you want to, if it makes more sense to you, you can name this is course page. And then if we are on the course page, we need to have an exit button in our navbar so we can go back to, well, to the home screen, right? So I can go back here. And then when I click again on the advanced web development, I am back inside. All right. Now what we have to do is we have to create a mobile sidebar component so we can render the chapters here. So let's go back inside of the course navbar. And what we're going to do is above the navbar routes, we're going to render a course mobile sidebar component, which we don't have yet. We're gonna pass it the course and we're going to pass the progress count. Great. Now we have to create the course mobile sidebar component. So in the same folder, create course-mobile-sidebar.dsx. Go ahead and import menu from Lucid React. Go ahead and import chapter course user progress from Lucid, whoops, from AdPrisma slash client. Great. Import sheet, sheet content, and sheet trigger from add slash components UI sheet like that. and also import course sidebar from .slash course sidebar. Now let's go ahead and write interface course mobile sidebar props. And what we can do is we can just copy them exactly from the navbar. So they're exactly the same. So you can just paste them here. And now go ahead and write export const course, course mobile sidebar. Okay, course mobile sidebar, map the props, course mobile sidebar props. And go ahead and extract the course and progress count. And all we're going to do is add our sheet component, the sheet trigger component. Inside of the sheet trigger component, we're just going to render the menu icon which we imported. Let's give the sheet trigger some classes. Hidden on medium devices, because our sidebar is going to be visible then. PR-4, hover, opacity-75, and transition. below let's add the sheet content and let's render the course sidebar and let's give the course sidebar a course and progress count and let's now add class name to the sheet content to have p-0 bg-white w-72 let's also give it an attribute of side left. Now we can go back to the course nav bar and you can import the course mobile sidebar from dot slash course mobile sidebar and save the file. And it seems like I forgot to write return inside of the course mobile sidebar. So make sure you write return like that. There we go. Now I have a little icon here, which as you can see, opens my progress, my chapters. Perfect. So we finished the layout for our chapters. Now what we're going to do is we're finally going to create the player view here and the enroll button so we can finally purchase this course. Great, great job. What we have to create now is the individual chapter page. So when we click on introduction, for example, we don't want a 404. We want an actual chapter page. So in order to do that, let's go ahead inside of our course ID folder right here and create a new folder called chapters. So chapters and inside of chapters, you're going to create another folder in brackets, chapter id and inside create a new file page.dsx and let's do our shortcut sfc chapter id page and just render chapter id great and now let's try and click on introduction again and there we go now it says chapter id and you can see how this one now has a nice little bar to indicate that it is selected. Great. So what I want to do now is I want to show you how to reset your entire database with Prisma just in case something went wrong in the previous part where we modified our Prisma schema and got that warning that some of our chapters might be deleted. So I want to show you how you can manually reset the entire setup. So I'm going to close all of my terminals here. and I'm going to go ahead and run npx prisma migrate reset. So you don't have to do this if you don't want to, but I just want to teach you how to reset your entire database with Prisma. So always do this in development, right? You don want to do this in production with live users So what I have to do here is confirm this by pressing Y And now all of my data will be deleted from the database And then let go ahead and run npx prisma db push So you have to run db push after you reset your entire database. So you push the collections back to planet scale or whatever you are using for your MySQL solution. And then you can npm. Actually, let's not run npm already, first thing we have to do to start generating our database from the beginning is run node scripts seed.ts, because this is going to generate our categories. And then you can do npm run dev. Perfect. So what I'm going to do now is I'm going to exit this. So yeah, you can just refresh your page and go to localhost. And you should not have any courses in the browse page. So I'm going to go inside of teacher mode and I'm going to create a new course, for example, advanced web development. And inside of this course, I'm going to go ahead and I'm just going to finish all of this and I'm going to create a couple of chapters and publish all of them. So I added these four chapters right here and I marked introduction as a free chapter. Right here, I selected this checkbox. So I recommend that you do this as well. So have at least three or four chapters so you can develop this. And let's publish this entire course now. There we go. And now we can go back to the browse page in here. And there we go. I have my course. So what I want to do next is that when a user clicks on this course right here, right now they are redirected to this page where it just says, watch the course. What I want to do is to immediately redirect to the first chapter, which would be the introduction chapter. So let's do that. So I'm going to go back and find my course ID page. So go inside of course in parentheses, courses, course ID, and find page.tsx inside of the course page. Let's go ahead and extract the params from here. So params, and I'm going to map them to params, which is an object that holds the course ID string like that. So I know that because it's inside of the course ID folder. All right. Now let's import DB from add slash lib slash DB. and also let's import redirect from next slash navigation. Inside, I'm going to go ahead and attempt to fetch the course. So const course await db.course.findunique where id is params.courseid include chapters where is published true and let's also order chapters by their position. So position ascending. As you can see, I have an error in my await function, which means I have to mark the course ID page as an asynchronous function. I'm now going to write if there is no course, return redirect, which we imported back to the root page. And instead of returning anything from the elements here, we're going to return a redirect again open backticks to slash courses slash individual course.id slash chapters slash course.chapters the first one in the array dot id just like that and now if you take a look I'm gonna go and exit this and I'm gonna go inside of my browse page when I click on a specific course, I'm immediately redirected to the introduction. And you can see how introduction doesn't have a lock icon. That's because we have a function in our course sidebar item, which checks whether it should be locked or not. And since I marked this one as free, it doesn't have a lock icon. And since I have not purchased this course, and other chapters are not marked as free, I have a little lock icon. Great. Now let's go ahead and let's create, let's finally start creating our player right here. So let's go back inside of the chapter ID page. So page chapter ID, whoops, let me just close everything so we can find it again. So app folder course, course ID chapters, chapter ID page dot DSX. Now inside, first thing I'm going to do is mark this as an asynchronous function. And then I'm going to go ahead and extract the params from here. So params and let's give them types. So params inside of chapter ID page hold both the course ID and the chapter ID. So let's write course ID to be a string and chapter ID to be a string as well because we have both of those. Let's now extract the user ID from the out function using clerk next JS. If there is no user ID, return redirect from next slash navigation back to the root page. Great. And now what I want to do is I want to write a function called get chapter. So let's go inside of our actions folder and create a new file, get-chapter.ts. Let's go ahead and write an interface, get chapter props. user ID, which is a string, course ID, which is a string as well, and chapter ID itself, which is a string. Expert const get chapter is an asynchronous function which has the get chapter props inside of the props here. Let's extract the user ID, the course ID and the chapter ID. Let's open the arrow function. And let's open a try and catch block right here. First, let's use the catch one. So I'm going to go ahead and write console log, get chapter with the log error. And I'm going to return an object with chapter to be null, course to be null, max data to be null, attachments to be an empty array, next chapter to be null as well, user progress is going to be null, and purchase is going to be null. Great. And now let's go in the try block and let's actually fetch all of these items. So what I'm going to do is I'm just going to prepare my imports so we can just focus on fetching the data down there. So first let's import the database And then let's import attachment from Prisma Client and chapter from Prisma Client. And now let's go back inside of the try block. And first thing I want to do is I want to see whether the user purchased this course or not. So const purchase, purchase like that, await db.purchase.findUnique, where a combination of user ID and course ID matches. So user ID and course ID to be course ID. And don't forget to put equal sign here. So you can do it like this or you can use a shorthand like that. and it's not an equal sign my apologies it's colon like that great so we have the purchase now now let's attempt to fetch the course so const course await db db.course.findunique where is published is true and id matches the course id and let's select price to be true Now let's go ahead and fetch the chapter So cost chapter await db.chapter.findunique Where the id is chapter id and is published is true If we don't have a chapter or if we don't have a course let's throw new error chapter or course not found. Now let's attempt to fetch max data attachments and the next chapter. So let max data is null by default. Let attachments is a type of attachment and an array by default is going to be an empty array, let next chapter is a type of chapter or null and default value is going to be null. And now we're only going to fetch these things if the user purchased the course, right? So if we have a purchase, which we loaded right here in the beginning, right here, only then are we going to go ahead and load the max data to play the video, the attachments that the teacher provided and the next chapter to redirect the user to. So let's write attachments are equal to await db.attachment find many where course ID is course ID. Now let's go ahead and let's go outside of this if clause for purchase and let's check if chapter is free or if we have a purchase. And now we're going to get the max data. So max data await db.max data.find unique where chapter id is chapter id from the props. Next chapter also inside of that if clause is going to be await db.chapter.find unique. So we find first where course ID is course ID is published is true and position is greater than the current chapter position. Right. So we want to load the next chapter by position and order by. This is also important position ascending. And last thing we going to do is we going to fetch the user progress So outside of this if clause const user progress await db unique where user ID and chapter ID combination matches So user ID and chapter ID. And then what we have to do is write return chapter course max data attachments. Next chapter user progress and purchase. Great. So now we are ready to go back inside of our chapter ID page. So let's go back here. All we did so far was add a redirect. And now let's go ahead and write await get chapter. And let's pass in the user ID. Chapter ID to be params.chapter ID. Course ID to be params.course ID. And let's import get chapter from add actions get chapter. I'm going to separate those. and let's extract chapter course max data attachments next chapter user progress and purchase excellent now let's see if we successfully got that or not so if there is no chapter or if there is no course return redirect from next slash navigation which i think we already have here to the root page. Yes, we already have the redirect. Great. Now let's write const is locked to be if chapter is not free. And if we don't have a purchase, and now let's write const on complete, sorry, complete on end. If we have a purchase, and we're going to do that by adding two exclamation points, which is going to turn this into a boolean, right? So if we have a purchase and if user progress is completed like that, this is supposed to be end, end. So we're going to use this to trigger autocomplete when the video finishes or not. So this is going to be our, well, our clause for that to happen. Now let's go inside of the return function. And inside of this div, first thing I want to add is a banner. So let's write user progress question mark is completed. So if that is true, we're going to render a banner, which we already created. So import that from s slash components banner, just like that. And we're going to give it a label. You already completed this chapter. And we're going to give it a variant of success. Now we're not going to see anything here. That's because we have not completed this. So let's copy and paste this. And let's change this if clause to be is locked. In that case, we're going to write warning. And we're going to write, you need to purchase this course to watch this chapter. Great. And let's see if we can already see that. So right now we are in introduction. So that is free. That's why we don't have that warning. Now let's click on getting started and let's refresh and see. There we go. You need to purchase this course to watch this chapter. You can see how it says that on the locked ones. But if I go to introduction, it doesn't say that because I marked introduction as a free chapter. Excellent. Now let's go ahead below this. Let's create a div with the class name of flex, flex-call, max-w-4xl, mx-auto, and pb-20. Inside, we're going to open another give and give it a class name of padding4. And here, we're going to render our video player component, which we are going to create. Let's give it a chapter ID of params.chapterID. Let's give it title of chapter. dot title course id to be params dot course id next chapter id next chapter question mark dot id playback id max data question mark dot playback id is locked is locked complete on end complete on end great and now we have to create this video player component so inside of chapter id i'm gonna go ahead and create a new folder called underscore components and a new file video-player.tsx. Mark this as use client. Let's import Axios from Axios. Let's import mux player from mux player react. Let's import use state from react let's import toast from react hot toast let's import use router from next slash not router but navigation let's import loader2 and lock from lucid react so we have some icons Let's import cn from libutils. And let's import useConfettiStore from at hooks useConfettiStore. Now let's write an interface for this video player props. Interface video player props has a playback ID, which is a string. Horse ID, which is a string. Chapter ID, which is a string. Next chapter ID, which is an optional string. is locked, which is a Boolean, complete on end, which is a Boolean as well, title, which is a string. And now let's write export const video player. And let's add those props here. So this is a type of video player props. And let's extract the playback ID, the course ID, the chapter ID, the next chapter id is locked complete on end and title so everything that's written in the props excellent and now what i want to do is i just want to return a div whoops a div which is just going to say video player and now let's go back inside of page.tsx and let's import the video player from .slash components video player. And in order to fix this, we're going to just add an exclamation point at the end because the TypeScript has specific logic, but we know that it's going to be here, right? Because we won't allow this chapter or this course to be published if we don't have a working playback ID. All right. And now inside of here, let's write a class name, relative aspect-video. below that let's write if it's not locked a div with a class name absolute inset-0 plex items-center justify-center bg-slate-800 and inside a loader2 element which we imported and give it a class name of h-8 w-8 animate-spin and text-secondary. Great. Now you can see we have a little loader here. And of course, you're only going to see this if you are in a free chapter. So make sure you're inside of a free chapter or create one if you don't have it. Below that, we're going to write another cause for is locked. So if we are locked, we're going to render a similar div. So class name absolute inset-0 flex items-center justify-center bg-slate-800 flex-call gap-y-2 text-secondary. inside render a lock icon with the class name h-8 and w-8 and a paragraph this chapter is locked and give a paragraph a class name of text dash small and now let's try clicking on a locked chapter and there we go this one says this chapter is locked but the introduction one is still loading. Great. What I want to do now is I want to add some states here. So I'm going to go here and add const is ready set is ready to be used state and default value of false. And now let's use this is ready alongside of this is locked right here. So I'm going to write if it's not ready and if it's not locked, then we're going to display the loading animation. Otherwise, if it's locked, there's no point in displaying the loading animation because we are never going to load the player because it doesn't have the necessary attributes, right? And now below this, add, if it's not locked, go ahead and render a mux player, which we have imported, with a title of title, class name of CN, and let's just write, if it's not ready, it's going to have a hidden class name. And then on can play, set is ready, is going to become true. And on ended, for now, it's just going to be an empty function and also write auto play great and let's see if we missed something we did we need to add a playback id here so playback id to the mux player and there we go as you can see now you can refresh it's loading and then here we are with our test video from Max. Perfect. Great job. As you can see, you can play around with this player. So Max is a really high quality player. You can also change the quality and you can even explore the documentation for so many other things that you can do. But what is very useful for me right now is, well, firstly, the video processing, which can handle videos up to 10 hours.\nand more, right? Which is extremely good if I want to create my own course platform. As you can see, my videos are very long and I love that Mux can process those long videos. And of course, we have additional actions. For example, once the video reaches its end, I want to redirect to the next chapter. So we're going to work with that as well. Great. So we're going to stop here. And then in the next part, we're going to add the enroll button, the title, the attachments, everything that we need to purchase the course. Great job. Let's go back inside of chapter ID page. So right here, I believe, there we go. So chapter ID where we render the video player. And what we're going to do is we're going to go outside of this div, which is encapsulating the video player and add a new div. And inside, let's add another div with a class name of p-4, flex, flex-call, md, flex-roll, row, items-center, and justify-between. And let's write h2 chapter.title. Let's give this h2 element a class name of text-to-excel, font-semi-bold, and margin-bottom of two. Great. So mobile devices is going to be centered, but if I expand my screen, it's right here, like that. So I'm just going to move it back so I can see more of my code. Now, what I'm going to do is I'm going to go below this H2 element and I'm going to write, if we have a purchase, in that case, for now, I'm going to add a comment to do add course progress button. Otherwise, we're going to go and we're going to render the course and roll button. Now, of course, if I save this, we're going to get an error. So we're going to create it right away. Let's just pass it some props. Course ID, which is going to be params.course ID. And let's pass it price, which is course.price, and put an exclamation point at the end. And let me just see if I maybe have to render something here. So I'm going to render a div. Yeah, we have to render something here. And inside, I'm just going to add a comment to do, add course progress button. So we have to render at least a div here. Great. and now let's create the course and roll button so inside of this components where we have the video player create a new file course dash and roll dash button dot dsx let's mark this as use client and all we're going to do is export const course and roll button and return a button from components UI button. And we're going to write enroll for, and then we're going to import format price from add slash lib format. And inside, we're going to paste the price, which we're going to get from the props. And we're also going to get, we're also going to get course ID itself, right? So let's prepare the interface. Course enroll button props price, which is a number and course ID which is a string and now we can assign those props here course and roll props like that and all right I think this is good enough for us to go back to the page and just import this so make sure you import it from dot slash components course enroll button Great. And let's go ahead and let's just give it a class name. W-full. On medium devices, W-auto. So there we go. On mobile devices, it's going to take the whole screen. But on desktop, it's going to be to the side like this. And I also want to give it a size of small. Great. There we go. So we finished this button. Now, of course, we're going to go back and it's actually going to open the Stripe checkout page. But for now, let's go back to page.tsx so you can render the attachments and anything else we might need. All right. So first thing I'm going to do is going to into the whoops, not ports. I'm going inside of terminal and I'm going to write npx chat CN-UI at latest add separator. go ahead and confirm the installation and after it's done npm run dev and refresh the local host all right so now that we are outside of this course enroll button go outside of this div which is encapsulating the chapter title and the course button and go below it and you can just write a separator from add slash components UI separator. So don't accidentally import it from Radix. So let me just see. There we go. And I'm just going to move it to the top. And there we go. You can see how I have a small line here now, which is, well, a separator. Below this separator, let's open a div. And inside, let's render a preview component from add slash components preview. So make sure you import preview from the components. I'm going to move it here. and go ahead and give it a value of chapter.description and put an exclamation point at the end. And now you're going to have your HTML description rendered here. Excellent. Outside of this div, go ahead and add, if we have attachments.length, in that case, we're going to go ahead and open a fragment. We're going to render a separator here at the top. and then we're going to open a div with a class name of p-4. Inside of that div, we're going to run attachments.map. We're going to get the visual attachment. We're going to open an href like that and let's give it a key of attachment.id. let's give it an href of attachment.url let's give it a target of underscore blank and inside we're going to open a paragraph and we're going to render attachment.name and now let's go back to this a element and let's give it a class name flex-items-center-p-3-w-full-vg-sky-200-border-text-sky-700-rounded-md-cover-underline. And give this paragraph a class name of line-clamp-1 and add a file icon from Lucid React. So I just imported the file icon. I'm going to move it to the top. Great. And now I'm going to click refresh here. And it seems like it's not rendering any attachments. So let me just confirm that in my course, I actually have the attachments. So that's the first thing we have to do. All right. We have the attachments. And I just remembered. Yeah. So we are not going to load the attachments until we purchase a course, right? We don't want any files to be shown to students who have not purchased the course. So right, we cannot see them now. So what we're going to do next is we're going to enable this enroll button to actually open up Stripe. So we're going to have to now set up our Stripe environment, add all the environment keys, create your store and create some routes and hooks to do that. So let's go ahead and create a Stripe account and add some Stripe API keys. So go to Stripe.com and find the login button and just enter your dashboard. Once you're inside of your dashboard, so as you can see, I already have a project here. So perhaps your screen is a little bit different. So I want you to find this store icon in the upper left corner and go ahead and scroll the way down and click on new account, right? Or this will be your default screen if you don't have any account, I believe. And create this account. I'm going to call it LMS tutorial like that and select your country of operation and just create an account. All right, so I'm just going to zoom out so we can see what we're working with here. So I'm just going to skip all of this and I'm going to click API keys for developers right here. And what I'm going to do is I'm going to click reveal test key, this one, right? And I'm going to go ahead and copy it. So you can just click to copy. And let's go inside of our dot environment file. And below the max token, let's add stripe underscore API underscore key and paste it inside. Great. And I'm going to leave the stripe in my other browser tab. And I suggest you do the same because we're going to have to create a webhook later on. So what I want to do now is go inside of my terminal and install Stripe. So npm install Stripe like this. And then you can run your app again and just refresh your localhost. If you do that, I'm going to close everything and I'm going to go inside of the lib folder and create a new file stripe.ts. I'm going to import Stripe from Stripe package, which we just installed. And I'm going to export con for Stripe util, which is going to be new, stripe, and I'm going to pass in the key, which is process.environment. Well, it's exactly what you've written in your .environment file. So you can copy it from here and paste it here like that and put an exclamation point at the end so it knows that it's here for sure. And let's go ahead and give it an API version. And you can just press on whatever it autocompletes for you like that. And let's give it TypeScript option to be true. Great. So now we have our Stripe util. What we have to create now is the checkout route. So the checkout route is going to be the route which we trigger once we click on the enroll button right here. And that's going to be inside our app folder API courses individual course ID and create a new folder checkout and inside create a new file a route.ts and now let's write export asynchronous function post which is going to have a request and it going to have params and the params is an object which has a course ID as a string Let open a try and catch block So I'm going to resolve the error first. And I'm going to console log course ID checkout and an error. So it doesn't matter that this is a typo, right? This is just for us. And then return new next response from next slash server internal error. with a status of 500. Let's go inside of the try block. And first thing I want to do is fetch the entire user. So I'm going to use await current user from clerk slash next JS. And now let's write if there is no user, or if there is no user ID, or if there is no user dot email addresses question mark zero question mark dot email address so we need at least one email address for the user in all any of those cases we're going to return new next response unauthorized with a status of 401 great so let me just expand my screen here so i can zoom in And now let's attempt to fetch the course we are trying to purchase. So const course await db.course.findunique. And I have to import db. So import db from as slash lib db like that. And let's add where id is params.courseid and is published is true. So we can only purchase published courses. Now let's check if we already have purchased this course. So const purchase await db.purchase.findUnique where userid underscore courseid matches. So userid is user.id and courseid is varams.courseid. Great. And now we're going to write, so I'm just going to scroll down here. If we have a purchase, return new next response already purchased with a status of 400. If we don't, but if we don't have a course, return new next response not found with a status of 404. Great. And now let's define the line items for the Stripe checkout page. So const line underscore items is a type of Stripe. So import that from the Stripe package like this. I'm going to move it to the top. So Stripe from Stripe. and go ahead and write stripe.checkout.session.createparams.alignItem and add an array. And in our case, it's going to be an array, but it's only going to hold one item inside. So quantity is going to be one. price data is going to have a currency of US dollars product data is going to be have a name which is course.title description which is course.description and we can put an exclamation point because we know that if it's published it needs to have a description and the unit amount which is going to be math.round horse.price times 100. Great. And now that we have that, let's go ahead and let's create the Stripe customer. So let's Stripe customer await db.stripecustomer.find unique where user ID is user.id and let's select Stripe customer ID to be true. Excellent. And now let's check if we don't have a Stripe customer. So that's completely fine if we don't. So if we don't have a Stripe customer, we're just going to create a new one. So this is someone purchasing for the first time. So let's write const customer, await Stripe, but this time from s slash lib slash Stripe, right? So make sure you have this imported. Stripe from s slash lib Stripe. And then down here, what you're going to do is you're going to write stripe.customers.create email user.emailaddresses 0.emailaddress. And now let's define the Stripe customer in our database. So Stripe customer await db.stripecustomer.create data user ID to be user.id and Stripe customer ID is going to be customer.id. Great. And now we can go outside of this if clause and we can write const session to be await stripe.checkout.sessions.create. Customer is stripe customer.stripe customer ID. We add the line items, which is a constant we created right here above line items. So we can just pass it like this. Mode is going to be payment. Success URL is going to be open back text. Process.environment.next underscore public underscore app underscore URL slash courses slash individual course ID slash. And it's just going to write success equals one like this. So this is the entire line. And you can duplicate this line for the cancel URL. And this is going to say canceled to be one like that. And metadata. This is extremely important. So don't forget the metadata. Metadata is going to be an object where we add a course ID to be course.id and user ID to be user.id. And now let's quickly go inside of next public, sorry, inside of environment file and add this process environment variable. So below this, add that. And I'm just going to write HTTP that is slash slash localhost 3000. So just like this, no slash at the end. Perfect. Now that we have that, we can go ahead and return next response dot JSON URL session dot URL. So let me explain why is it so important that we have this metadata? Well, what are we actually doing when we call this route? We are not creating a purchase here. We are creating a customer. But we don't know when the payment will succeed, right? Because they have to do in real world. So users have to authenticate with their bank that they're doing an online purchase, right? So we don't exactly know when that's going to happen. So the reason we are adding the metadata is that when the purchase goes through, so when the user authorizes it, the Stripe is gonna throw us back a webhook using this metadata. And then using that metadata in the webhook, we are gonna know what payment just went through, right? Using that metadata, we're gonna know, oh, okay, so this what I just received in my webhook is this user purchasing this course. So that's why this metadata is so important. Excellent. And now what I want to do is I want to set up my Stripe CLI webhook, right? So let's go inside of developers right here. And let me just zoom out so you can see where this is, right? So inside of your Stripe right here, so let me just try and see what you're going to see. You have to find the developers button, basically. And once you're in the developers button, go ahead and find webhooks. And in here, you're going to click test in a local environment. First thing you're going to have to do is download the CLI to set up Stripe. So just click on this link if you don't have the CLI. They have instructions for Windows, for Mac, and for Linux. Once you have the Stripe CLI, you're going to be able to type Stripe in your terminal. So this is not going to work unless you click on download the CLI and set it up. And now let's follow these steps. so first thing we have to do is do the stripe login so i'm going to open my terminal here and i'm going to open a new one so keep your app running i'm going to write stripe login and as you can see i have the url here which i can either click on or i can press enter and that is going to open that url inside of my browser and as you can see here i have a pairing code polite-beauty-1-valor and i just have to confirm that it's the same one here so it's me who is trying to connect to my Stripe dashboard. If it's you, just say if the code matches, just go ahead and click allow access. Great, and now you're inside and connected. And now right here, you should see the completed button for this first step. The second thing we have to do is we have to add a mock, like a dummy webhook, right? So copy the second step and let's go ahead and paste that here. Let me just zoom out so you can see this. All right. So you have to modify this to be forward to localhost 3000 slash API slash webhook. Don't forget to put the slash API and don't mess up the port. So this needs to be exactly where you're running your application. We don't have the webhook route yet, but we're going to create it. And this is where it's going to be. So just press enter. And now you have your webhook signing secret. So copy this like that. And now we have to add that inside of the environment file. So just copy this bold text right here. Keep that terminal running. And now let's go inside of our dot environment file. And right here at stripe underscore webhook underscore secret and paste it in here. Great. So make sure there are no spaces at the end or at the beginning. So it just needs to be exactly what you saw in the terminal right here. So just exactly as much as this. Perfect. And now let's go ahead and let's revisit. Actually, no, let's create our webhook route now, right? So go inside of the app folder API and create a new folder webhook. And inside create a new file route Go ahead and import Stripe from Stripe Go ahead and import headers from next slash headers Go ahead and import next response from next slash server. Go ahead and import the Stripe util from add slash lib slash Stripe. And import the database util like that. Let's export asynchronous function post. which is going to have a request, which is a type of request. Let's get the body. So const body is await request.text. Let's get the signature. Const signature is headers.get stripe-signature as string, lowercase string. Great. Now let's define the event, which is a type of stripe.event. And now let's open a try block where we're going to attempt to construct the event using the signature. So event is stripe.webhooks.construct event. Let's pass in the body. Let's pass in the signature. And let's pass in process.environment.stripe.webhook.secret. So double check that you have this in your environment file, right? You need to have Stripe webhook secret. And you can just paste it here like that and put an exclamation point at the end. Excellent. And in the catch block, let's get the error, which is a type of any. And let's return new next response. And I'm just going to use backticks here to write webhook error, error dot message. with a status of 400. Great. And now below that, let's get the session. Const session is event.data.object as stripe.checkout.session. Let's get the user ID from the metadata. So this is what I was talking about. This is why metadata is important. Const user ID is session?metadata?userID. const course ID is session question mark metadata question mark course ID. And now let's type if event dot type is checkout dot session dot completed. If we don't have user ID or if we don't have a course ID in that case, we cannot finish this web cook, right? So this is why it's so important that we pass that inside of our checkout route. So that's why I emphasized in this checkout route that we add the metadata like that. Because otherwise, we don't know what we just received in this webhook, right? This webhook works asynchronously out of our control, right? It's just at one point, it's just going to get received to us. So that's why we need to have the user ID on the core CD. If we don't have it, we have to return new next response. And we have to pass in another webhook error missing metadata. with a status of 400. Otherwise, if we have those two, we can finally create our purchase. So await db.purchase.createDataCourseID, which is course ID, user ID, which is user ID, and that's it. And in here, you can write else, return new next response, webhook error unhandled event type. And let's log that type. So event.type. And one important thing, give it a status of 200, right? So you have to be very cautious in the Stripe webhook when passing 400 status errors. If your webhook has too many 400 status errors, Stripe is going to turn off that webhook because it's obviously not working as intended. So that's why we are not gonna throw 400 just because of an unhandled event type, right? So we're gonna log this for ourselves. So we know that we are receiving some events which we don't want, but it doesn't matter. We don't handle them. And that's not technically an error for the webhook. We don't have to break the webhook. So we're gonna pass 200 for it instead of 400. Great. And then at the end, because of that very same reason, we're also going to add return new next response null with a status of 200. Excellent. So we completed our webhook route. So now we're going to go back inside of our course enroll button. So let's find it inside of the app folder, course, chapter ID components, course enroll button here. and let's go ahead and let's add const is loading and set is loading from use state. So import that from React. I'm just going to move it right here. And now let's write const on click to be an async arrow function, which is going to have a try and catch block. In the catch block, let's use the toast from React hot toast. So I'm also going to move that here. So it's going to have toast.error. Something went wrong. In the finally, we're going to have set is loading to false. Set is loading. Is it this one? Cannot find name. Oh, async. Like that. Okay, I misspelled async. And in try block, set is loading is going to become true. And in here, we're going to get the response for the checkout. So const response is equal to await Axios from Axios. So make sure you add this import as well. Axios.host.backticks.api.courses.individualcourseid.checkout. And then we're going to do window.location.assign.response.data.url. And then let's add that on click here. and let's disable the button if we're loading. Excellent. And now you can click done here and you're going to have to refresh this page. So just make sure you refresh that and keep your terminal running where you have your Stripe CLI. So in here, you're going to see all the logs, successful and failed. So you're going to know what's going on with your webhook. so let's try and purchase this course right now so I'm going to click enroll and let's see if that's going to work I should get redirected to the Stripe checkout page and there we go as you can see I'm redirected back here and now I have to fill in this information so you can use this credit card to trigger a success from Stripe right so this is a dummy credit card which just goes 4242, 4242, 4242, and 4242. And for the rest, it doesn't matter, right? So it's just dummy numbers. And you can put whatever you want here. Just for this one, make sure that's in the future, right? And if you do a different credit card, this is not going to work, right? So make sure that you have the correct credit card. And now let's finally click pay. And I forgot to do something. So we have 401s inside of my terminal here. As you can see, it's still locked, right? So we made a mistake in the development. I forgot something. So all of my events yield 401, which means unauthorized. What I forgot to do was go inside of my middleware and I forgot to add the webhook inside of public routes. So let's go and add public routes, an array, API slash webhook. Great. And now Stripe can safely access our webhook. And you might be wondering, well, isn't it dangerous that our slash API slash webhook is now not secured by authentication? Well, take a look and visit the webhook route. So our webhook route is very much secured using the Stripe signature and using our process environment. So we have to construct the event. If we fail doing that, we throw an error, right? So you don't have to worry about it. It needs to be publicly accessible for Stripe to work. And let's try this one more time. So I'm going to go and prepare my terminal here so I can see if I'm having errors or success. Let's go ahead and click enroll one more time. In the checkout, use the working 42424242 credit card. And there's some fake information here and click pay. And let's see if you're going to get, and there we go. Everything is green, 200, 200, 200 right here. And now, as you can see, I can see my attachments here. I can go ahead and visit the next chapter. So this is the next chapter description. There we go. Amazing, amazing job. So you just completely finished the Stripe checkout. So in order to test Stripe checkout, just remember you have to have your local CLI running here. When we deploy, we're gonna add that to Stripe configuration. But locally, we have to test it right here because Stripe doesn't have access to localhost, right? This is not a public domain. This is just on our local environment. So that's why we have to use a local Stripe CLI to simulate a webhook. So whenever, if you're gonna like take a rest now and shut down your application, just remember when you start working on this again, you have to run your application using Stripe listen forward to localhost 3000 slash API slash webhook. Or if you're gonna wrap up with the tutorial, just don't shut down the Stripe CLI. Perfect. What we're going to do next is we're going to go ahead and create the button which I wrote to do which is going to change the progress of this course, right? We can mark it as complete and we're going to modify the video player to also mark it as complete if we reach the end of the video and what's left to do is also on the last video add some confetti and we are getting closer and closer to finishing the entire app. Great, great job. So now that we have purchased this course, let's revisit our course sidebar here and let's add the progress we've made in this course. Since we've purchased it, we can finally calculate the progress of how many chapters we've completed. So I'm going to go ahead and close everything. And I going to go and find the course sidebar which is inside of the app folder course Components and right here we should have the course the course sidebar which is inside of the app folder course components and right here we should have the course the course dash sidebar So go ahead and open that one And as you can see here, I left the comment, check purchase and add progress. So as you can see here, I have the purchase. So what I'm going to do is I'm going to modify this to be a conditional. If we have a a purchase. In that case, we're going to render a div with a class name margin top 10. And we're going to render the course progress component. If we save, we're going to get an error because we don't have that component. But let's already pass in the variant success and value progress count. So this is going to be a reusable component because we're also going to use it inside of our course card. So let's create this component. And we're going to do that in our global components folder, because I just as I just said, it's going to be reusable. So course dash progress dot bsx. Great. And let's go ahead and export const course progress component. And let's return a div course progress. Let's go back inside of the course sidebar. And we can now import it from as slash components slash course dash progress. So I'm going to move it with the database here. All right. And if you refresh in the sidebar now, you should see the text, the course progress. We're going to turn that into an actual, uh, into a, well, an actual component now. So let's first give it an interface interface course progress props, which is going to have a value which is a number a variant which is going to be either default or success and we're also going to give it an optional size which is either going to be default or small all right now let's go ahead and append those props here so course progress props let's write a value variant and a size great and now let's create some constants here actually we can do that outside right so const color by variant is going to be an object if it's default it's going to be taxed our sky there 700 If it's success, it's going to be text-emerald-700. You can copy and paste this entire constant and change this one to size by variant. And this one on default is going to be text-small, but on small is going to be text-extra-small. And let's just fix this typo success with two Cs like that. Perfect. Now let's go inside and let's modify this to have the progress component, which I forgot to add. So we have to go inside of the terminal. Like that, npx chatcn-ui at latest, add progress. So that's the component you're going to add. It's called progress. Install the package. and after it's installed npm run dev your app again and just refresh your local host. Now inside we're going to add the progress component from dot slash ui progress like that. So just make sure you imported the progress and I'm just going to change this to use slash components ui progress like this and let's give it a class name of h-2. Let's give it a value of value like that. And already you should see a progress bar right here. So it's very light. So it's not visible yet. So what I'm going to do now is I'm going to go inside of the progress component and I'm going to edit some variants. So let's go inside of the UI folders instead of components UI progress.dsx right here. Right now it doesn't have any variants. So we're going to extend this Schatzian component to use some variants, right? So const progress variants is going to be CVA from class variants authority. Let's move to the top and let's also import type variant props from it. All right. So it's going to be CVA and it's going to accept H-full, w-full, flex-1, bg-primary, and transition-all. Open an object, type variance, and type variant inside. Default one is going to be bg-sky-600. Success is going to be bg-emerald-700. And give it default variance, variant, default. Like that. Perfect. And now let's use this progress variant inside of the progress primitive indicator right here. So instead of this class name, we can now remove that because if you look, it's exactly the same as the one we defined here. So we're going to preserve these styles, but we're going to combine it with CN. And then we're going to add the progress variants. And inside, we're going to go ahead and pass in the variant. Now we don't have the variant right here yet. So let's go ahead and modify this progress props here. So I'm going to go ahead and find, well, we're going to define new props here. So let's write export interface progress props is going to be extends react.html attributes HTML div element and let's add a comma here and let's write variant props type of progress variants and an empty object at the end. And now let's write a type combined progress props like that to be progress props this is supposed to be progress props my apologies so this interface is progress props so combine progress props and react.component props without the ref like that so let me just zoom out so you can see it which is going to have a type of progress primitive.root with capital R like this So if you think you've messed this up or you can't get the result, you can always just copy it from my source code and copy the entire UIProgress.jsx. Great. And now we have to add this instead of this one right here. So just delete this one. And instead, just add the combined progress props. Right? And now after the value, also extract the variant. And there we go. We now have no more errors inside. And let's go back to the course progress and let's pass in the variant now because we can and variant as well. There we go. Perfect. So now that we have that, let's go below the progress and let's add a paragraph, which is going to render math.round value percent, outside of it, percent complete. And let's give it a class name. of CN. So just make sure to import CN from s slash lib utils. All right. So the value here, default value is going to be font dash medium, empty dash two, tax dash sky dash 700. And then we're going to write color by variant, variant or default and copy that and give it a size by variant and it's gonna be size or default like this great and now we have finished our course progress component as you can see now it says zero percent complete right here perfect so what I want to do now is I want to also add that to the course the card so let's go ahead and exit this and in the browse is right now, I think it's the price. There we go. So we have to do progress component. Perfect. So all we have to do is go inside of the course card. So that is inside of our global components, I believe. Components, we have it right here. Course card. And here where we have to do, let's remove the div and let's render the course progress from dot slash course progress. So make sure you imported that. And I'm going to change that to add slash components. So we have the course progress. Let's give it a size of small, a value of progress. And let's give it a variant to be dynamic. So if progress is 100% complete, in that case, it's going to be success. Otherwise, it's going to be default. And there we go. Now you can see that it no longer price displayed here. Instead, it says 0% complete. Perfect. So now let's go ahead and let's create a little button here, which is going to trigger completing a specific chapter. So let's go back inside of our chapter ID page. So I'm going to close everything here. Let's go inside the app folder, course, course ID, chapters, chapter ID, page.tsx right here. And in here, you can see that I have a conditional here. So we had the course enroll button, and now I'm going to remove this div. And instead, I'm going to render course progress button. And let's pass in the chapter ID to be rams.chapterID. Let's pass in the course ID, rams.courseID. Let's pass in the next chapter ID to be next chapter?id. and is completed is going to be a boolean of user progress question mark dot is completed and now we have to create uh is completed like that and now we have to create the course progress button so that's going to be inside of these components where the course enroll button is so course dash progress dash button dot dsx like that so let me just Let's expand so you can see course dash progress dash button. All right.\nLet's mark it as use client. Let's export const course progress button. Like that. And return a div saying course progress button. Go back to page.tsx and you can now import this button from .slash underscore course progress button. The same way we did with the enroll button. And there we go. You can see now I have a text here, course progress button. So now let's finally add the interface for it so we can style it into a proper button. Interface course progress button props are going to have a chapter ID, which is a string, a course ID, which is also a string, is completed, which is an optional Boolean, and next chapter ID, which is an optional string. So we might not have next chapter if we reach the end of the course, right? Now let's extract all of those props here. So course progress button props, chapter course ID is completed and next chapter ID. And instead of chapter, it should be chapter ID in the props and same thing here, right? So I forgot that. And there we go. Now in this page where we render the course progress button, we no longer have any typescript errors here. Great. Now let's go ahead and let's style this button. So instead of div, it's going to be a button element from add slash components UI button like that. And it's going to say if it is completed, it's going to say not completed because it's going to indicate an action. Right. So we do the reverse. If it's completed, the action is going to be not completed. So the user knows, OK, I have to click on this and then it's not going to be completed. Otherwise, it's going to be mark as complete. Great. And below that, let's render our dynamic icon. So for that, we have to define const icon is completed, question mark, x circle from Lucid React. Otherwise, check circle from Lucid React. So make sure you add those two. And I'm just going to separate these two imports. And then we can render that icon here like that. It's a self-closing tag and give it the class name of h-4, w-4, and ml-2, and give this button a type of button, and give it a variant, is completed, outline, otherwise, is going to be success, which we don't have yet. So what we have to do is we have to go inside of the button, and we have to add the success variant. So let's go inside of button. So that's inside of our UI folder. So components UI button, the same thing we just did with the progress a while ago. And this one is going to be much simpler. So I'm just going to go inside of this big button variants. I'm going to find the last one, which is a link. And I'm going to add success. And I'm going to write bg-emerald-600 text-white, hover, vg-emerald, emerald 600 slash 80, like that. And there we go. Now you can see how it's greenish color because we added the success and it works right here because it's not completed. So it uses the success variant, which we just added right here. So it's just as is that we added a new variant to the button. All right. And now what we have to do is give it some class names. So class name W-full on MD is going to be W-auto. So this is what it looks like on mobile. And on desktop, if you expand, there we go. It's a nice little button on the side here. So what I want to do now is I want to actually indicate an API call when we do this, right? So I'm going to go ahead and I'm going to create some states and I'm going to add some hooks here. So first, let's do const router to be use router from next slash navigation. So make sure you add this import. All right. Below that, const confetti, use confetti store. And we import that from add slash hooks, use confetti store. Below that, go ahead and add const is loading and set is loading. from use state. So make sure you add use state and default to false. So let me just add import use state from react like that. All right. And now let's write const on click to be an asynchronous function. We're going to try and catch. in the catch one we're going to call post from react hot toast we're going to call post.error something went wrong in the finally block we're going to set is loading to false in the try block we're going to set is loading to true And now we're going to do like conditional changes here. So await Axios from Axios. Make sure you add that import. Axios is going to call .put slash API slash courses slash individual course ID slash chapters slash individual chapter ID slash progress. and it's going to send a body of is completed to be the opposite of what we currently have. So it's going to be like a toggle button, right? And then if it's not completed and if there is no next chapter ID, this means that we have reached the end of the course. So once user clicks this, all the chapters are going to be marked as complete because this is the last one. So in that case, we can do confetti confetti.open sorry on open like that and let's also go ahead and let's write if it's not completed and if we do have next chapter id in that case we are not firing the confetti but what we are doing is router.push slash courses individual course id slash chapters and next chapter id like that and then at the end let's do toast.success progress updated and router.refresh great and now if you go ahead and try and do this you're going to get an error because that one doesn't exist let me just refresh this and let's see have we actually added this on click we haven't so let's go ahead and copy this on click and add it to the button on click and let's disable the button if we are loading so the same thing we did with the enroll button right and now let's try and click mark as complete and we're going to get an error because we don't have that api route so let's go ahead and create it so we have to revisit our api folder right here i'm going to collapse everything so i'm going to go inside the app folder API, courses, course ID, chapters, chapter ID. And alongside publish and unpublish, I'm going to create a new folder called progress. And inside, I'm going to create a route.ds. And I'm going to write export asynchronous function put, which has a request of type of request and has params. rams are an object which hold the course id which is a string but also the chapter id which is also a string let's open our good old try and catch block here let's console log whatever is useful for us locally so in my case is going to be chapter id progress and an error so i know what's going on. And let's return new next response from next slash server, internal error, something whoops, status 500. Like that. So make sure you added the next response import. In here, as always, we get the user ID from the out like that. If we don't have the user ID, we can return new next response unauthorized with a status of 401. And while we are here, let's also extract is completed from await request.json. Let's go ahead and let's import the database. So db from s slash lib db. And now below this, let's write const user progress is await db.user progress. And we're going to use absurd. So it's either going to create it for the first time or it's going to update it. So .Upsert where user ID, chapter ID matches the combination of user ID and chapter ID from params.chapter ID. Oops, chapter ID. And we're going to pass in update is completed. but if it's our first time creating, we have to pass in the create option. So we're going to create it using the user ID, chapter ID, which is for rams.chapter ID and is completed like that. So now we handle both cases. If this is the first time we are creating the user progress or if we are updating it. Perfect, exactly what we need for this use case. And return next response.json user progress. Excellent. And let's try it out now. So I'm going to refresh this. And I'm going to click mark as complete. And let's see if that's going to work. It says progress updated. And there we go. We are redirected to the next one. It says 25% complete. And you can see if I click back, I have a nice little icon. I have a banner saying that I've already completed this chapter. And I can toggle it as uncompleted. So I can click again. And there we go. Now my progress is back at zero. So I just want to mark this as complete. So I can also confirm that in the browse page, it's also 25%. And it is perfect. So exactly what we wanted. Great. So what I'm going to do now is I'm also going to enable the video player to complete the chapter if it reaches the end. So let revisit the video player component So I going to close everything here And video player component is inside app folder course courses course ID chapters chapter ID underscore components right here video player And as you can see, on ended, function is empty. So we're going to go ahead and create it now. So besides this state, I'm also going to add a router from use router, which I think we already have imported here. Yes, we do. Great. And we also have the use confetti store imported. So we can add that as well. So const confetti, use confetti store. Excellent. And now let's write const on end to be an asynchronous function, which has a try and catch. In the catch, it calls toast.error. Something went wrong. So make sure you have toast imported from React Hot Toast. in the finally block. In the finally block, actually, we don't have a finally block in this one. We don't need it, right? Yeah, because nothing is loading per se. Yeah, perfect. So that's it for the catch. And now let's go if complete on end. So if we have that condition set from our props inside of the video player, only then we are going to complete this, mark this as completed when it reaches the end. So await axios.put. Make sure you have axios imported from your project. Slash API slash courses slash course ID slash chapters slash individual chapter ID slash progress. Like that. And pass in is completed to be always true. So we don't have to toggle it, right? Because if it's not true, we're not going to enable, there is no way that the video can toggle it, right? The button makes sense because click once, it's completed. Click another time and you mark it as uncompleted. But for finishing the video, it doesn't make sense. If it reaches the end, it's completed, right? It cannot toggle to uncompleted. So that's why we always know that we want it to be completed in this case. Great. And now, if there is no next chapter ID, we have to throw the confetti because we finished the entire course. So confetti.onOpen. Like that. And let's write post.success. Progress.Updated. Let's do router.refresh. And lastly, let's check if we do have next chapter ID. In that case, what we're going to do is router.push slash courses slash individual course ID slash chapters and slash next chapter ID. So just make sure you added the router here. And it seems like we did all of that correctly. So let's attempt to do this now. So first, I'm going to try it in this one, which is already completed. When it reaches the end, what it should do is redirect to the next video. So I'm going to refresh and try again, just in case it wasn't saved. So let me go. I'm going to move it to the end here. And once it reaches the end, it should redirect me to the next one. And it seems like it's not exactly doing that. So let's first check if it even works for submitting, right? So I'm going to, oh yeah, I know why it doesn't work. Because we forgot to pass in the on end, the same thing I forgot in the button. Okay. So on ended and pass in the on end function. my apologies I completely forgot about it so now let's try it again just for good luck I'm gonna refresh I'm gonna go ahead and visit the end of the video and what it should do is just redirect me to the next chapter and it seems to still not be doing that then again let's go ahead and check if the completion of it works like this there we go so it does progress update oh yeah Only then it will redirect, right? So it's not going to redirect if we already finished the course, right? It assumes that we're coming back to watch it purposely, right? But for the cases where it's first time completing, it's going to redirect. And for the next one, what should happen is when I click mark as complete, since I finished all of this, the confetti should be thrown. Let's see. And there we go. We have a nice little confetti. Amazing, amazing job. So now if I go back in the browse page, I should also get 100% here. Amazing, amazing job. You finished the entire course preview right here. You have everything you need. And what we have to do now is we have to create the student dashboard. And one more thing we have to create is the individual teacher analytics to see how much each course has generated. Amazing, amazing job. The first one I wanna do is I want to go and exit the teacher mode and I want to resolve this student dashboard first. So right now that page is page.tsx inside of our app folder dashboard routes. It's this one, page.tsx, right? So a couple of things I want to do first. I don't want to keep it just like a file here because it's going to have some specific components for itself. So what I'm going to do is I'm going to create an organizational folder inside of routes called root like this. And then I'll just drag and drop the page inside. So nothing URL-wise is going to change, but now my page.dsx file has its own little folder where I can add components. And as you can see, that triggered an unsaved file inside of .next types. So I'm just gonna click on this file. I will go inside and click save. And I'm going to shut it down like that. And when you go back, make sure that you're not accidentally in the .next file. So don't go here, close this file and find your file inside of the app folder. I've seen some people make the mistake in my Discord channel where they send me screenshots of editing this file and they don't understand what's wrong. Well, because you don't edit in that one. That's just cache. Close this file, this folder and go back to the app folder right here in the new root folder page.tsx. Excellent. Now what I want to do is remove this user button. We no longer need it. So this is now inside of our navbar, right? Let's also change this from home to be dashboard. Great. And what I want to do now is I want to create a function called get dashboard courses, which I'm going to use to load everything we need for the dashboard here, including our completed courses. So let's go inside. I'm going to close everything and go inside of the actions folder and create a new file, get-dashboard-courses.ds. Let's go ahead and let's write export const get-dashboard-courses like that. And now let's go ahead and let's take what we need inside, which is a user ID, which is a type of string. And what we're going to return is a promise. But inside of that promise, we're going to have to add specific type called dashboard courses. So I want to create a type for that. So type dashboard courses like that is going to have completed courses, which for now is going to be any end array and courses in progress, which is also going to be any end array. We're going to change that right now. So I just wanted to prepare it like that. And then you can use the dashboard courses and add them in the promised result. So this is what it should look like in one line. Perfect. I'm going to zoom in now so you can see better. And above this type, let's now create a type course with progress. Whoops. Course with progress with category. That's going to be a course from Prisma client. And it's going to have a category, which is a category. from Prisma Client is going to have chapters, which is a chapter array, and progress, which is number or null, like that. And then we can replace this any with that proper type. Great. So now let's go back inside of here and let's first open a try and catch block. So I'm going to get the error And I'm going to console.log get underscore dashboard courses and error. And let's return completed courses to be an empty array and courses in progress to be an empty array as well like that. And let me just see if I did this incorrectly. I think this should work. I'm not exactly sure. why it's throwing an error like this. Let's continue developing. Maybe it will make more sense later. Weird, yeah. Okay. And now what I want to do is I want to go back in the try block and let's write const purchased courses. Purchased courses like this. Await DB. So import database. I'm just going to separate this imports. Great. And now that we have the database and we added await here, let's also write asynchronous here. Oh, that fixed the return error. Okay, great. And now let's write await db.purchase.findmany where user ID is user ID. Right? And we're going to select course. and inside of the course for this purchase, we're going to include category. So just write true. And we're also going to include chapters where is published is true. So we're going to use the purchase model and we're going to use the relations to get the appropriate course category and the chapters. And now we're going to write const courses to be purchase courses dot map purchase and we're going to return purchase.course like that. And we're going to map that as course with progress with category and it's going to be an array. So this is supposed to be in one line like this, right? So I'm just going to zoom in so you can see better. So basically, we're going to consider a course in progress the moment we purchase them, right? So we don't care if we completed 0%. The moment they are purchased, we're gonna include them in our courses with progress. And now we gonna use some logic to separate courses which are 100 complete from those who have just been started So let write for let course of courses const progress to be await get progress from dot slash get progress. But I'm going to change that to actions get progress. All right. Where were we? Right here. Get progress and pass in user ID and course dot ID. and then course progress is going to be progress which we just fetched right and now let's filter out completed courses from the ones in progress const completed courses is going to be courses filter get the course and write course.progress to be one equal to 100 like this and you can duplicate this and mark this courses in progress to be those that are not. So we can, we can make it like, you can either put not 100%, or if you also want to cover for the new case of the progress, you can write the following. Course.progress 0 is less than 100, like that. So this is going to amount. So if the course, if the course progress is less than 100, and if there's no course progress, we're going to count that as zero like that. And then you can just go ahead and return completed courses and courses in progress like that. And there we go. No more errors inside of this function. So now that we have that, we can go ahead back inside of our page.tsx. So which is now instead of a root folder here. And what I want to do is I want to mark this as an asynchronous function like that. And I'm going to go ahead and import user ID from out. So I got that from clerk next JS. And let's write if there is no user ID, return redirect with a slash like this. and now let's get await get dashboard courses which we just created in our actions and what it accepts is the user id argument and from it let's go ahead and let's extract completed courses and courses in progress perfect and now what i want to do is give this div a class name of padding6 space dash y dash four and inside create a div with the class name of grid grid dash calls dash one sm grid dash calls dash two and gap dash four like that i'm gonna create a div here and i'm gonna write to do info card and i'm gonna copy this one more time so these two divs are actually going to be info cards. And below that, we're going to go ahead. So outside of this div, which has its own grid, we're going to go ahead and we're going to render courses list, which we already have. So this is going to be reusable, right? From our add components courses list, we already use this in the search page. And in here, we're going to pass in items to be a combination of courses in progress and completed courses like that. So I made a choice that I first want to display courses in progress and then the completed courses, because I just assume that user is more interested in the ones that they're still doing rather than the ones they've completed. But you can, you know, mix and match with that. Great. So now this looks very similar, right, to the search page. But what we're going to do is add two info cards here on the top. So let's go ahead and let's create those info cards. So the first thing we're going to do is we're going to go ahead and replace this div to actually render an info card like this and this bottom div as well. Let's just write info card and keep it in the grid. So this is what you have to do. Have this grid, this div, and an info card inside. And let's go ahead and give this info card some props. So it's going to have an icon of clock from Lucid React. It's going to have a label in progress. And I'm just going to write it with a capital S with a capital P and number of items is going to be courses in progress dot length. And make sure you have this clock imported from Lucid React. Perfect. And now let's create this info card. So I'm going to go inside of this root folder and create a new folder underscore components and create a new file info dash card dot pss. Great. Inside of this info card, export const info card and return a div info card. Go back to page dot pss and import it from dot slash underscore components info card. So it's in the very same folder just right here. Perfect. Now we have to fix these types right here. So let's go ahead and give this div a class name of border rounded dash MD flex items dash center gap dash x dash two and padding three. Let's go inside and let's add an icon badge component from add slash components icon badge which we created. Let's go ahead and give it a variant of, well, variant like this. And we're going to have the variant from props. So variant comes from here. And icon is also going to come from here. And we're going to remap it to capital icon like that. And let's just quickly write in the interface for our info card. So info card, info, whoops, info card props is going to have number of items. to be a number. I completely forgot the right interface here. Sorry. Interface info card props. We're going to have number. We're going to have a variant, which is an optional, either default or success like that. We're also going to have a label, which is a string. We're going to have an icon, which is lucid icon from lucid react. So just make sure you add those. and now let's go ahead and attach those info card props to here perfect and now we can extract the number of items the label as well great and now let's go ahead and pass in the icon as icon right here perfect and great now we have our clock icon right here below that let's add a div and let's write a paragraph here which is going to render our label let's give it a class name font-medium and below that add a new paragraph which is going to render number of items like that and then number of items is equal to one it's going to be course otherwise it's going to be courses so in progress zero courses like that and let's write class name text-gray-500 and text small like that perfect so we finished the info card so as you can see it says zero courses in progress that's because this one is fully completed so it's no longer in progress right and now let's go ahead back inside of page.tsx and let's copy and paste the info card and let's call this one completed and it's going to use completed courses length and the icon for it is going to be check circle from lucid react so just make sure you input check circle and it's going to have a variant of success like that and let's just refresh this and there we go now it says that one course is completed and it says zero in progress and if i go inside of this course right and let's say i'm going to select wrapping up here and i'm going to mark it as not completed so this is going to make this 75% complete. And if I exit now, there we go. Now this one is in progress because it's 75% complete. Perfect. Great job. So what's left to do is to create this analytics tab right here for the teacher. Let's finally wrap up this by creating the analytics page right here. So one action we're going to need before we can actually render anything on the page is going to be called get analytics. So go inside of your actions and write get-analytics like that, .ds. And let's go ahead and let's import database from at slash libDB. And let's import course and purchase from at prisma slash client. Now let's write type purchase, purchase with course to be purchase and course, which is a type of course. So just make sure you have not misspelled purchase like I did. Great. Now let's write const group by course to be purchases, which accept a purchase with course, which is an array, and write const grouped to be a type of object which has course title, which is a string, which is a number, like that, and an empty array. Now, let's go ahead and write purchases.foreach, get the individual purchase, const course title is equal to purchase.course.title. if grouped, if there is no grouped course title, let's go ahead and create it by saying grouped course title to be zero. Otherwise, grouped course title is going to get increased by purchase.course.price and put an exclamation point at the end because we know we're going to have it. And then just return group. Great. So we have our grouping function here. And now let's create export const get analytics. Like that is going to be an asynchronous function, which takes in the user ID, which is a type of string. You can open a try and catch block. Let's get the error first and let go ahead and let consular log get analytics and log the error and return data to be an empty array total revenue to be zero and total sales to be zero as well Now let's go back inside of the try group right here and let's fetch the purchases. So const purchases are await db.purchase.findMany where course is created by this user like that. So we use user ID to match and let's include course of each purchase. Great. Now let's write const grouped earnings. So now we're going to know how much each course has made because of this function right here. So we create an object with the title of each course and we calculate the price that it has, right? So now we can write group by course and purchases like that. Now let's write const data object.entries grouped earnings.map. And let's go ahead and open and write an array. course title total so let me just zoom out a bit and open and return an immediate object name course title and total to be total like that and now write cons total revenue total revenue to be data.reduce get the accumulator the current and just combine the two so accumulator plus current.total and begin with zero. And const total sales are just going to be purchases.length. And let's just not misspell the constant here. All right. And what we have to do at the end is return data, total revenue, and total sales. Great. So we have our get analytics function. And now we can go back inside of the analytics page. So let's find it inside of the app folder, dashboard, routes, teacher, and we have the analytics. And let's go ahead and let's mark this as an asynchronous function like that. And let's go ahead and let's import out from clerknext.js. Let's import redirect from next slash navigation. Let's import getAnalytics from getAnalytics. And now let's go ahead and let's get the current user ID using auth. And let's write, if there is no user ID, return redirect to the root page. Otherwise, go ahead and call the getAnalytics function. So await, get analytics with user ID inside. And now let's extract everything we need from here. So data, total revenue, and total sales. Perfect. And now let's go ahead and give this div a class name, padding six. Let's create a div, which is going to be our grid inside. So grid, grid-calls-1, mdgrid-calls-2. gap-4 and margin-bottom of 4 as well. And inside, I want to render a data card component, which we don't have yet. So save this file. We're going to get this error and go inside of analytics and create a new folder underscore components. And inside, create a new file data-card.tsx. Let's export const data card like that. It'll just be a div saying data card. let's go back and let's import data card from dot slash underscore components data card like this perfect so now what i want to do is i want to install a chat cn package called card so i'm going to shut down this app and i'm going to write npx chat cn dash ui at latest add card like this. Confirm the installation and you can npm run dev your app again. Let's go back inside of the data card here. And first I want to create an interface. So interface data card props value number label string should format an optional Boolean. So we're going to reuse the data card two times. One's to display the number of sales and the other time to display the total revenue. So we're going to use this should format Boolean to decide whether we want to format the value. Because if it's total revenue, I want it formatted in dollars. But if it's total sales, I just want to display a number. So that's why this is an optional Boolean. Perfect. Let's assign these props, data card props, and let's add value, label, and should format. Let's remove this div, and instead let's render the card element from s slash components UI card. So just make sure you have this, and inside add a card header from s slash components UI card again. So I'm just going to start collapsing this so they import in that manner. Let's give this card header a class name I'll flex, flex-row, items-center, justify-between, and space-wide, there's zero, and tb-2, like that. Inside, open a card title, again, from as slash components UI card, and just render a label inside. So make sure you have the card title from here. And give this card title a class name, text-sm font-medium, like that. And let's go back to our page now and go ahead and pass it a label of total sales like that. And refresh your local host. And now in here, you should have a small card with this label right here. There we go. So it says total sales. And now let's go outside of the card header and let's add card content here from at slash components UI card. So make sure you have that. and inside open a div and a render should format question mark is going to use format price from at slash lib format and go ahead and pass in the value otherwise just display the value let's go ahead and give this div a class name of text to excel and font dash bolt perfect and now we can go back inside of this data card and we can go ahead and write value to be total sales like that perfect and now we have total sales one and you can copy and paste this and rename this one total revenue and write total revenue inside and let's add should format and there we go now it looks like dollars and if i expand they're next to each other perfect what we have to create now is uh the chart right so let's go ahead and let's install a package called recharts so i'm gonna go inside of my terminal i'll shut down the app and npm install recharts like this so just wait for a second to this uh for this to install and great and you can do npm run dev again and you can refresh this page and you're going to go outside of this div encapsulating this to data cards and just render the chart component so we don't have the chart component yet so we're going to get an error so let's get inside the underscore components and create a new file chart.dsx like that and inside we're going to mark it as use client and export const chart like this and return a div same chart. Go back to page and then you can import that from dot slash underscore components chart the same way we did with data cart. And now we have a text same chart here. So inside first thing I'm going to do is create an interface chart props. Give it a data which is an object of an array and the object has a name which is a string and the total, which is a number. Let's go ahead and let's map those chart props. So we're going to extract data. And now we go back to page and pass in the data to be our data from get on Linux like that. And as you can see, it perfectly matches our structure. Great. Now inside of the chart, let's go ahead and let's wrap this entire thing in a card from s slash components UI card. So just make sure you add this import. And while we're here, let's go ahead and let's import the following from recharts. So I'm going to add a bar, bar chart, responsive container, x axis and y axis like that. Inside of here, let's go ahead and let's create a responsive not responsive cookies, responsive container, which we just imported, give it a width of 100% and a height of 350. Inside, render the bar chart from recharts. So we have that imported. Passing the data here to be data. Oops. Data. And inside, add an x-axis, which we have imported. It's going to be a self-closing tag and passing the data key to be name stroke to be hashtag one, two, three, four, five, six. So six eights like that. On size to be 12 tick line to be false axis line to be false as well like that. And now you can see I have advanced web development here, which is the name of my course. So that's on the X axis. And now let's create the y-axis, also a self-closing tag. Give it a stroke, again, of six eights. So one, two, three, four, five, six. Font size of 12. Ticked line is false. Axis line is false. Tick formatter is a function which takes in the value and just returns $$ value. Like that. and let's render the bar which is also self-closing tag which is going to have a data key of total bill of hashtag 0 total\n3, 6, 9, 8, 1, radius, an array of 4, 4, 0, 0. Like that. And there we go. We have our chart here. Perfect. Amazing, amazing job. You finished the entire project. So you can now play around and create a couple of more courses. You can play around and add some more chapters, purchase some new courses, and you're going to see how this site is growing. Great. So one more thing I'm going to show you before we deploy. So right now, one thing that I really wanted to do for this project, but I didn't manage to do was implement the Stripe Connect. So right now, when I purchase a course, it goes to my global Stripe store, right? So if someone else comes to my page and creates a course, they're not going to get their cut, right? That's not how this website works. If you want to do that on your own, you can explore Stripe Connect. The reason I didn't do it for this tutorial is because the setup for it seemed like I needed to have a ready business to start. So I didn't really understand how I was going to explain that, especially to beginners who are watching this. So I skipped it for now because obviously I don't have enough knowledge in that area. But I am going to explore it for some future projects because it's something that we need to understand and I hope that we can build one day. So what I am going to show you for this is how to make this course platform your own so that only you or maybe one or two user IDs can create courses and everyone else can only purchase and watch them. So that's one more thing I'm going to show you how to do before we move on to something else. so now if you want to you can follow this part of the video so I can show you how to protect this teacher mode so that only a specific user can see it and so that only specific user can create courses and everyone else can just watch them and purchase them so what I'm going to do is I'm going to go and inside of my lib folder create a new file teacher.ts and inside of here I'm going to go and write export const is teacher, which is going to accept a user ID, which is going to be an optional string or null, like that. Go ahead and return user ID and compare it to process.environment.next underscore public underscore teacher underscore ID, like that. And I'll copy this and go to your environment file and paste it here. And in here, you put the user ID that you want to be a teacher for this website. So you can go that by going to clerk.com, select the project, go inside of users and find the user that you want. And then just copy the user ID like that. And that's it. That's all we need from clerk and go inside and paste that right here. Perfect. And now let's go ahead and let's protect our navbar. So this teacher mode only applies to that user. So I'm going into navbar routes, which is inside of our components folder. So our global components. And in here we have a button teacher mode, right? So what I'm going to do is I'm going to write, well, first I'm going to fetch the user ID, right? So this is a client component. So we have to do that using const user ID from use out from clerk. So make sure you imported use out from clerk right here. And then make sure you import is teacher from s slash lib teacher. And now we're going to extend this ternary here by adding is teacher user ID, like that. Another question mark here, which is going to render this link. Otherwise, we render null like that. So a double ternary here, just like that. Perfect. And as you can see, the teacher mode is still visible here, right? But what I'm going to do now is I'm going to log out into another user and see if it's still visible for the other user. And there we go. I logged out and now the teacher mode cannot be seen from the nav bar here, right? So I can only browse the courses which are published As you can see in this account I have not purchased this course So I seeing the price here Perfect So now I gonna go back and show you how to protect with the other things Well, in fact, I don't have to go back, right? So it's better if I keep in this guest account, right? So this one that cannot see the teacher mode. But what I can do is I can still manually go to slash teacher slash courses like this. So I can still do this, right? And then I can click on the new course and there we go. I can now create a new course. Well, we want to protect from that as well. So the way I'm going to do that, well, I'm going to kind of experiment. So what I'm going to do is I'm going to go inside of the app folder, inside of the dashboard, inside of routes teacher right here. And I'm going to create a layout file here. So I'm just going to try it out. I'm not sure if this is going to work. So we're trying out this together and I'm going to write SFC teacher layout. We need the children, right? And let's map the children to be react.react node. And inside, just go ahead and return the children. I think we can, can we just do that? Maybe we need a fragmented list. So children like this. And nothing should change much. There we go. So everything is still working inside. But what I'm going to do is I'm going to get the user ID from out using clerk next JS. and then I'm going to write if question mark, sorry, exclamation point is teacher. So if the teacher is not this user ID, return redirect from next slash navigation back to the root page like that. And there we go. So that works. So if I manually try to go to teacher courses, I'm redirected back. And since this is a layout file, it's going to apply to all of those inner files as well. Perfect. So we protected our front end. And now what we have to do is using that same logic also protect our backend. So we have to go inside of the API file right here. And let's go ahead inside of the upload thing first, inside of core right here. And we're going to modify the handle out. So we're also going to write const is authorized. The is teacher from a slash lib teacher and pass in user ID like that. So just make sure you have the is teacher and we're going to write if it's not user ID or if it's not authorized, throw the error like that. So now only the teacher can upload files. Perfect. And now what we have to do is you have to go inside of courses and let's go ahead and let's protect the creation of the courses. So if there is no user ID or if it's not a teacher user ID. So whenever I add is teacher, just remember you have to add that using a lib teacher like that. So if it's not a teacher, it's going to respond to another way. So this is if someone bypasses the front end and directly uses the API. We're also going to protect that. Great. So we have the protection of the creating the course. And now let's go ahead and protect deleting the course. So again, or if it's not a teacher, user ID. And we have to do the same thing in the patch function below once more. So if it's not a teacher, again, user ID. Like that. We did that. Now we have to go to attachments. So for creating an attachment, again, if it's not a teacher, make sure you import that user ID. Great. Now go inside of attachment ID. Do the same thing for deleting. If it's not a teacher, user ID. And we have to do, that's it. So we cannot update attachments. Great, so attachments are done. Now for the chapters. So same thing. So if you're not a teacher, user ID, like that. Even though I think it's enough for us to just protect creating of the chapters. Yeah, I believe this is actually enough. We just have to protect creating the chapter, creating the attachments and creating the courses. Because if we cannot create a course with our user ID because we are not a teacher we also not going to be able to delete or update or modify because we always look for the owner ID So I believe we didn have to add it to attachments as well right But you get the point So you have to add it. You have to try it out yourself and see where else you need to add it. I believe that it's enough that you just add it to courses route.ts. So this is the important one, the post function of the course. Make sure that it needs to be a teacher to create a course. Yeah, I think that's enough because if you don't have a course, in that case, you can't create attachments, you can't create chapters. Yeah, so I'm actually going to remove it from the chapters like this. I'm going to remove this from the chapters. So let me just modify it like this. Yeah, sorry for that, but I'm just trying this out now as I write this. So the layout one is important. It's important that's in in the API courses in the post function, but it doesn't need to be in deleting and updating the course. It doesn't need to be there because if we cannot create it, it doesn't matter. Great. So the only one I changed in the API in the end is the upload thing right here. And I changed the course post route. So that's the only things that I changed. And from the front end, I did the navbar routes and I added a layout to teacher layout so it redirects the user and I created this util themselves. Great. And now what I want to just check is just confirm that I can go back to the old user which has the user ID and still create a course. So just to check that we didn't mess something up. All right. So I'm back here. I can click on teacher mode and I'm not redirected back. Let's create a new course. Test like that. Let's see if it's going to work. And there we go. So it's still working. Perfect. So we can still create courses if we are an admin. Perfect. Amazing, amazing job. You completed the entire thing. What I'm going to show you now is quickly how to deploy it to Versal. So deploying to Versal is going to be quite simple. So let's go inside of our app right here. And first thing I want to do is I want to go inside of package.json right here. And I'm going to add a new script here named post install. like that and go ahead and write prisma generate like that perfect once that is done let's go ahead inside of our project shut down the app you can shut down the strike tool and go ahead and write git add and get to meet final like that. So just commit everything you have in your project. Now go to the GitHub and create a new repository. So this one is going to be lms-tutorial. It's going to be private in my case, and I'm just going to create a repository. After this is created, go ahead and pick this option, an existing repository. So you can just copy these files, these lines, and paste them here, and just press enter. and that's going to push that to this repository. So I can refresh the file now and there we go. It's all right here. You can see all of my code. And now we go to versal.com and we click add new project. And in here, I have this new LMS tutorial. So I'm going to click right here. And what we have to do is we have to go inside of our dot environment file and copy everything inside. So just select everything and copy. and go ahead and click on environment variables, pick the first field, and just paste the entire thing inside. Now, obviously, there are two things we're going to have to change. We're going to have to change Stripe Webhook secret and next public app URL, but we can only do that after we deploy because we don't know what the URL is going to be. So click deploy, and let's wait for this to finish. So I'm going to pause the screen. If I get any errors, I'm going to show you how to resolve it. If not, we're going to continue with changing the variables. There we go So my project deployed without any issues So now I have to find the domain for this LMS tutorial right So in my case it is lms So I can go ahead and open and visit that application and then redirect it to the sign-in page. Perfect. So I'm going to go ahead and copy my URL link right here, or you can copy it from here, right? And now we have to go inside of settings of this project. So let me just expand this so it's exactly as you see it. So go inside of settings. Just make sure you're in the LMS tutorial here. Go ahead and copy the domain you have. Go into settings and go inside of environment variables. And go ahead and find the next public app URL. And change that to the new one. So HTTPS LMS tutorial and remove the backslash like this. and go ahead and copy this. So don't remove this already and click save. All right. And now we have to change the Stripe webhook secret. So we have to go back into Stripe right here and select developers again, go back to webhooks. And as you can see, we have a local listener here, which is our localhost 3000 API webhook. We now have to add a new endpoint with our website, right? So just make sure you're in test mode here. and click add endpoint under the hosted endpoints here. And go ahead. Now I have to confirm my password. So I'm just going to do that. All right. After I've confirmed my password, now I have this option, listen to Stripe events and add an endpoint. So what I have to do is add my endpoint URL. So that's going to be my page, which I just deployed, which is lmstutorialversoul.app slash API slash webhook. So the same thing that it was, but instead it was localhost, right? Well, now it's my website slash API slash webhook. And we have to click select events. And we have to find the checkout. And go ahead and select checkout.session.completed. So that's the only one we are looking for. And click add events. And click add endpoint like this. And now click on signing secret reveal. And copy this secret. Go back inside of the environment variables. Find the Stripe webhook secret and change that as well. there we go, and click save, perfect, and now that you saved that, go back to your deployments, pick the working one, and click redeploy, and don't check this, and just click redeploy, like that, so again, I'm going to pause this, and wait for this deploy to finish, and there we go, so another successful deployment, and now I'm going to go ahead and visit my website again. So you can either use this link, which is a specific deployment, but the real link is this one. So the short one. So I'm going to open this app again, and I'm going to go ahead and log in with my other account. So I logged in into an account where I am not the teacher and where I haven't purchased any courses yet. So I'm going to go ahead and test the webhook endpoint now. As you can see, it's waiting for events. So I'm going to attempt to purchase this course now. There we go. I have the preview. And just remember, if you want to remove this MUX box right here, and if you want your videos to be longer than 10 seconds, you have to add a credit card to MUX. And even then, you have a generous free tier. But if you just want to use it for API test purposes, this is completely fine. I'm going to click on Enroll here, and I'm going to get redirected to the Stripe checkout page. There we go. I'm going to go ahead and use a dummy page here. a dummy name and I'm going to click pay. And let's go ahead and look right here. If there are any events going on, I'm going to refresh here. Let's see. And there we go. Checkout session completed 200. Okay. Which should mean that I officially purchased this course and it's completely unlocked. I can mark it as complete. I can go outside and there we go. Great, great job. You finished the entire project. And I'm so happy you watched this to the end. Thank you for watching and see you in the next one.",
  "transcript_chars": 521465,
  "transcript_filled_at": "2026-06-06T15:54:49.969702+00:00",
  "transcript_filled_by": "tk-bulk-groq-retry-20260606"
}