hey there my name is Antonio and today we will learn how to make our own language learning software as a service users will be able to pick a course dive into a lesson and interact with questions using AI generated voices we are also going to have a success and error sounds and a dynamic heart system to keep Learners engaged and motivated once we reach the end of a lesson we will hear a success trumpet and see our statistics such as Hearts left and points gained in case a user Runs Out Of Hearts they will see an appropriate popup but don't worry users will be able to refill their hearts by going into a previously completed lesson in a form of a repeated practice where they can regain their hearts by answering correct questions we are also going to have a shop system in which users will be able to exchange their newly gained points to refill their hearts we'll also feature a leaderboard ranking users by points as well as mil Stones like hitting thousand points in the quest stab and of course to make our app a real software as a service we are going to have a premium tier which will give the user unlimited hearts in an exchange for a monthly subscription using a stripe as you will see in the following example Pro users do not lose hearts on wrong answers besides the main application we are also going to build a landing page and an entirely functional admin dashboard where you will be able to add list and modify courses units lessons challenges and Mark correct answers or change their audio and image files and to wrap it all up we are going to ensure that the entire application is fully responsive on all devices so without further Ado let's get started let's get started and let's configure our next JS application in order to do that we first have to check if we have the necessary system requirements so head inside of your terminal and write node DV to print out your current node version after you confirm that you have the necessary system requirements go ahead and run the command for automatic installation I'm going to go ahead and copy the command and I'm going to paste it inside of my terminal after you've pasted the command you're going to get get prompted to enter your app name so I'm going to write lingo I'm going to be using typescript so I recommend that you do the same and select yes for this option same for Sint Tailwind CSS as well Source directory I'm going to select no I recommend you do the same so you have the exact same results at least during the first time you're doing this tutorial app router is very important to select yes here as well and I'm not going to change the default import alas if you know what you're doing you can change it but again I recommend that you do the exact settings that I did so you have the best results and now let's wait a minute for this to install after the project has been successfully installed you're going to see this success message at the bottom of your terminal so let's go ahead and let's open our new project after you've opened the project if you're using visual studio code you might get a prompt which will ask you do you trust the authors and you can press yes if you get that prompt when you're inside of your application you should see a structure similar to this you should have the app folder with a favicon globals layout and page. PSX you should have the node modules the public folder and some configuration files most important ones are the Tailwind config because we're going to be using Tailwind for styling the post CSS which enables tailwind and TS config which is used for typescript before we run this project I want to add a library which we are going to use to build our own component library for that we're going to be using a Shad CN UI if you head into the documentation you're going to see that it says that this is not a component Library it's a collection of reusable components that you can copy and paste in your apps and what they mean by saying that it's not a component library is that you do not install this as a dependency so we are only going to install specific components that we need and they are not going to be stored inside of node modules as you might expect so let's go ahead and actually do the installation so you can see what I'm talking about head to the installation Tab and select nextjs or you can just follow my steps here so we already have a nextjs project so we can skip the first step and let's go immediately into the Second Step let's run the CLI command to initialize shat CN UI so I'm going to be using npn throughout this tutorial so every command I run is going to be for npm make sure that you are inside of your project and open the terminal again confirm again inside of the ter terminal that you are inside of the project you want and paste the npx shat CN UI latest init command I recommend that you select the default style because if you select the New York style that is going to give you different packages to work with specifically for icons so again if you want the best results from this project do it exactly as I am so I'm going to use the default style and I'm going to choose the Slate color I'm going to select yes for the CSS variables and now let's wait a few seconds for this to install there we go once this has been installed we can go ahead and do mpm run Dev this will run your project on Local Host 3000 so let's go ahead and visit this application you should be seeing a screen similar to this what I want to do now to wrap up this first chapter where we set up our project is change the font of our application and then I want to go ahead and add a button component so we'll learn how to do that so let's go ahead and change the font we're going to use the nunito font for this project so let's go inside of the app folder and let's go inside of the layout file inside of here we can see that we have a predefined inter font from Google so what I'm going to do is change this import to be nunito then I'm going to use the denito here in this constant and I'm going to change the constant name to something more generic like a font and now I have an error inside of my body element so let's go ahead and paste that here and let's go ahead and save our project and in a few seconds you should see the changed font inside of your application now what I want to do is clear out this entire page this code the code for this page is located inside of app folder page. PSX so I'm going to go ahead and close this return method and I'm going to completely remove it and then I'm going to remove the import image as well because we do not need it and let's go ahead and return a div which is going to very simply say hello lingo and there we go I'm going to go ahead and zoom in and we now have a text which says hello lingo so now I want to try if my Tailwind is working and I want to show you a useful Tailwind CSS extension let's go ahead and get give this a class name of text green 500 after I save you should see the change as well if you're wondering how come I have this little box here which indicates the color and how come when I hover over my classes I can see the exact CSS inside that is thanks to the Tailwind Library so go ahead and search for Tailwind inside of your extensions Tab and the very first is the extension that I am talking about so besides having this little useful color indicators and hover displays the CSS we also have an autocomplete for example font Dash and you can see all the options which you can do so I'm going to do font bold and there we go once I save you can see that my font has changed to bold you can also use this extension to verify whether a class name in Tailwind exists for example text extra small exists and I can see that but because when I hover I can see the exact underlying CSS but if I change this to something like 20 XEL and then hover you can see that this doesn't exist so nothing is displayed great so let's go ahead and do the following to add the a button from shaten UI I'm going to go ahead and open a new terminal again just make sure that you're inside of your app and run npx shat CN UI at latest and I'm going to go ahead and write add button after a few seconds the button component will be added inside of our project so let's go ahead and find where it was installed go ahead and visit your components folder UI folder and now you should have a new button folder as you can see this button this button file was not added in our node modules instead we have the actual source code inside of our project this is going to be extremely useful because we're going to build our own component library to give our app that cartoonish 3D look that the Dual lingo application has so let's go ahead and use this button I'm going to go back inside of my homepage here I'm going to remove this and I'm going to add a button component from at/ components UI button and I'm going to right click me and there we go you can now see how we have a nice button and you can see how it already G gave us some predefined button variants using the class variance Authority Library so we have the default variant the destructive outline secondary ghost and Link and we also have the sizes this size and variant keys inside of this object are translated to props when used as a component so I can change the size to be large and I can change the variant to be destructive like that and I can very easily modify a variant so I can change this from BG destructive to BG green 500 for example and there we go that immediately changed the color so I'm going to undo this change and leave it like this you can just as easily add a new one for example premium let's make this BG Indigo 500 and text white I can now go back inside of my page. vsx and you can see how I'm going to have type safety here and I can select the premium variant and there we go now it's purple that this is exactly what we are going to do to build our own component Library later on because we're going to have to style our components to have a 3D cartoonish look great great job now let's go ahead and let's build our component library for buttons before we do that I quickly want to show you how we are going to create the r inside of a nextjs application so for all routes be that an Avi route or a client side route we are going to use the app folder so inside of the app folder in order to create a new URL you can create a folder and simply give it the name that you would wish the URL to be so for example I'm going to write buttons and in order to turn this into an actual route we have to use a reserved file name which you might have already guessed is page. TSX so once you create a new file page. PSX inside and if you attempt to go to Local Host 3000 slash buttons you're going to get an error that the default expert is not a react component in page SL buttons so it recognized that this is a URL but it gave us an error that is because besides page being a file convention which we have to follow there is another convention which is quite important and that is that every page. vsx needs to do a default export so let's go ahead and write the following const buttons page the name does not matter the only thing that matters is the default export so I'm just going to write buttons page and then I'm going to export default buttons page once I save there we go we have the buttons page so if you try and rename this into something else for example a component. CSX you can see that now this is no longer a route it is a 404 page and if you change something from a page to a non page you will most likely get this weird unsaved file inside of your next folder you don't have to worry about unsaved or uh conflicted files inside of this next folder because all that it does is cache so feel free to uh simply click on this one unsaved if you even got this and you can just save this file in case you think you've did it wrong you can always remove this folder completely so let me just demonstrate that that to you in case it happens so I'm going to go ahead and shut down my application so make sure it's not running so you should get the site can't be reached in your browser and I'm going to go ahead and completely remove the next folder and on my next npm run that folder will be generated again so there we go if I go back I have the do next folder again great so now let's go ahead inside of our app folder buttons and I'm going to turn this back into a page there we go so if I refresh here again I can see the buttons page so just make sure you on Local Host 3000 SLB buttons so what I want to do in here is I want to render all buttons and all of the variants which we are going to have so you already know how to import the button so I repeating that here again and first I'm going to go ahead and add a primary button but like this so I'm just going to write that below that I'm going to go ahead and add a primary outline like this and let's go ahead and give this a class name of padding four and let's give it a space y4 so I want them equally spaced out uh and let's also actually turn this into Flex Flex call so I want them uh to be one below another and I'm simply going to limit their width so max width is going to be 200 pixels it really doesn't matter what I'm trying to do here is just a demonstration of all the types of buttons which we are going to have inside of our app so this is probably what you are seeing because I'm working with my browser zoomed in great so we now have a primary button and we have a primary outline button but they look exactly the same so let's go inside of the button which is located inside of our components UI folder and let's go ahead and modify the first thing I want to modify are these default classes right here for example I don't want to have rounded MD I want to have rounded XL so once I save this you can see how both of my buttons just got a more rounded Corner what I want to do next is I want to give it an uppercase value so whatever I write I want it to be uppercase and I'm also going to turn on tracking wide so my letters are more spaced out between and one more thing I want to do is change from font medium to font bold there we go so this is the Baseline that I want to work with what I want to do now before I change my variance is change my sizes so the default size of my button is not going to be h10 it's going to be h11 I just want them to be a little bit bigger for the SM option I'm going to remove a rounded MD and for the large option I'm going to change the height to go to 12 I'm going to remove arounded MD and for Icon we can leave it as it is and I'm going to add just one more size which is going to be rounded which is just going to do rounded pool great so now we have that prepared what I want to do now is I want to go ahead and remove all of the variants except the default one so let's go ahead and let's actually remove everything inside of the default and let's style it again from scratch I'm just going to go ahead and disable my co-pilot so it doesn't cause us any problems in this tutorial let's give it a background color of white let's give it a text color of black let's give it a border slate to 100 border is going to be two border bottom is going to be four pixels so we're going to use this uh specific value so you can always break from Tailwind if if it doesn't exist in your for for example border bottom three border bottom four or you can write four pixels directly so it looks like our four actually exists so we can use it like this border bottom four on active border bottom is going to be two on Hover BG slate is going to be 100 and let's give this a text slate of 500 there we go so this is our default button you can see that when I click on it it kind of has a 3d effect because you can see how this border on the bottom gets evened out with the rest of the borders so let's actually go ahead and change this to be our default button like that and then I'm just going to go ahead and copy this and and change this one to be primary so now we're going to develop this two primary and primary outline for the default one we don't have to explicitly pass in the variant because if you take a look in here in our default variants the variant which will be chosen if no prop is passed is the default one which is the one we have just created so now what I want to do is create a primary one so let's go go ahead and write this one from scratch again and then the rest we are just going to copy so for primary I'm going to give it a BG Sky of 400 and here's the thing I want us to do I want us to go into page and just give this button a variant of primary so that we can see exactly what we are doing so primary is going to have a BG Sky of 400 a text primary foreground is it's going to have hover BG sky 40090 like this border Sky 500 border bottom four active border bottom zero so there we go now we have our primary button now let's go ahead and create a primary outline so primary outline is going to be quite simple background is going to be white text is going to be Sky 500 and on Hover BG is going to be slate 100 let's go ahead inside of here and let's give our third button a variant of primary outline there we go so this is going to be our outline button not too much effects on that one but you can see that this big button buttons have a cool 3d effect great what we have to do now is do the same thing but for more variant that we are going to use throughout the app so let's prepare those other variants I'm going to copy our last two buttons here and I'm going to go ahead and call this secondary and call this secondary outline so now we should have this which look exactly the same as primary but we are already going to change this to be secondary here so you should have an error for this two variants and they should look completely naked now let's go inside of the button here and what we can do is copy this primary and primary outline because they're going to be quite similar so let's change this uh to be secondary and change this one to be secondary outline and now let's change this from BG Sky 400 to BG green like that so if you want to you can leave it like this or you can go ahead and modify this uh even further so what I'm going to do is change this to BG green 500 because I want it to be just a little bit darker uh then what I'm going to do is on hover I'm going to use BG green 500 again and for the Border I'm going to use BG green 600 like this there we go and the rest is completely fine and now what we can do here is simply change this from text Sky 500 to Green 500 uh or uh you can actually leave it like this and we can leave this to be slate exactly like that perfect so if you want to you can you know play around and modify the colors a bit I just want them to be as the app you saw in the demo so now let's copy these two buttons and we're going to create a destructive property here but we're going to call that danger so danger and danger outline so I'm going to change this two to danger and danger outline so again this two should be naked now but we're going to go ahead and copy and paste our secondary inputs here and let's replace this one to be our danger and danger outline so instead of using BG green here we're going to be using uh background Rose like this so we have danger and we can leave it exactly as it is and for the danger outline again change from text green to text Rose there we go so you can see how our component library is very easy to create thanks to shat CN UI what I want to do now is go ahead and create a couple of extras here so I'm going to go ahead and prepare my super buttons which we're going to use for our premium components right for pro users so I'm going to change this danger and this danger to say super so let me just scroll down they should be here at the bottom and I'm going to change these two to be super and super outline let's go inside of the button let's copy the last two here don't forget to add a little comma here and change this from danger to Super so instead of being rows these are going to be Indigo and text is going to be Indigo as well and you can leave it as it is great and now I want to go ahead and I want to create one individual um component here which is not going to have its matching outline and that's going to be called a ghost button so I'm just going to give this a variant of ghost it should be at the bottom and it should be completely naked so I'm going to write ghost and I'm going to give this a BG transparent I'm going to give it a text slate 500 I'm going to go ahead and give it a board of transparent border of zero and hover BG slate 100 so just a very simple ghost button which we can use it could this is technically an alternative to our default button so technically you could make it as a default outline if you want to so I'm just going to creating as many options as I'm going to need for my project here and the last one I want to create is the side bar ones so the sidebars are going to be kind of unique some would say they might not even fit here perhaps they could be custom components but I found it to be useful to have those two variants as well so I'm just going to go ahead and do it uh I'm going to go ahead and copy these two values from Super and I'm going to go ahead and change from Super to sidebar and I'm going to change the variant as well so sidebar and sidebar outline let's take a look at them here at the bottom they're completely naked so I'm going to go ahead and create the sidebar one so sidebar is going to have BG transparent it's going to have text slate 500 border to border transparent power BG slate 100 and transition none so I want transition none because I want it to be behave very fast uh inside of I want it to behave very fast uh when it's in the sidebar you're going to see that uh later of course so we have the sidebar and now let's go ahead and create a sidebar outline so this is going to be BG Sky 500/15 text Sky 500 border Sky 300 border 2 hover BG Sky 500 and the opacity of 20 and transition none again there we go so once in the sidebar and inactive it's going to look like this but once clicked and once we redirect to that route it's going to look like that great so I believe that is all we need for now if I forgot something we're going to come back to this and revisit later but there we go we just built our first component set using shats n UI and you can already see how this changes the app completely so now that we are going to use this type of these types of buttons you can clearly see how we change the tone of the application into a playful cartoonish and a 3D style exactly what we were aiming for as always you can re visit my GitHub and you can find this exact source code if you think uh you're you did something wrong or you're having some problems great great job what I want to build next is our marketing or landing page and while we do that we are also going to learn about route groups and how to reuse layout components so let's go ahead and do the following we learned that once we create a new folder like buttons and put page. CSX inside that will turn it into a route but what if you want to create a folder which is purely organizational and should never even if I put page. vsx inside of it put that folder name in the URL well we can do that so no you don't have to keep your root page. CSX inside of the app folder instead you can do something like this I'm going to create a new folder here and I'm going to call it marketing and inside of that I I'm going to drag and drop my page. vsx component and now let's go ahead and try and go back to Local Host at 3000 as you can see nothing has changed so this is a marketing page there we go so I successfully moved my page. vsx from the app folder inside of this marketing folder but my URL for the root page hasn't changed so so that's what I want to achieve with this route groups so route groups are folders which can still be used to render routes by using the reserved page. vsx file but the difference is if you wrap them in parentheses that means that this part will not be part of the URL so it's very useful for organizing your routes another thing that is very useful is that you can also create a layout file inside of it so layout files are similar to page files in a sense that they are a reserved file name they also need to have a default expert inside of them but there's a catch so let's go ahead and do the following I'm going to write marketing layout here and very simply I'm going to return a div and I'm going to write layout and then export default marketing layout once I save this file the error is gone but my page seems to have disappeared we can only see the layout text that is because in order to make this layout files functional we actually have to add a children prop so let's write type props here and let's write children to be a type of react react nodes also a quick tip if you don't know you don't have to import react in a file when working in nextjs so let's assign this props here so we have type safety and let's extract the children and now instead of rendering this let's render children instead and there we go now we can see a page inside so what did we actually achieve with this layout. PSX well you're going to see that in a moment so what we can do now is we can define a reusable layout which is going to wrap any of the children inside of this layout so what does that exactly mean well let's go ahead and do the following let's give this div a class name of minage screen and flex and flex cor and let's give it a back background of red 500 so we can test out whether it's actually taking 100% of my screen great so now that we know it's taking up 100% we can go ahead and remove the background color now what I want to do is wrap my children inside of a main element and then I'm going to give this main element a class name of flex One Flex Flex column items Center and justify Center there we go so you can see that now every page or route that I create inside of this marketing folder will have the exact same outer layout so if I create a new folder for example frequently asked questions and inside a page. vsx and if I go ahead and return this frequently Asked question page not Pages like this and I will just return a div frequently ask questions page and now if I go to Local Host 3000 slf AQ you can see that it has the exact same Styles but if I move the FAQ page in the app folder for example now it doesn't have those Styles so that's what the layout file is useful for in a combination with a route group so I'm going to remove this FAQ file completely and now we have this errors in this page but we can close that so this is that. next folder that I was talking about and I showed you how you can resolve that so you can just save that unsaved file let's go back to our Local Host 3000 and let's demonstrate how this layout can be used even more effectively so above the main element here I'm going to add a header component and let's go ahead and create this component header. DSX and instead of exporting this as default let's do export con header because this is a component not a page and I prefer named exports when it comes to components so in here I'm going to go ahead and I'm very simply going to return a header element I'm going to give it a class name of height 20 full width border bottom to border slate 200 and PX of four and I'm going to write out header inside then I'm going to go back to the layout and I'm going to import this header from slhe header and there we go you can now see how I have a header component inside of here so if I were to create again for example uh I don't know a docs here page and inside page. vsx so you didn't have to do this I'm just demonstrating here so docs page docs page and if I go to Local Host 3000 docs you can see that docs also has a reusable header so the question is when I changed from localhost 3000 to/ docs did this header component rerender well no because layout file ensures that what is inside of it will not be rendered by the children rendering themselves so that's another thing this layout files are really useful for so now I'm going to remove this docs page we are not going to need it but besides the header I also want to prepare a footer component so I'm going to go ahead and go inside of a footer export con footer here and let's return a not a div let's return a footer with a class name of hidden l LG block height of 20 full width border top of two border slate of 200 and padding two and let's render our footer and you might have noticed that I have given it a default class name of hidden which means on mobile devices our footer is not going to be visible but when it reaches a large screen which begins with a minwidth of 1,24 pixels that's when we are going to render the footer so let's go back inside of our layout and let's go and below the main element render the footer from do/ footer so now go back to Local Host 3000 here and if you expand you should see the footer and if you go onto mobile mode or basically just lower than LG you should you can see how the footer disappears perfect so we have prepared the skeleton for our landing page what we're going to do next is we're going to add a logo here we're going to add some authentication options a nice hero image we're going to detect whether the user is logged in or not so we can give them a different message like continue or register and in the footer we're going to go ahead and simply display some Flags which are the courses we offer inside of our application great great job in order to wrap up our marketing page I want to add authentication because the marketing or landing page is going to show different things and different labels in the buttons depending whether the user is logged in or not so for authentication we're going to be using clerk so we can add it super quickly inside of our app so go ahead and create an account on Clerk and once you're in you can click new application or you are immediately going to get prompted with a screen like this so I'm going to zoom in a bit so we can see what we're doing I'm going to call my app lingo I'm going to enable the email and Google if you want to you can go ahead and play around with all of this other amazing providers that clerk offers go ahead and click create an application and that should redirect you to the dashboard of your new app so the first thing we have to do is copy this invite enironment keys so let's go ahead and do the following we're going to create a new environment file and for that I want to go tog ignore and I want to find where we already have environment. loal and I'm going to add environment itself so let's go ahead and save that file and now create environment and paste those two keys inside so next public clerk publishable key and Clerk secret key after you've done that make sure nextjs is selected here and click continue in the doc so we're going to be using the app router so that's the type of uh instructions we are going to follow from this documentation first things first let's install the nextjs package for clerk so head inside of your terminal here I'm going to go ahead and use this separate one that I have I'm going to do mpm install Clerk nextjs and after this has finished installing we already added the environment variables so we can skip step two and instead we can go and add the clerk provider inside of our layout uh file so let's go ahead and find that in the app folder layout and let's just wrap everything inside but before we do that that we have to add the import clerk Provider from clerk nextjs so I'm going to add the clerk provider very simply like this and indent the entire thing once we've added clerk provider let's go ahead and add the middleware so I'm going to copy this boiler plate here actually let's copy the lower one so we have the matcher so I'm going to copy this the second one and be careful with the naming of this file so it needs to be created in the root of your application outside of any folder so middleware do DS and make sure you don't misspell it because just like page and layout middleware is a reserved keyword so I'm going to paste this inside and in here you have the basic layout so I'm going to go ahead and remove the ignored routes because we're not going to need them and for the public routes we can go ahead and add a slash so the only public route we are going to allow is our landing page so if I leave an empty array here and if I go inside of my Local Host here and refresh I will I'm redirected to the login page right but if I add the slash inside of the public routs array and then go back to my local host 3000 there we go I can now see the header component great so that is it for authentication so just like that we added authentication without any problems later we're going to add the user button but before we do that uh we're going to go ahead and actually use some other clerk components so let's head back inside of our clerk uh sorry inside of our app folder marketing header component here and let's start styling this a bit so I'm going to give this a div and I'm going to give it a class name of large Max width of screen LG MX AO Flex items Center justify between let me just remove this xss letter which I wrote so justify between and height po so what does this do let's give it a background color of red 500 so we can see it more clearly here so you can see that at one point this red block stops expanding you can see that by zooming out a lot in your application but if you zoom in you can see that at certain point it will always go as much as the screen is right uh excluding the padding which we've added so what we've done here this two uh these two classes here so on large devices we use the max width to be large screen meaning up to this break point and then we give it an MX AO right so on large devices it's not going to go and expand all the way so it's not like we're going to see our logo here we're only going to expand up to this point because it's just nicer for it to look that way great now let's go ahead and prepare the area for our logo here so I'm going to go ahead and create a div here I'm going to give it a class name of padding top 8 pl4 padding bottom of seven Flex item Center and GAP X3 now in here I'm going to go ahead and add an image component so import that from next SL image and now in you can either use any logo you want for your app or if you want the exact one that I'm using let's go ahead inside of my repository you can find that using the link in the description go inside of the public folder and inside of here you can find all of my images and all of my sounds so go ahead and find the mascot so I'm going to go ahead and find the mascot right here and I'm going to download it if you're wondering where I got this from it is from K game assets so if you want to I'm going to leave the link in the description for this as well uh this is literally thousands of completely free game assets if you want to you can donate I highly encourage you to do so I did so myself because I just amazed by the amount of content provided by Kenny here completely for free and I found this to be the perfect design style for our dual lingo app so this is where I found some 2D assets specifically this shape characters which I've then used to create our mascot so you can either create your own you can use any other logo you want or you can simply download the file from my GitHub and then just add it to your public folder there we go mascot. SVG so let's go ahead and just close this things let me go back here I'm going to go inside of my header component where we left off and now let's add a source of/ mascot. SVG for this image and let's give it a height of 40 and let's give it a width of 40 and I'm going to give it an ALT of mascot and let's also remove this background red 500 from our wrapping div because we no longer need that we use that only to demonstrate what we are uh doing with our classes and below the image add an H1 element which will simply render the name of our app lingo and in here give it a text of two Excel font extra bold and let's give it a text green 600 and tracking wide and let's not misspell tracking there we go so we have our logo here in the corner great and now what I want to do is I want to go outside of this div which is wrapping my logo and I'm going to add a clerk loading from clerk SL nextjs so make sure you import this and inside of here I'm very simply going to render a loader from Lucid react we have lucid react already installed because shaty nuui initialization did that for us so let's go ahead and give this loader a class name of height five withth five text muted foreground which will give it a gray color and animate Dash spin so now if you refresh for a second you should see a little spinner here in the corner indicating that we are checking whether we are signed in or not great so we have handled the loading status and now let's do clerk loaded from clerk nextjs as well so let me just prepare this Imports like this inside of clerk loaded I'm going to check for uh signed in status again from cler clerk nextjs so we can import that as well and uh sorry not sign in signed in and let's also do signed out so these two components and then let's do sign in button and let's do it like that so instead of using sign in we're going to use the status signed in here and all I'm going to render if we are signed in is the user button from clerk SL nextjs notice how I also have this import for their beta version so clerk is going to get updated soon uh but don't worry uh this implementation that we are doing in this tutorial will be supported for a long time but if you want to you can already explore the future better you can see that it has all the existing components which I'm using in this tutorial anyway but I recommend that you follow this exactly as I'm doing if you can simply import it from clerk nextjs go ahead and do that so user button is actually a self-closing component so let's go ahead and uh make sure you import user button signed in clerk loading and clerk loaded so right now you should not be seeing anything because we are not logged in we have not done that part yet so then inside of clerk loaded go ahead and add uh if we are signed out which we already have imported and in here we're going to add a sign in button component which we also have from our nextjs here and go ahead and give it a mode of model and let's give it after sign in url to go to slash learn and after sign up to go to slash learn as well so regardless if we log in or create an account and inside we're going to use our button which we've created and styled in the previous chapter so let's go ahead and write inside login I'm going to give it a size of large and variant of ghost there we go so now we should have a login button in the corner which should open uh the login model from clerk without redirecting the user great so now uh we are done with the header component what I want to do next is I want to well first let's actually log in right so we can demonstrate this user button because there is one thing missing from this user button which is a prop after sign out whoops after sign out URL simply go back to slash and save this and refresh so I'm going to go ahead and log in so we can demonstrate the user button so after you've been logged in you're going to get redirected to this 404 page because we told it to do that but we have not yet implemented that page and there we go now in here you have the user button and you can use it to log out now let's go ahead and create the middle content in here which is located inside of page. TSX itself so for that we're going to need another image from here so let's go ahead and find the hero. SVG inside of my public folder so the same place you found this or of course you can use your own if that is what you prefer there we go so this is our hero image also thanks to Kenny game assets so let's go ahead and drag and drop that inside of our public folder there we go and now let's go back inside of page. THX here let's give this div a class name of Max with 998 pixel sorry 988 pixels MX AO Flex one full width Flex Flex call on large Flex is row so they are next to each other so we are automatically going to make this responsive and then we're going to go ahead and give this an items Center and justify Center let's give it a padding of four and a gap of two and now let's create a container which is going to hold our image component so this container is going to have a class name of relative width is going to be 240 pixels height is going to be 2040 pixels as well and on large devices width is going to be 424 pixels and on large height is going to be 424 pixels as well so these are obviously values uh which I have defined while working on the app that they look the best so I'm also adding a margin bottom of eight on mobile devices because on mobile they're going to be one above another and on large devices they're going to be next to each other this image and our next component which we are going to do so no need for modic in bottom here let's give this image a source of/ hero. SVG let's give it a fill property and an I'll of hero and of course we have to import the image from next SL image and there we go you can see how now it's here in the middle but when I expand you can see how uh it gets bigger so let's go ahead and add the next element to it now so outside of this let's go ahead and create a new div with a class name of flex Flex call items Center and GAP Y8 and then in here let's let's add an H1 element learn practice and master new languages with lingo let's give this H1 element a class name of text extra large on large devices text is going to be 3 Exel font is going to be bold and text is going to be neutral 600 let's give it a Max width of 4 180 pixels and text Center there we go and now outside of H1 element I want to create special cases on whether we are logged in or not so let's add our clerk loading from clerk nextjs inside of here we are going to render a loader from Lucid react so let's give this a class name of height five width five five text muted foreground and animate spin so make sure you have added clerk loading from clerk nextjs loader from Lucid react and image from next image and we're also going to need this button which I have here from before so now if you refresh you can see the same way as we have in our header we now have a little spinner here and then if clerk has loaded so clerk loaded component onent from clerk nextjs make sure you add that to your Imports we're going to do the status for signed out from Clerk nextjs and we're going to do sign in from clerk nextjs so again make sure you've added clerk loaded clerk loading signed in and signed out all from clerk nextjs so inside of the find out we're going to go ahead and add the sign up button from Clerk nextjs and inside we're going to render our button component which will say get started so let's go ahead and give this a size of large variant of secondary and class name with full so again make sure you added sign up button import from Clerk nextjs and let's give this sign up button a mode of model after sign in to go to slash learn and same for after sign up there we go and then so this is inside of uh signed out so let's copy this sign up button again paste it below and change this to be a primary outline button which will say I already have an account there we go and this is going to be sign in button this time from clerk nextjs because the user using this uh is trying to log in whereas this get started is sign up so make sure you have both sign in and sign up button and for this that we just copy pasted you should be using this sign in button so now if you log out for example there we go you should be seeing get started and I already have an account so let's go ahead inside of the signed in here and what we're going to do here is simply add a button component and write continue learning and we can add a link component from next SL link give it an H wrap of Slash learn give this button a size of large variant of secondary class name with pool and as child prop is important because we're using the link component as a child here so make sure you imported the link from next SL link and now if you go ahead and try and log in so I'm going to click here I already have an account and let me go ahead and log in and now if I go back to Local Host 3000 there we go it says continue learning great so we've done uh the basic uh landing page here uh we're going to go ahead and style it even better and we're going to wrap it up with our fter component in the next chapter and then we can go ahead and start creating our database and slowly start creating the initial inner interface great great job so all that's left on the marketing page is to add a footer component but just before we do that I want to add uh a few more class names for our main page here let's go ins the app folder marketing page. vsx and let's find the div which is encapsulating our clerk loading and clerk loaded so this part right here and to this div I want to add a class name of flex Flex call items Center Gap Y3 and Max width of 330 pixels and let's give it a full width like that there we go so now this looks just a tiny bit better and you're going to see uh that even more when I log out so now this two have a gap between them like that I like this it's a tiny detail but I think it just uh makes this look a tiny bit better great so now that we have this we have to create uh our footer component so in order to do that let's prepare some Flags so again you can find all of the assets inside of my public folder in GitHub here and for the flags I'm using this flag pack Library so originally this was a react library but uh I'm not sure if it is maintained at the moment so what I did is I used the figure resource for it so if you want to you can do that yourself and this is flag pack you can find the link in the description uh or you can find the ones which I've downloaded from figma and exported here so I'm going to go ahead and just add a couple of random Flags inside of my footer which are going to be used uh to Showcase some of the languages which our app is going to offer so I'm going to go ahead and download Spanish English Eng Croatian Italian and German flag for my case you can go ahead and choose any flag you want so I ended up adding Spanish flag French flag catian flag Italian flag and Japan flag right here so you can find this exact names so HR i t JP f r es so just go ahead and find them inside of my public folder and download them this is just you now for for preview of course it's just the landing page it has no real functionality so you can use any image you want so now let's go inside of the marketing here inside of footer. vsx and let's go ahead and actually style this so inside of the footer create a div with a class name Max with screen large MX AO Flex items Center justify evenly and height pull so we're going to do the same Max width screen trick that we did in the knv bar so it is consistent with the footer as well but we don't have to hide it under the LG prefix because it the footer will be F hidden on mobile devices and now inside of here I'm going to use the button component which we've created and I'm going to add a little a span here sorry I'm not going to add a span I'm going to be using the image element from next image and and I'm going to do for example hr. SVG which will represent creation language and I'm going to go ahead and try this with a height of 32 and a width of 40 and we're going to see how that looks like so I'm going to give this button a size of large I'm going to give it a variant of ghost and a class name of with full and let's also write creation here and let me just do that after the image uh so I just want to collapse these elements here so it's more readable and let me go ahead and just add a class name here mr-4 and rounded MD all right so let's try it out this should simply render a creation flag uh in my footer there we go this looks fine and now I'm going to go ahead and do that uh for the rest of the flags that I have so I'm going to copy the button I'm going to write Spanish for example and now we should have Spanish I'm going to copy the button again uh and just look at your public folder right and check what you have so we have FR FR that's going to be French uh let me let me change the text to French here as well and I have to change the AL of this image to Spanish let's go ahead and copy one more time this one is going to be Italian using it.svg Italian and last one is going to be Japanese so let's add JB here Japanese and let's copy that and paste it here and I believe uh we should now have a nice little foter great if you want to you can add these to open a model as well by wrapping them in the sign in button or sign up button uh great and you've probably notice that these flags from flag pack have a unique little touch to them I like that because I want this to kind of have a whole cartoonish feel to it so yeah even the creation flag where I'm from is a little bit modified so this is supposed to have like a little Artistic Touch to it you can of course find your own images of flags if you prefer different ones great so that wraps up our uh landing page as you can see we have nice little loading States so we can see if we uh have an account or if we are uh already logged in so let me just log in so we can just uh see how this looks on that screen so yeah we are still getting redirected to this 404 that's the next page we're going to work work with and there we go in here we have a nice little uh user button and we have a button continue learning let's just check on mobile looks fine great so all of that is working great great job you finished the marketing or the landing page what we're going to do next is we're going to go ahead and work on this 404 SL learn page let's continue developing our lingo app and what I want to build next is the layout for this 404 page so I finally want to get rid of this error and start working on the reusable layout so what I'm going to do is I'm going to create a new route group similar to this marketing one and we're going to go ahead and call this Main in parenthesis so I don't want main to be a part of the URL I just want to visually separate this from everything else that's going on inside of the app folder so inside of here I'm going to go ahead and create a new layout file and I'm going to go ahead and create a folder called learn because that is where we redirect the user after they log in so because this is a route I need a page. TSX inside so if I save these files and refresh we no longer have 404 but we do have an error because we need a default export so if we just go ahead and do this so if I go a write learn page here and a div learn page make sure you do a default export it still won't get rid of the error because we are also missing the layout export so we have to go inside of the layout here and let's go ahead and simply do const sorry type props children react react node and let's just go ahead and do the same thing I just did inside main layout and let's return a div which render uh renders the children and we have to destructure the children from the props there we go and now we should get rid of the error and we should see the text which says learn page if you are on Local Host 3000 learn so now when you click continue learning it should redirect you to here and just confirm that the same thing works if you are logged out so I'm going to go ahead and log in and see if it will redirect me uh to that page and there we go once I logged in it redirects me here so just confirm that you have the necessary redirects on your marketing page uh great so now that we have this layout uh let's go ahead and actually uh structure this a bit better so first of all this layout is going to be a fragment and then I'm going to go ahead and wrap this in a main like this and I'm going to go ahead and give this main a PL of 256 pixels like this so I want this to be moved from the side here because this is where we're going to add our sidebar next so let's just keep this thing separated I'm going to give this a background color of red 500 and let me give it a full height and see if this will add uh add something to it it looks like it didn't add anything uh so let me just see how we can improve this I'm not going to add the background color here instead I'm going to do the following I'm going to add a div here and the class name of BG red 500 there we go so this is what I wanted to Showcase I just want to showcase uh the this is the part that we have to work with right so that included the color but this is what I wanted to demonstrate but one thing that seems to not be working as it should is our H full line so let's go inside of the app folder global. CSS and let's add the following HTML body and colum root and let's go ahead and add at apply h-4 so this is the the equivalent of adding height 100% but since we're using Tailwind we can use add apply and write a Tailwind class name and there we go you can see the moment I added that in our globals we now have the full height uh of our learn page here so let's go back inside of this layout here and let's actually create a little sidebar component or at least prepare it so that we can see what we are working with so I'm going to go inside of this uh well we can actually keep this uh inside of learn or actually this is going to be reusable so I'm thinking where we should put the sidebar I was thinking I can do it the same way I just did in the marketing folder but then I remembered that the sidebar is going to be reused for the mobile uh drawer as well so perhaps it's better to think of it as a reusable component uh so that's what I'm going to do I'm going to go ahead and create this in the components folder but not inside of the UI folder because that's where I want to keep my shaty and UI components so my custom components are going to go to the root of the components folder so I'm going to create sidebar. THX right here and let's go ahead just very simply and Export const sidebar so no default export because this is just a component and let's just return a div which says sidebar and I'm going to give it a class name BG blue blue 500 full height and let me also give it a fixed width of 2506 pixels and now I'm going to go back to my layout file and just above the main I'm going to import that sidebar from at/ components sidebar so let's see what did we achieve here so it looks like uh we have this set up right but they are not aligned next to each other so you can only now see the learn page if you scroll down so somewhere in one of our layouts we have to resolve this so they can actually be next to each other so there are obviously multiple ways we can resolve this as I said we can go in one of the layouts and we can apply uh a flex row to this right we could do that uh or we could go inside of the sidebar and we could give it a fixed position which in my case suits me better because I never want that to scroll with the rest of the page so I want my sidebar to always have its own position so that's what I'm going to do I'm going to go ahead and give it a fixed position uh and you can see that already this resolved our scroll issue so now you can see the sidebar takes its width and because we added a PL it this learn page is pushed exactly for the content of this sidebar so this obviously looks fine for uh web uh screen but we need to do the same thing for mobile and for mobile specifically I want the sidebar to collapse once it breaks a certain uh viewport so let's go ahead and do that next I'm going to go ahead uh and give this a couple of more class names here so I'm going to give it a flex class name I'm going to go ahead and Mark this with as L with an LG prefix so I only want it to be uh I only want it to be here visible on desktop right and the same thing I want to do for the fixed like this right so on mobile I wanted to take 100% of its width which it will do automatically because we are not limiting it like we used to and I'm also removing the fixed position because it's going to be used in a drawer which is fixed in itself right you're going to see what I'm talking about in a second once we implement the mobile view from it uh so only you can see that when I expand or zoom out it shows them next to each other as they should be on desktop but once I break it goes into a tablet or mobile mode obviously uh it would not collapse into the sidebar itself but I'm just confirming that this works as expected great so we now have this and let me just add a couple of more positioning Fields here so I want to give it a specific left zero and top zero uh I'm going to prepare PX4 here border R2 and flex call for all the items inside uh there we go so now you can see how I have a little border here as well great now that we've prepared this let's go and give this sidebar component a type of class name sorry a prop of class name so an optional prop which can come from outside wherever it's being reused right and what I'm going to do is I'm going to use our CN util which we've gotten once we've installed shat CN UI so you can import that CN util from at Li utils it's a very simple function which will help us merge Tailwind classes without causing any additional conflicts and I'm very simply going to pass that class name here and what I can do now is I can optionally where I want to add a class name to this sidebar without hardcoding it inside of here because sometimes I don't want it to behave a specific way but here's what I want to do so inside of my U main layout here I want to ensure that this class name sorry this sidebar is hidden on mobile devices and only visible on desktop so when I expand there we go you can see how only then it appears but on mobile it doesn't appear and then we have to do the same thing for this learn page we have to ensure that this padding here only shows if we are on desktop so I'm going to add LG prefix here there we go so on mobile the learn page will take the entire content but when I expand the sidebar will appear great and now I want to add something that will enable the users to trigger the sidebar when they are on mobile mode so in order to do that we're going to do the following so first of all let's prepare a padding top of 50 pixels here so that should push this learn page as you can see even on desktop but I only want it on mobile because on mobile we're going to have like a little header which will uh serve to open the sidebar so let's go ahead and just ensure that this only happens on mobile so we're going to write it like this and then once we reach a large breakpoint we're going to give it a zero so if I go ahead and expand there we go once the sidebar appears back the learn page does not have that padding anymore but on tablets and mobile devices they appear uh great so what we have to do now is we have to create a mobile header so let's go ahead and create a mobile header component so we don't have it yet but we're going to go ahead and do that in a second so I'm also going to create that in here keep it reusable so mobile dashe header. TSX and very simply I'm going to export con mobile header and I'm going to return a nav element and I'm going to give this nav element a hidden value on large devices I'm going to give it a px of four I'm going to give it a fixed height of 50 pixels Flex item Center I'm going to give it a BG let's use yellow 500 border bottom fixed position top zero full width and let's give it a z index of 50 and I'm simply going to write mobile header here so I'm going to use this uh color here let me just disable the copilot I'm going to use this color here so we indicate and it's easier to see uh where it's actually appearing so let's import this mobile header from components mobile header the same way we did with the sidebar and there we go we now have a mobile header here but when I expand the mobile header should completely disappear perfect so what I want to do now is I want to create a component called mobile sidebar but before I can do that I need to add a component from shat cnii so let me open a new terminal besides my npm run here and do npx shat cnii at latest add a sheet component so let's wait a few seconds for this to install and then we're going to use this as a drawer which will open the sidebar on mobile devices so after you have this uh you're going to go ahead and do the following so instead of having this text mobile header we're gonna have uh mobile sidebar being rendered here so I'm going to create a new component in the components folder mobile sidebar and in here what I'm going to do is I'm going to import the sheet component the sheet content and the sheet trigger so prom add components UI sheet component and let's go ahead and do the following we can now import our reusable sidebar component from at components sidebar or do/ sidebar because they are in the same components folder but I prefer explicit way like this and let's do export Conant mobile sidebar and let's very simply render these components so we need a trigger here and the trigger is going to be a menu icon from loose Lucid react so let me just move this Lucid react import to the top here and let's give it a class name uh off well very simply let's give it text white everything else is pretty much uh okay and let's write sheet content here and inside we're going to render our side bar component so now let's go inside of the mobile header and import the mobile sidebar we should get rid of the error now and if I click here there we go that opens a drawer and you can see our sidebar here but obviously it's not as perfect so first things first I want this to be on the left side and I also want the sidebar to take the full height and width without any padding here so that's why I'm using this bright colors so we can clearly indicate how this is supposed to look like so let's go back inside of the mobile side bar and find the content component and let's give it the following class names I'm going to give it a padding of zero I'm going to give it a direct zindex of 100 and I'm going to give it a side of left and now if I try this again there we go this is exactly how I want my sidebar to behave on mobile devices perfect so on desktop this is going to be uh quite nice to look like we have the sidebar and we have the learn page and then when we collapse when we have too many elements we're going to collapse to mobile mod so if you're wondering how come I'm using the large viewport and not the medium viewport for collapsing to the mobile well that's because uh we're going to have another sticky sidebar on desktop here so it simply doesn't look good when collapsed right so that's why we simply have to uh fall back to mobile mode once we don't have enough space to work with at least that's the solution that I found great and just to wrap this layout up let's do one thing that we can already finish which is go inside of this mobile header and let's give it a proper color so it's not going to be yellow it's going to be green there we go so this is going to be um the proper color for our uh header here which is going to be used in the final version as well so we can already prepare that and obviously the sidebar and the learn page are not going to have colors but I just want to leave them here so they can indicate for us what's going on uh great so we just reused a layout once again we learned how that works and you're going to see how powerful this layout component is now because in our marketing it's not that visible because we only have one page right we kind of try to demonstrate it with frequently as questions and with the doc page but here in the main route group you're clearly going to see how useful this is is because we're going to have a lot of routes besides learn we're going to have quests and leaderboard and a ton of other things and you're going to see how useful it is that we don't have to write the mobile header every time or this entire sidebar functionality uh great great job what I want to do next is build the actual sidebar so before we move on onto any page I want to have a fully functioning sidebar so you can do that either in the mobile mode like this or you can zoom out and expand your screen to see how it's going to look like on desktop but both are going to use the same component so you don't have to worry about checking both at the same time so let's go ahead inside of our sidebar component and for the first item I want to add we can actually revisit our marketing page specifically the header and from here you can copy this image and H1 saying lingo and you can even copy the div which gives it the exact padding that we need so I'm going to copy these three elements and I'm going to replace the text sidebar with that once you've done that you have to import image from next SL image so let me just add that and make sure that you have this div make sure that you have an image which uses SL mascot and we're going to see if we're going to modify the height and the width and the H1 I believe can stay exactly as it is so if I click here oh and let's also remove the background color we're not going to need it here there we go so now this says lingo and let me just close this there we go so this says linger as well so this is how it's going to look on 100% height this sorry 100% Zoom that looks great now what I want to do is add some sidebar items here so let's go ahead and uh and yeah let's turn this uh this logo into a link so in order to do that I'm going to use the link import from next link and I'm going to wrap the entire div inside of that and I'm going to give it an HRA to go to slash learn because slash learn is going to be the default like homepage for logged in users right I'm not going to disable the marketing page with the redirect but the uh the logo click will go to slash learn now let's go outside of this link and let's add a div element here which is simply going to hold all of our uh routes so give this Flex Flex column so it goes one below another let's give it a gap Y 2 and let's give it a flex of one and now inside of here we're going to use the component sidebar item which does not exist yet so we're going to go ahead head uh and create one so I'm going to do that inside of the components folder sidebar item. CSX and let's go ahead and create this reusable component so this is going to be a use client meaning that we're going to create boundaries that we can use some hooks inside because in next 13 sorry next 14 and 13 you know basically uh nextjs versions which use the app router uh every single component that you create and Page inside of the app folder is a server component by default that means that you cannot use hooks uh and other interactivity obviously there's much more going on between server and client components if you want you can read the nextjs documentation for that but when you see me write use client it means that I'm creating a boundary so that I can uh work with this as your usual react component I can add hooks I can add on click uh I can add use effect and stuff like that but for other components by default they are server components and you're going to see what they are very useful for later so this is going to be our good good old line component so let's go ahead and Define some props here it's going to have a label it's going to have an icon source and an HRA and let's go ahead and Export con sidebar item and I'm going to go ahead and assign the type props here and extract the label icon source and the HRA and then inside of here I'm going to go ahead and just return a button component from /ui button but I'm going to change this input to go to components UI button and in here I'm just going to render the label for now like that and we can go back inside of this sidebar. TSX and we can import sidebar item from do/ sidebar item so now you should just see a button in here uh great so and I didn't pass any label here so that's why it's not showing so if I pass a label for example learn there we go we now have a button here which says learn great so let's go ahead uh and do the full implementation of the sidebar item component here so I'm going to go ahead and now finally add a hook here so I'm going to call this path name to the Ed path name from next navigation and let me quickly demonstrate so when I save this there are no errors going on but if I comment out use client I'm not entirely sure if it's going to throw an error it does so you can see how it threw an error and you can see why it's going on so use path name only works in client components add the use client directive at the top of the file to use it and you can read more here right so obviously if you want to learn more about server components and client components we are going to talk more about them the more we use them throughout this tutorials I also have short section in all of my tutorials about client and server components but I'm not going to focus too much on them for this tutorial if you want to you can go ahead and visit the nextjs documentation and in here you can Le uh you can learn all about uh the components inside of the app router so just make sure you're using the app router and in here you can see how we Define routes pages and layouts uh and also the difference between for example a server component uh and a client component which is our good old usual components that we are used to so I just want to clear that up in case this is your first time seeing the use client directive so make sure you have use client at the top and then using a hook should not cause any errors and now let's determine whether this uh s bar item is active or not so we're going to define a constant here path name is equal to hre and let's go ahead and give this a variant to be dynamic so if we are active we are going to use the sidebar outline otherwise we're going to use the sidebar so those are what some of the variants we've created in our buttons uh chapter if you remember remember and let's give this a class name of justify start and let's give it a fixed height of 52 pixels and let's also pass in the as child prop because we're going to incorporate a link component here from next SL link and let's give it an hre of HRA make sure you import next link from link and we have an error because we're not passing any href here so let me just add an hre to slash learn here there we go no more errors and we can now see the text learned here and we can see that this page is active because our URL is at slash learn uh great and one more thing that we have to do is we have to render an image component again from next SL image so just make sure you've added that and this image component is going to have a couple of props so the source is going to be icon Source alt can be the label and then we're going to have a class name of margin right five and let's give it a height of 32 and a width of 32 as well uh and there we go now we have this broken image here because we're not passing anything so let's go ahead and actually pass in an image so I'm going to go ahead head and collapse this and for this one we're going to pass in the icon source which is going to be/ learn. SVG so again this is going to be a broken image because we don't have that so now uh you can either use your own icons as always or you can go inside of my GitHub here go inside of the public folder and I'm going to add all the items I need here so let me go ahead uh and find my learn icon here I'm going to download Learn and let me go ahead and paste that in the public folder here so learn. SVG there we go so I added this little house icon these are MIT licensed icons from Microsoft if I remember correctly uh let me go ahead and show you this there we go so this is how it looks like so what I want you to do now is find the equivalent icons uh for leaderboard quests and Shop so I'm going to go ahead and do now I'm going to download all of those icons and then I'm going to show you their names but you can find all of them here in the public folder so I've added the following I have a leaderboard icon so you can find that in here I have a learn icon quests and shop. SVG so make sure you add all of them uh all of those you can find them in the public folder and or you can of course use your own and now let me go back inside of the sidebar here and and I'm just going to go ahead and copy this sidebar items so the next one is going to be leaderboard it's going to go to slash leaderboard and we're going to have a leader board SVG and for the next one is going to be quests and for the last one is going to be shop so let's go ahead and try that out so if I go ahead here uh and zoom out there we go we have learn leaderboard quests and Shop if I try and click on any of those I'm redirected to a 404 page but once we Implement them uh they're going to have the same status as our current learn page here so just make sure you've added this icons and one more thing we have to add is our log out button here so let me add a div here with a padding of four let's uh go ahead and import a couple of stuff from clerk here so from at clerk nextjs we're going to go ahead and import clerk loading clerk loaded and let's add user button and while we are here let's also import a loader from Lucid react now I'm going to go ahead and add a clerk loading here to add a loader with a class name height five withth five live and text muted foreground and animate Spin and then clerk loaded is simply going to render a user button but remember to give it an after sign out URL to go back to the marketing page there we go so we've added that let's go ahead and see how it looks like there we go so here in the bottom we have our user controls let's see how it looks on mobile perect perfect so we have a responsive sidebar and we can use it to control what part of the app the user is on and we can also manage user settings and we can also sign out perfect so we are officially ready to start developing the learn page and we're going to go ahead and do a similar process we're going to start with creating a skeleton layout because we're going to have a sidebar here and some feed content here and we're going to work up until to the point where we need some stuff in our database and then we're going to connect drizzle and we're going to start building uh our back end great great job so now let's go ahead and let's develop the layout for our learn page right here so I recommend that you while you're developing this you zoom out so you see the desktop mode because the last thing we did was we created this collapsible sidebar right so we have this header here well in order to fully develop the learn page I would ask you to zoom out or expand your screen if possible so that you can see this static fixed sidebar on the side here so that you can properly develop the rest of this application so let's go ahead and do the following let's revisit our main and let's revisit our layout inside of that Main and now we can finally remove the background red for this because we know that it is working and and let's go ahead and do the following let's do Max width and let me go ahead and give it a value of 1,56 pixels and let's give it MX Auto so it's pushed to the sides once it reaches that uh Max width and let's give it a padding top of six like this there we go so now you can see how my learn page words are not expanding after a certain break point right so you can see that the MX Auto is activated once we break from that 1056 pixels great now that we have that prepared let's go inside of learn page. CSX and let's create some basic uh classes here so I'm going to give this a class name of flex Flex row reverse Gap 48 pixels and PX of six and now inside I'm going to create a sticky wrapper and inside I'm just going to say my sticky sidebar here so let's save this and let's create our reusable sticky wrapper inside of the components here so sticky wrapper. DSX it's going to have some props here so let's give it a type of ch to be react react node and Export cons sticky wrapper it's going to have children like that and let's go ahead and return a div inside let's prepare another div and let's render the children and let me just turn off my GitHub co-pilot and to the first div let's give it a class name of hidden on mobile then on large it's going to be visible let's give it a width of 368 pixels let's make it a sticky position let's give it a self end position a z index of one uh actually I don't think Z index of one exists so I'm going to remove this and let's give it a bottom position of six and for this one let's give it a class name of Min height and now let's do a calculation of 100 viewport height minus 48 pixels because this is going to be our little header that we're going to create lat later and let's give it a sticky position a top six position Flex Flex coal and gap y4 for all elements inside now let me go ahead and add this sticky wrapper here from components sticky wrapper and there we go now inside of here we have a sticky sidebar and in here we have the space needed for our for our well feed wrapper so now I want to create the equivalent but for our feed so just outside of the sticky wrapper add a feed wrapper and I'm going to write my feed well my feed is good enough now let's go ahead back inside of the components and create a new file feed wrapper. CSX inside of the feed wrapper it's going to be even simpler so give it a type of props again with a children of react react node and Export const feed wrapper and let's not forget to assign the props children and let's return a div which renders the children and then in here I'm going to give it a class name of Flex one relative top zero and padding bottom of 10 so that when we reach the end of our feed we have some space at the bottom so that we can scroll and let's import the feed wrapper the same way we did with sticky wrapper just remember that you have exports otherwise you won't be able to do that so there we go feed should be visible on mobile but desktop this is how it should look like great so now I want to try something out I want to go inside of my feed wrapper here sorry my page here and I'm going to create this div block and perhaps I can also make it a cell closing tag so you don't have to do this you can if you want to try it out so I'm going to give this a height of 200 pixels and a width of full and a BG of blue 500 like this and let me make it even more let's do 500 pixels and I want to copy it as many times as possible because I want to try something out so I'm going to go ahead here uh and there we go I should now be able to scroll so I'm scrolling right now you can see how I reach the end of my uh blue bars here but you can see how the sticky wrapper stays in place so that is the exact effect I wanted to happen here great uh so now what we can do next is we can create two components one being the header and one being the user progress which later are going to be filled with actual values from the database but uh the components themselves are just presentational so they only accept the props so technically we can already create them so we see how that's going to look like and then for the rest of the stuff we are going to have to start creating some database items great so of course if you have created uh these things you can now remove them so I think this is even simpler for me to do I'm just going to add the feed wrapper again and write my feed great so we ensure that the sticky wrappers actually stays sticky and the feed wrapper actually holds our feed uh and now that I think of it you might not even need Flex uh row reverse instead you could remove this and then you could add this uh in this position so feed wrapper first and then the sticky wrapper I think this should still work as expected um I think I might have just just been experimenting in my original source code when I added this or perhaps I wanted it to be prepared for a mobile view where the sticky wrapper is going to be on top something like that uh but for now I'm going to stay true to my source code so I'm going to write Flex row reverse just because I don't want any uh additional issues to be caused but I am pretty sure that I was just experimenting with something and we actually might not need that but then again I don't want to cause any problems uh in later in the future so I recommend you do the same leave the flex row reverse put the sticky wrapper in the first position and then the feed wrapper in the second position if you want to you can add a little to-do comment to try and remove that later and reverse their positions to see if everything later works as intended great now I want to go ahead and I want to create a very simple component here called a header so that's going to be inside of the feed wrapper here so I'm going to write header like this and I already know that the prop it's going to accept is going to be for example Spanish basically it's going to be the name of the course that the user is taking so now let's create this header component but this is not going to be reusable so it's only going to be inside of this uh learn here so I'm going to go ahead and create a new file header. vsx and since this is a component I can do expert const header I don't have to do expert default here and let's just return a div for now and let me just already prepare the props here title string and let's go ahead and add uh let's D structure this so props the title let's save it and let's import the header from do slhe header so don't accidentally import it from our marketing folder right that's the landing page great and now let's actually go ahead and style this so I'm going to give this a class name of sticky top zero background color of white padding bottom of three on large we're going to give it a padding top of 28 pixels Flex items Center justify Center uh my apologies let's give it justify between border bottom two margin bottom of five text neutral 400 and on large give it a z index of 50 because on large we are going to have some other floating Elements which are possible to overlap with our header so we want to ensure that the header has a higher index now let's add our link from next SL link so just make sure you add that import and let's add our button from add components UI button I'm going to give this link an hre of/ courses which we're going to create later and inside of the button I'm going to use the arrow left icon from Lucid react so make sure you have this three Imports here so so link Arrow left and button and let's give the arrow left a class name of height five with five stroke two and text neutral 400 let's give the button a variant of ghost and a size of small and outside of the link let's add an H1 element which is very simply going to render our title here and let's give it a class name of font bold and text large and there we go we now have our header but there is an issue our text here is all the way in the right corner but I want it to be centered now here's a trick that we can do since we are using justify between we know that it's going to align all elements uh equally spaced out but we only have two elements inside we have this link and we have this H1 element so what we can do is we can add a little trick we can give it an empty div and now it becomes perfectly centered great so now we have our header here ready and once we go back it's a 404 because we don't have this page yet but we are going to have it in the future and now I think we can also do the initial items for the sticky sidebar here at the top just so it aligns with our header so let's go ahead inside of our page. TSX and let's go ahead and create a component called user progress so I'm going to write user progress here I'm going to pass the active horse to just be an empty object for now I'm going to pass in Hearts to be five points to be 100 and has active subscription is going to be false for now great so now we have to go ahead and create the user progress now user progress is going to be usable so let's go inside of the components and let's create user progress. DSX and I'm going to go ahead and Export con user progress here and return a div user progress let's go ahead and quickly add the types here so type props in here we're going to have an active course which for now is going to be a type of any but I am going to add it to do replace with database types later when we actually have some database types because we don't have them at the moment we're going to have hearts which is a type of number and we're going to have points which are number as well and has active subscription is going to be a Boolean and let me go ahead and just assign the props here there we go now let's go back here and import the user progress from as/ components user progress so now that we've added this import let me just change the user uh the user progress active course here so I'm going to pretend like I've loaded an active course which has the title of Spanish and an image source of sl. SVG so just make sure you're using something that you have in your public folder right so I have es.svg which is uh iteration of a Spanish flag so if you have that you can use that we've added those while developing our marketing page if you remember so just do something like this so we pretend like we loaded a course and now we can go ahead inside of the user progress here and let's give this div a class name of flex items Center justify between Gap X2 and with full now I'm going to go ahead and just remove this I'm going to add a link component from next slash link so just make sure you've added this import and I'm going to give it a hre to go to slash courses again and inside I'm going to render a button from /ui button but I'm going to replace it to go to components UI button and I'm going to go ahead and render an image component inside so just make sure you've added an import for next image and let's give this image a source of active course. image source and let's go ahead and give it a alt of active course. tile and let me just destructure the active course from here and let's go ahead and be more strict with this image source is string and title is string right just so we don't make a mistake while passing this even though we're going to replace this later make sure that you have no errors with passing the title and the image source and this type so you know that you're getting this correctly uh and now let's give it a class name of rounded MD and border and let's give it a width of 32 and let's give it a height of 32 as well so now when I expand there we go we have a Spanish flag inside of here and now I just want to give this button a variant of ghost like that great and now we're going to do quite a similar things so I'm going to add a new link button here uh this one is going to go to SL shop and again we're going to have a button with a variant of ghost and let's give it a class name of text orange 500 and let's add an image here with a source of SLP points. which we don't have yet let's give it a height of 28 a width of 28 let's give it an ALT of points and a class name of margin right two and then I'm going to render our points and we have to destructure the points and the hearts and also has active subscription so let me collapse these so that you can see them there we go so active course points hearts and has active subscription and let's copy this link which has our points and it's still going to go to SL shop but this one is going to have a text row 500 it's going to use the heart SVG it's going to have 22 width and height the AL is going to be hearts and we're going to have we're going to render depending on if we have an active description we're going to render the infinity icon from Lucid react make sure you import it as Infinity icon so with Lucid icons you can either import infinity or Infinity icon right but Infinity is reserved in JavaScript right so it might cause some errors so I recommend that you do Infinity icon here so we're going to use Infinity icon or we're going to run under the number of hearts that the user has and let's give this a class name of height four WID four and a stroke of three and let me just collapse this so that you can see so this is what it's supposed to look like Infinity icon with a class name H4 with four and a stroke of three now if I expand uh we have some issues because we don't have this images so let's head inside of my GitHub repository and add those images so inside of my public folder here you should find points and heart so let me find uh my points here points there we go let's download that and we should have a. SVG there we go so download that all of these icons are either MIT licensed or open source so let's go ahead inside of the public folder here let's pass in the heart and let's pass in the points there we go so we should have those two inside of your public folder just confirm that so public folder you should have a new heart icon and you should have the points icon so now if I go ahead and refresh there we go we have the heart and we have the points perfect so you've successfully created the initial uh header here and some header for the sticky wrapper so we're going to add some future content here in the sticky wrapper and we're going to add some units and some lessons here in the feed wrapper one thing that I'm noticing is that my header has this too much padding as opposed to this side here so let me quickly revisit my header component so it's located inside of my main learn header here so I've added large padding top 28 but since I'm trying to do a sticky effect in this I also need to add large margin top minus 28 pixels like this so let me try that out there we go so now they are nicely aligned next to each other so if you're wondering why I want that effect you can try that out again go inside of the feed wrapper here and let me just create like a little div here with a class name space y4 and I'm going to create like a self- closing div just to demonstrate that again so class name it's going to have height of let's give it 700 pixels BG blue 500 and full width there we go and I'm just going to copy it as many times as I can to create a scroll effect so now you can see how it this Spanish doesn't get uh moved even for a second so it stays sticky the same way our sidebar here stays sticky so they have the same effect so you need to do that sticky position and that little trick with the margin top and the padding top to achieve this effect uh at least in my experience uh that was what I needed to do great so uh now I can just simply remove this entire div there we go uh great so I think this is a great start we have a nice little layout here and what we have to do next is we actually have to create our database and start adding some items because there isn't much we can do anymore which is static everything else is actually going to have some real values great great job in this chapter I want us to set up our database and finally connect to Neon using drizzle orm so we can close what we have opened in our editor and I want you to focus on neon website so head to Neon doe you can use the link in the description or simply Google neon database and go ahead and create a new project so I'm going to go ahead and give my project a name of lingo and I'm going to give my database the same name of lingo and I'm simply going to click create project so now let's go ahead ahead and copy this string so I'm just going to go ahead and click reveal here and then you can drag the entire thing and copy or you can simply use the copy option here and now I want to go ahead and add that to my environment keys so let's go inside of environment and after our clerk Keys let's add database and let's actually use the full name database URL and let's paste the pogress URL so I'm going to add it in quotes you don't have to you can add it like this as well but you can see that this weird syntax is enabled for me so I'm just going to wrap it in quotes great so we have the database URL ready here and that is pretty much it for neon so it was that simple to create a database with neon and we are going to keep this open just so we can later confirm in these tables that we have some new records added meaning that our drizzle is successfully connected to our database so that's it for neon that was very simple and uh yes it's completely free at least at the time of me recording this so this is a completely free database and now let's go ahead and set up Neons so we're going to be using uh sorry let's set up drizzle using neon so we're going to be using drizzle orm and in here I'm following their drizzle orm documentation for postle so let's go ahead and install drizzle orm and add neon database SL serverless that is the exact provider which we need to use neon with Drizzle so I can actually shut down my app for this part and install drizzle dorm and add neon database SLS serverless and after that we're going to have to install the drizzle kit but inside of our Dev dependencies because it's not going to be used for production it's only going to be used for our development purposes so that we can easily uh set up and see our studio and also do things like push to the database right so great after we have drizzle orm and neon database serverless let's install drizzle kit in the dev dependencies and once we have that let's go ahead and do the following so we're going to have to run some commands uh like npx drizzle dkit studio and drizzle kit push postgress but this is a long command to write every time time so I recommend that we do the following let's go inside of our package.json here and let's add some scripts which will save us some time in the future so I want to add database Studio here which is going to be npx drizzle kit studio and then let's also add database push which is going to be npx drizzle kit push pogress so first things first let's try out the database studio and see if it's working so I'm going to go ahead and run MPN run DB studio and there we go you can see how that runs the npx drizzle kit Studio but we did not configure our path and this file does not exist yet so we have to configure that first before we can actually run some commands here let's go ahead and create a folder called database so I'm going to go ahead and I'm just going to create in the root of my application a new folder called database inside of that folder I'm going to create a new file drizzle. DS in here I'm going to import neon from neon database serverless package which we've just installed and I'm going to import the drizzle from drizzle or/ neon HTTP and then let's import well we cannot do that yet but we have to import schema now but we don't have the schema yet so for now let's simply do this Con SQL is neon and then let's use process. environment. database URL and let's put an exclamation point at the end so we don't have any typescript errors and whenever you're writing this just double check that you didn't misspell the database URL here and here so just copy and paste it and confirm and then let's do const database to be drizzle and pass in SQL inside and let's export default database like this and now we have this little typescript error here and this is what I'm going to do so for now I'm going to add a TS ignore here just for this line and we're going to see if this will actually break the code or not because this did not happen to me in the previous versions of uh drizzle and neon database serverless so I don't know which package exactly is it which causes this little typescript bug but I'm pretty sure it will not cause any issues in actual uh function of this database of course so I'm just going to add this for now and then later we're going to see and actually uh try and debug what version causes this and can we fix this by manually adding some types perhaps but uh during the initial development of my lingo app this did not happen this typescript error so for now I'm just going to add this you for example in the future might not even get this great so now that we have our drizzle set up let's go ahead and create a new folder in the database folder called schema. CS inside of this schema folder I want to create my courses table so let's export con courses and let's add PG table from drizzle orm slpg core and let's call this courses let's give it an ID of Serial and make sure you import it from PG core as well so our ID is going to be named ID it's going to be a type of Serial and primary key type of Serial means that it will auto increment on every new uh well entity of course you could also add uh integer from PG core like this but then it will not autoincrement right so serial will Auto increment so that's why I'm using that yes there also exists uu ID so you can use that if you want as well you can explore other things which exist in the neon documentation but I'm going to use serial for this tutorial besides the ID we're going to have a title which is a very simple text from PG core as well and it's going to be called title in the database as well and we're going to add a not null rule here meaning that it is required and let's also add an image source which is also going to be a text and this is the cool thing about defining drizzle table so it allows us to use camel case here in JavaScript so when I write this in JavaScript I'm going to access course. image source using the uh capital S like this but in the database I want this to be saved like this so I can just as easily do that great so that's going to be not null and there we go now we have our first uh table here so let's head back inside of our drizzle. s here and let's do what I wanted to do which is import everything as schema from SL schema and then let's extend our database here by passing in the schema as the second argument inside of an object this will allow us to use the drizzle query API which is similar to prismas which is going to uh make it easier for us to get introduced from neon because if you watch my previous tutorials you know that we use Prisma a lot so this is going to be just a stepping uh stone for us here so we don't have to go immediately into uh raw SQL last call it that of course it's not raw SQL uh but to us coming from Prisma it might look like that so using the queries API which drizzle team has developed is going to help us a lot in this tutorial and for that we need to add this uh SQL schema here uh you can of course find all of this in this documentation which I'm using here right now so let me quickly try and find the queries here and there we go you can see how they've added the schema here and then you can use the query API like find many for example great now that we have this let's create the last file we need which is the drizzle config TS so I'm going to close everything here and in the root of my application I'm going to create drizzle. config dods and in here I want to go ahead and install a package uh called EnV slash let's actually just install EnV like this now let's go ahead and import environment config and let's import type config from drizzle kit now let's ask for default schema to go to/ database schema. DS out is going to be/ drizzle driver is going to be postgress and DB credentials are going to be connection string process. environment. databaseurl and you can see that I have a little typo here so let's just finish wrapping this so let's add satisfies config so I have a little typo here so make sure you don't do that as I did so I have databas URL it should be database URL so as always is just double check that it is exactly what's written in your environment file and paste it here uh great and now that we have this let's go ahead uh and do the following let's open our terminal here and let's try and push our new table inside the neon database so we're going to use our npm run database push here to see if this is working so there we go it says changes applied so it read from our drizzle config here let's go ahead and open it it knows where our schema is so it run from our database schema here and it notice that this courses is a new table then it gave us a message changes applied so let's confirm that by going inside of our neon console and clicking on the tables and there we go we have the courses table in our neon database with an ID of integer image source of text and title of text if you did not get this result here's a little thing you can confirm just in case if you think it's not your code so if you're certain you wrote the exact same code you can go ahead and look at my dependencies or go to the source code and take a look at the exact dependencies that I have for drizzle Oram uh for neon database serverless and for drizzle kit and you can install those if you think it's a versioning issue great so now that we have that let's just confirm one more thing inside of here and that is that our npn run database studio is working uh so it it says us to please it tells us to please install the required pack packages PG so let's go ahead and do that so mpm install and I'm going to install this in the dev dependencies so let me try again database studio and there we go so I needed to install the PG package and I've added it inside of D dependencies because this is using the drizzle kit which is already installed in our Dev dependencies so I assumed that the right place to install this PG would be in the dev dependencies as well and there we go we now have the drizzle studio is up and running uh on this domain so let's go ahead and open it right here and there we go we have our courses we can even choose between different uh schemas here so let's go ahead and try and add a record here here so I'm going to go ahead uh and give this an ID of one I'm going to give it a title of Spanish and I'm going to use the image source of/ es.svg and I'm going to click save and there we go we officially have a record inside of our database perfect so this is how we are going to fill our database now and then we're going to go ahead and create the courses page so when people click on this page or when they click on the back arrow I don't have the app running right now so it's giving this uh connection refused error uh so that's the next page we have to do the page where users will choose which course they want to learn and that page is going to load this table so this exact table great great job so that's what we're going to do in the next chapter so we left off by configuring our drizzle and database and we added our first record inside our courses schema table right here so what I want to do next is just add a couple of more courses inside of our database so that we can display them on the courses page and I recommend that you add all of this which you have in your public folder so Spanish French Croatian Italian and Japanese are the ones that I have added so let's go ahead inside of the terminal here and I'm going to open a new terminal and I'm going to run MPN run database Studio here and that will give me an URL where I can look at my database here so let's go ahead and do the following I'm going to open my public folder here here so that I can see I already have Spanish added in my courses so let me add another one so this is going to be an ID of two I'm going to add French uh and let me just see if I can do this and write French can I do that and slf fr.svg like that and save one change there we go let's go ahead and do it again so the third one is going to be creation and I'm going to go oh I have to confirm this and then slash HR SVG and let's go ahead and add a fourth one and I'm going to make that Italian and let me go slit SVG and Italian so you have to press enter in the column once you uh finish writing and then press uh save change there we go so I have Spanish French Croatian and Italian I think four of them are enough for us to create the initial page so yeah we have to manually enter the IDS when we are adding them through the database like this I believe um but later when they are created through our admin dashboard we are not going to need to add the ID so I want you to learn how to use the drizzle Studio that's why I didn't add the admin dashboard at the moment because it's quite useful because from here you can also delete records so this is kind of a super admin dashboard which you have which is your database great so just ensure that in your drizzle Studio you have at least four elements so that we can work with somewhat of a grid so I'm going to go inside of the app folder here and what we have to do now is we have to go inside of the main here and let's create a new folder courses and inside let's create a new file page. DSX it remember to expert default the courses page and let's return a div courses page I'm going to go ahead and try to go to slash courses so this arrow and the click on a flag should redirect you to slash courses now there we go you can see how I'm redirected to slash courses perfect so I'm going to go ahead and style this a bit I'm going to give this a class name of full height Max width of 9912 pixels bx3 and MX Auto then inside I'm going to add an H1 element with language courses typle let's style this H1 element by giving it a text to excel font Boldt and text neutral 700 there we go so let me expand this so we can see how it looks like and there we go perfect so now that we have this I want to create uh a query which is actually going to fetch our newly added courses from the database so let's go ahead and inside of our database create a new file queries. DS and let's do export const get courses and make sure you cache this function so import cache from react it's going to be an asynchronous method and let's go ahead and Patch the data using await let's call the database from do/ drizzle. query. courses. find many and very simply return the data and this is supposed to be asynchronous like that so if you want to you can go with do/ Drizzle or you can use add slash database drizzle depending on how you how and what you prefer so we are using cache from act so that we can use this get courses without having to pass the props every time so if I go ahead and turn this into an asynchronous function and if I do const data wait get courses here from database queries now and let's say here I have a list component and I don't want to pass in the props like this right of course this is perfectly fine to do but let's imagine that I don't want to pass props for any reason if I go inside of this list component and if I call this function again get courses it's not going to call the database again because of this cache function so that's why I want us to Cache every single query that we are going to write so that's why I'm doing that great so now let's go ahead and do the following let's go ahead and write json. stringify and pass in the data let's save that and there there we go so you can see how I successfully loaded my Spanish my French my creation and my Italian records from the database perfect so now we're going to go ahead and create the actual list component which will render this so I'm going to go ahead and add a list like that and the list component is going to accept the courses which is going to be our data uh or we can actually name it courses right and we are going to have an active course ID which for now is going to be hardcoded to one so let's go ahead and inside of the courses folder create a new list. DSX component and inside of here let's mark it as use client because it's going to have some interactive elements let's give it a type of props and here's the cool thing so now we can finally use the type of courses for our props so let's write courses and that's going to be a type of courses from database schema dot infer select and let's go ahead and add an array at the end and we're going to have an active course ID we can write number because we know that our schema type of ID is serial which is actually a number or you can do uh uh well we actually don't have the ID we actually don't have the model uh prepared for this I'm going to explain it in a second so for now let's just keep number I wanted to do something with a type of as well but we are missing one table which I'm going to create in a moment once we finish this so for now let's just do it like that and let's export cons list here let's sign those props and let's return a div saying list and let's see if this is broken here so list is not defined so we have to import that from slash list and there we go you can see how we no longer have any prop errors so this accepts courses this is the object and this is the array it accepts exactly what we expect so now what I want to do is I want to give this div a class name and I'm going to go ahead and create a grid I want to display my Flags so padding top is going to be six it's going to be grid grid calls two on small or mobile devices and then on large we're going to have grid Das calls and we're going to go ahead and write in square brackets repeat and then we're going to pass auto- fill comma min max open parenthesis and in parenthesis we're going to write 210 pixels comma 1fr and whenever you are writing inside of this uh square brackets make sure you don't accidentally put spaces in between those values so neither here nor here you can check if a Tailwind class is correct by hovering on it so you can see how this custom value which I've added has perfectly been translated into CSS if you have the Tailwind extension and you hover and yours is not it probably means that you have a space somewhere so just make sure that you have the exact this is all one line of course there are no spaces in this place right here I didn't break this line this is just my editor settings so that you can see everything all the time so I don't have to scroll in here right so we have that and let's add Gap four and now inside of here we're going to go ahead and actually use these courses so let's extract the courses and active course ID and in here we're going to map over our courses let's get the individual course and in here we are going to render a card element so let's give it a key of course. ID and let's give it all the other necessary Fields so ID is going to be course. ID as well title course. tile image source is going to be course image source on click for now it's going to be an empty Arrow function disabled is going to be false and active is going to be if course ID is equal to active course ID so now we have to create the card element so let's go ahead and create the new file inside of the courses folder called card. vsx let's go ahead and Mark this as actually we don't have to mark this as client because it already is in a client component so it will not magically become a server component uh if you're interested you can pass server components inside client components but that is done through the children prop so not directly like this so let's go ahead and Define the props for this new card component which we've just created so title is a string ID is a number image source is a string on click is going to accept an ID which is a number and return a void disabled is going to be an optional Boolean and active is going to be a optional Boolean as well let's export const our card element here let's go ahead and destructure these props and let's just return a div saying card let's go back in our list component and then then we can import the card from dot SLC card like this and there we go you can see how my props are perfectly fine with this great so now let's actually D structure the title the ID the image source the disabled the on click and the active prop here and let's make this uh look like something so I'm going to go ahead and give this div an on click to open an arrow function and call the on click and pass in the ID which we have from the prop uh it doesn't need a key but it does need a class name and class name is going to be dynamic so open the uh which are this the curly brackets right and import the CN from lib utils and now in the first parameter we're going to give it the default classes so the default is going to be height full border is going to be two rounded is going to be extra large border bottom is going to be four pixels hover is going to use BG black sl5 cursor is going to be pointer active is going to be border bottom to flex Flex Das column items Center justify between padding off three padding bottom of six Min height is going to be 200 and 17 pixels and mean width is going to be 200 pixels now let's go ahead and add the additional values so those are going to be if it is disabled we're going to write pointer events none and opacity 50% and now I'm just going to zoom out just a bit because this is too zoomed in to be realistic mobile view so I'm going to leave it like this and let's go ahead and write a dip here inside with a class name of minimum height of 24 pixels let's give it a full width a flex items Center and justify end and then inside if this class is active this card sorry if this card is active we're going to give it a little check mark here so inside of here we're going to render the check icon from lcid react so just make sure you add this import here so we have the check icon and I'm going to give it a class name uh of text white and stroke four and I'm going to do that inside of the square brackets and height four and width four and in here I'm going to give it a class name of rounded medium BG green 600 and I'm going to give it Flex items Center justify Center and padding of 1.5 there we go so we're going to have a little indicator that one of these courses is currently active so the first one is currently selected as active because if you remember in our courses page we passed the ACT course ID to be hardcoded to one so as you can see in our dzal studio that is this value Spanish all right so now let's actually add a little flag to this card element so we just wrapped up the active State here so go outside of this div which is encapsulating this and in here we're going to render an image from next SL image so it's going to be a self closing tag make sure you added the next image import here and we're going to go ahead and pass in the source to be image source we're going to give it an Al of title a height of 70 width is going to be 9333 and class name is going to be rounded large drop shadow medium border object cover and below that let's add a paragraph which is going to render the title of the course with a text neutral 700 text Center and let's not misspell neutral text Center font Boldt and margin top of three and there we go we have our flags perfect so when you click on them nothing much should happen but you now officially are are loading Flags directly from your database so what we have to do next is we have to create a new table inside of our schema which is going to be called user progress and in there we are going to store the current active course ID so what I want to do before we wrap up is not allow the user uh actually we're going to do that in the next one yeah I wanted to do it in this chapter but I remembered we do need uh that schema model to even start doing that basically if the user doesn't have any course selected they are not going to be able to visit any of these Pages learn leaderboard quests or shop so they're always going to be redirected to the courses page if they don't have any of the flags uh so I'm going to be calling them Flags or courses throughout the tutorial so you don't have have any of these selected this is the screen they will see until they click on at least one and of course they will always be able to go back and change them and the progress will be saved great great job so now let's actually create a new table inside of our schema where we are going to store which course is the user currently taking so besides the courses let's go ahead and write export cons us user progress that's going to be a PG table again with the name of user progress let's give it a user ID which is going to be a text we're going to call it user ID and that's going to be our primary key because it's going to be unique for every user let's also store the username here that's going to be text user uncore name let's give it not null and default is going to be user let's give it user image source which is going to be text user image source not null and the default is going to be slm mascot. SVG and then let's pass in the active course ID that's going to be an integer so let's import integer from drizzle orm PG core so make sure you add it from here not from my SQL or something else if it's going to be called activecore course uncore ID and we're going to add a references to it open an arrow function and call courses. ID from above and now in the last argument we can open an object and Define how it behaves on the lead of the parent in our case we want it to Cascade and then let's add the Hearts which are going to be an integer again called Hearts they're not going to be null and the default is going to be five and let's copy this two more times and let's call this uh our points and actually we just need it once and let's give it a default points of zero and now let's go ahead and do the following so let's go ahead inside of the courses here and let's X const courses relations and let's add relations from drizzle orm courses as the first argument and let's extract the manyu from the arrow function here and in here uh actually let's wrap this in parenthesis because we want an immediate object return and now let's write user progress to be many user progress like that and then so just make sure you imported the relations from drizzle orm then let's do the same thing here so let's write export con user progress relations relations user progress the structure one and again open immediate object here and write active course one courses open an object Fields user progress active course ID and then references course. ID my apologies courses. ID there we go so let me zoom out so you can see how this looks all in one line so we defined a relation many to many for user program and courses and we defined a one to many for user progress and an active course so let's go ahead now and push this to our database so I'm going to go ahead and shut this down and I'm going to write um what is our method mpm run database push so let's wait a second and see if this will be successful so there there we go it says changes applied so let's quickly go ahead and uh visit our drizzle Studio here so MPN run database studio and let's see if anything has changed there we go so we now have a relation with the user progress and we have the user progress table so let's go ahead and actually create a method which will happen once the user clicks on one of these flags right here also if at any point uh you get stuck uh with a drizzle migrations right so if you have a feeling you wrote something wrong here before you did uh the database push right and it gets stuck uh the way to resolve that is to very simply change your environment key so simply remove your database create a new free database and just add a new database URL and then with your new full schema do npm database push again so I am going to demonstrate that at one point in the app if we get stuck and if we have some migration issues so Prisma has something called npx Prisma migrate reset which just resets the entire database and all migrations and everything drizzle does not have that so you would actually have to do like a proper migration which is of course the right thing to do but in development I just find it quicker to delete my database and every single table everything a lot of times rather than doing the migration right so that's why you can always visit neon database and remove your database from there and then just add a new post new pogress URL to your database URL if you get if you even get stuck right great so now that we have this uh I want to go ahead and head back and inside of our app folder inside of main courses and specifically inside of this page. DSX uh and in here what we're going to do is we're going to change this so it actually loads uh so it actually loads the user progress as well so let's go ahead and keep this open and prepare a query for that so open this we already have get courses and now let's add export con get user progress so that's going to be cached as well it's going to be an asynchronous method and let's go ahead and let's extract the user ID from await out from clerk nextjs so I'm going to move this with this import here if we don't have a user ID it means we cannot load that so I'm going to return null there is no user progress for this user otherwise let's do con data and let's do await database. query. user progress find first where equals let's import equals from drizzle orm and let's pass in the user progress from do/ schema so I'm going to go with s/ database schema so user progress do user ID and comma user ID so those are the values which we are comparing and let's add with active course true whoa so we also populate the active course relation and then let's return the data there we go so now let's go inside of our courses page here and what we are going to do here is use that new query so just below that let's add const user progress to be wait get user progress from database queries like that and then for this active course ID we're going to do user progress question mark active course ID and if I save this I'm going to get an error here because I have a type of number in the list here so this is the perfect opportunity for us to do the thing I was talking about and change this to be an uh an exact type of that uh an exact Ty an exact type of that schema so I'm going to use type off here and I'm going to use the user progress import from database schema I'm going to use infer select and I'm going to select the active course ID and then I'm just going to mark this as an optional prop and there we go no more errors here and now as you can see no language and no course is selected for me right because I have not chosen any of the languages so what I want to do now is I want to add a redirect from the learn page if we don't have user progress also if you want to you can change this to be the following so if you want to use promise all for this you can do courses data here and courses data here remove the AWA here and then then you can do const promise. all and passing the courses data and user progress data and then from here you can extract the courses and the user progress so this should work exactly the same uh did I do something wrong here uh let me just quickly check oh this is supposed to be a wait Pro promise all there we go so if you want to you can do that I don't think this exactly helps you uh progress I have a typo here I don't think this exactly uh helps you with the waterfall because this is a server component right but if you want to have them inside of a promise all you can do it like that I think this is recommended as a best practice in nextjs documentation again I'm not entirely sure this resolves the um the the waterfall issue because this is a server component right so the network tab is not exactly being blocked at least I think that's how I understood it right so but I am going to be using this practice throughout the tutorial for when I have a lot of this calls so in here I removed a wait so the only thing I prepare actually is a promise so by default I cannot do anything with this so perhaps data is not the best naming we could call it promise but you know they they I know what I mean by data here and then I pass that in the promise all and then I extract the value of courses and user progress and use it as I did a moment ago so let's go ahead back inside of our learn page so app folder main learn page. DSX let's mark this as an asynchronous page and in here let's go ahead and prepare our user progress data to be our get user progress from datab Bas queries and let me add them here to the top and then in here I'm going to go ahead and call await promise all user progress data and I'm going to extract user progress so let me prepare the collapse of this because we are going to have multiple items in the future so very simply I'm going to do if there is no user progress or if there is no user progress active course in that case let's redirect using next navigation to/ courses so import redirect from next SL navigation and let me just move that to the top here there we go so now that that we have this right here if you try and go to the learn page you should be immediately redirected so if you go from the homepage here and click continue learn there we go you can see how I am immediately redirected back to this page and you did see a little blank screen here for a second don't worry we are going to add uh the loading States uh perhaps we can already add the loading state so let's go inside of the learn here and let's add a new loading. DSX here let's import the loader from luid react and let's do const loading Remember to prepare a default export so loading in here we're going to do a very simple div which takes the full width the full height and places the items in the center of the screen both vertically and horizontally and inside we're going to use the loader component we're going to give it a height of six a width of six and text muted foreground and animate spin let's go ahead now and uh try this again so you can also copy this loading and paste it in the courses and there we go so now if I uh refresh this you can see how we have a nice little loader if I go to the learn page there we go so we no longer have that blank screen now there are actual loaders inside of our applications while we are being redirected so what I want want to do to wrap up the chapter is that when we click on this is we actually create a new user progress inside of our database so let's first prepare the queries which we are going to need while we create this database action so inside of database let's go inside of queries here and let's export con get course by ID is going to be cache asynchronous method which will accept the course ID which is a number of course if you're working with uu IDs uh yours is going to be a string so in here let's do const data await database query courses find first where equals we already have this imported courses. ID course ID and let's import courses from database schema so just make sure you have courses and user progress and you already have equals imported from drizzle orm and let's return data and I'm going to add a little to-do here to do uh populate units and lessons so we don't have these tables yet so we cannot do that but later when I load a course by ID I want all the lessons and units it has so this is fine for now what I want to do now is I want to go inside of my list component in the app main courses so inside of the list component here we mark this as Ed client because we're going to have some interactive on click here so let's go ahead and do the following let's add a router from use router which you can import from next navigation don't accidentally imported from next router that is the old import then let's extract pending and start transition from use transition which you can import from react itself so this is going to help us to use a server action and its pending State and then let's do const on click to accept the ID which is a number again if you're using uu IDs or anything similar to that it's going to be a string for you if we are pending let's break the function no no need to allow it if the user has clicked on a course which is currently the active course ID no need to do the whole database update we are just going to break the function with the return and we are also going to redirect the user to slash learn and then let's do start transition so this is if user is selecting a new course in here we're going to call our server action which we don't have yet so let's go ahead and create a server action so in the root of my application I will create a new folder which I'm going to call actions and inside of here I'm going to create the actions for the user progress entity so I'm going to call the file user- progress very important whenever you're writing server actions is to have used server at the top of your file otherwise it's not going to work so let's write export const upsert user program it's going to be an asynchronous method with a course ID and a number here and then let's extract the user ID from await out from Clerk nextjs and D is lowercase let's also get the entire user from current user from clerk nextjs if we don't have user ID or if the user is missing we're going to throw new error from the server action called an authorized so whenever you're working with server actions behave like it's an API call right so you need to do authorization here as well then let's attempt to fetch the course which user just selected so await get course by ID from this database query which we just created a moment ago and pass in the course ID then if there is no course we're going to throw back a new error which will very simply say course not found so this will be a 404 if this was an API route and here's what I'm going to write if there is no course. units. length or if horse units first in the array lessons. length so if we don't have any lessons or units I'm going to throw new error here course is empty and then I'm going to comment this entire thing out because we don't have units or less lessons at the moment so at the top I'm going to add one more comment to do enable once units and lessons are added so right now no need for this error but I don't want to forget it later and then let's go ahead and do the following con existing user progress and let's do a wait get user progress from our database queries so we already have this method created and we've used it a couple of times already let's let me go back inside of my user progress server action here so if I already have existing user progress this means that user already had has been active in some course so all I have to do is await database from database drizzle so just make sure you add this do update and in here I'm going to use the user progress schema so make sure you add that from database schema here and we're going to do do set here and we are going to update the active course ID with course ID as simple as that and just to improve our user experience here we can also update the usern name here with user. first name or let's go ahead and add user as the default or and we can also do user image source with user Dot and let's go we're using clerk so let's find where our image is here uh is it profile image URL it says deprecated so now it's image URL user. image URL there we go or SL mascot. SVG like that so we don't have to do these two Fields but since we are using an external uh out provider when user changes their profile image using this manage account method right here it's not going to be immediately reflected to the leaderboard right so we're going to do the following since we don't have any web hooks or any background jobs which keep track and synchronize the state of Clerk and our database every time the user changes a course we're going to do a proactive update of their name and their image URL just in case they have changed it and the reason I'm doing this pipes here is just in case these two Fields fail we're going to go ahead and fall back to what we have defined in our schema here where I use user as the default username and mascot. SVG as the default user image source and this mascot. SVG we are already using so you don't have to add anything we already have that in our header and in our sidebar so this is our logo so let's head back inside of our user progress here so that is if user existing if existing user progress uh exists and otherwise here's what we're going to do we're going to do await datab do insert user progress. values user ID because we don't have that so we have to pass that the first time we are creating it active course ID is going to be the course ID that the user selected and then we can just copy and paste the username and the uh user image source here great but that is not enough so this will definitely create a new user progress in our database but remember all of our queries are cached which means we need a way to revalidate this cache which we are using and nextjs has a method of doing that so we need to import the following after we add a new record to the database let's do revalidate path from next slash so let me add that to the top here next SL cach I'm going to revalidate the following P slash courses slash learn and from the server action I'm going to redirect the user again we're going to import this from next navigation I'm going to redirect the user to/ learn and let me copy this and do this also in the existing user progress here so this will break the method right so once we hit this it will not go go further so now that we have this set up let's actually go ahead and use this upsert user progress so let's go inside of the list here and let's go ahead and add upsert user progress imported from actions user progress and let's pass in our ID and that should be it so let me just go ahead and check this out and let's pass this on click uh right here I think this should be enough on click accepts the ID I think that should work because our card will pass it like this great let's try it out so inside of my drizzel studio Let me refresh it here I don't have any user progress here once I click on Spanish I believe there we go I am redirected now remember this is hardcoded to Spanish so this actually has no uh there's no reason why this is a Spanish flag we are going to do that later but let me just refresh my user progress now there we go we have an active course ID of one which resembles Spanish we have five Hearts zero pors uh zero points and we have fullon relation with the active course perfect and we have the user image from Clerk and my username and my user id which is also my primary key if I go back to courses there we go Spanish is now selected and now let me just go ahead and improve this by actually using uh this pending state so if I go back to list here I have hardcoded the disabled to be false well let me actually use pending now there we go so now if user tries to click on French there we go you can see how it's disabled while it's loading perfect and French is selected I can go creation and there we go and I can always go back to Spanish but this is going to call upsurt and not uh insert as it does with Italian for example this is inserting right now I have all of this so now my user progresses uh uh so of course we only have one user progress for user right so we are only going to change the active course ID the actual progress is going to be stored in other additional tables right but we are going to know which table to load for which course thanks to this user program which will tell us all right the last time user selected this course ID so load the progress for that course ID great so we officially have a course selection uh one more thing I want to do before we wrap it up so I want to go ahead and I want to install soner so let's do npx shat CN UI at latest add soner so this will add uh soner to our um to our project so let's go inside of our root app layout here and let me import toaster from components UI soner and inside of the body here I'm going to go ahead and simply add the toaster component self closing tag like that now let's head back inside of our list component where we just added this start transition and upsert user progress and let's add a very simple catch here where I'm going to go ahead and call the toast from soner package so not from any add components from soner directly so in layout we call components UI soner but in here we call the soner directly and let's go ahead and do an error here something went wrong and now if I go inside of upsert user progress and let's pretend that we have an error here so I'm going to write throw new error test so let's say one of these goes wrong right either course not found or unauthorized or in the future maybe this let's see if that will log a nice toast message there we go something went wrong perfect so just remove this test we don't need it but now you know that you have a nice Edge case covering here perfect great great job what we are going to do next is we're actually going to use the user progress to indicate to the user the actual active course so if I click on creation we expect this to say creation and here to be a creation flag we also want the points and the hearts to be an actual resemblance of what is in the database we are also going to learn how to seed our database with a script and how to completely reset our database in case we run into any problems great great job so now I want to show you how you can use a script to seed the database instead of using the studio as we just did in the previous chapter and I also want to show you how you can completely reset your database if for any reasons you run into any migration issues especially later when we start adding some relations to our existing elements that might cause some problems so let's go ahead first and learn how to build a seed script so I'm going to go ahead and do the following I'm going to go ahead and create a new folder scripts inside of your root project so I have it here already because I tried it out so you probably don't have this so just anywhere in the root of your project create a scripts folder inside of it we are going to create seed. TS file now let's go ahead and let's import our environment SL config so that we can load the environment variables from here then let's import drizzle from neon HTTP and let's import neon from neon database serverless and now let's import everything as schema from do/ database schema let's define SQL and let me just not misspell this so constant SQL is going to be neon and it will use process environment database URL and and as always double check that you have the database URL inside of your environment file there we go it seems like I had a typo here again so make sure that you always confirm that otherwise you're going to run into problems which are going to look hard to debug and uh are just going to cause unnecessary issues so let's add typescript ignore for the next line because in here we have to Define our database which uses drizzle and passes in the SQL and adds our schema to it and now let's go ahead and Define our main method which is going to be an asynchronous method and yeah if you're watching this into the future try and remove TS ignore if you're not getting this typescript error it means that these two packages have fixed their type compatibility but until then you can just add typescript ignore and you've already seen throughout the project that everything works fine so it's just a type error Inc compatibility here so inside of this asynchronous function I'm going to open a try and catch block here and first thing I want to do is I want to catch the error in case seing our database goes wrong so I want to add console. error and I'm going to go ahead and pass in the error here and then I'm going to throw new error failed to seed the database and then inside of here let's add a console log seeding database and first things first I want to go ahead and remove all the existing elements so database. delete let's remove schema. courses and let's go ahead and delete schema. user progress and then let's conso log seeding finished so for now this is what's going to be our seed script so now we have to find a way to run this script so if you go ahead and shut down on your app and if you try and go and run n node scripts and run seed. DS you're going to get an error that you cannot use an import statement outside of a module here so what you would have to do is turn all of this into constant and then instead of using from you would have to use require right so that's how you would do it if you were running it with node but there's no reason for us to do that in 2024 there's a lot of other packages which we can use the simplest one would be to use TS node or DSX and of course let's just do one more thing so we defined this function but we never call it so let's just make sure that we call the function so just execute main here at the bottom so this is what I'm going to do I'm going to go ahead and I'm going to install in my development dependencies a TSX package and now I'm going to do the following I don't want to write this full script every time so I'm going to go inside of package.json and I'm very simply going to add database seed and that's going to be TSX and it's going to go to/ scripts SL seed. TS so let's just go ahead and confirm that this is working so I'm going to go ahead inside of my terminal here and I'm going to run npn run database seed and there we go seeding the database and seeding finished so if I am correct what should happen now is that I have no languages inside of my database because I've completely removed them and there we go no languages exist here so now let's go ahead and find a way to do this uh to actually add some languages here so I'm going to go ahead back inside of my seed script here and let's write await database insert and we're going to we going to use schema. courses here and let's add the values and let's open up an array so for the first one we're going to give it an ID of one give it a title of Spanish and let's go ahead and use a image source of sl. SVG like that so now let's go ahead and try this again so I'm just going to open a new terminal for now no need to shut it down npn run database seed there we go seing the databases seeding finished let's see if this is working and there we go I can now even select this language and then I can go back and user progress exists perfect so now we're going to go ahead and copy this and do it for other languages so yeah you can uh you can or don't have to write the ID here right but it can be useful especially because this is a seed script so if you want to modify your seed script to specifically add some challenges to a specific ID you can do it like this right so you will know exactly which course is which ID if you want to you can completely remove this and see how there are no errors because we are using serial inside of our uh courses here if we were to use integer here so if I save this and if I were to remove the ID there we go I have an error because I need to manually pass IDs that's why we are using the serial here so it's Auto increment ing every time and then you don't have to pass the ID but for a seed script it can be quite useful for your initial data so then I'm going to have Italian here and I'm going to use it.svg I'm going to go ahead and add the third one which is going to be French FR FR and I'm going to go ahead and add creati as the last one I believe I have the creation flag here you can of course add any flag you want from anywhere it does not matter there we go I have creation here great so now let's go ahead and run this again and then I'm going to show you one more way you can uh see your database so I'm going to go ahead and refresh this and there we go and I have all the flags here so there is a reason why I chose TSX to run this script right here so first things first I want to use a typescript file for that I cannot do uh node run I technically can but it would need some modification then I have an alternative of ts- node which definitely can run typescript but it kind of gets stuck when I am importing some files which then use uh es next Imports here so it gets confused with that so then I found DSX which works perfectly but there is one more thing which works even better than all of this and comes out of the box without any configuration and that is bun so if you are able to install bun it should be very simple using this uh script but the reason I didn't want to show you this initially is because I know it's still in development for Windows so if I go into documentation here installation there we go so we have Mac OS and Linux and there we go we have Windows which provides the limited experimental build uh for window windows but it is recommended to use it within a Windows subsystem for Linux so I don't use Windows so I'm not sure what exactly this is I am aware that there is a thing called Powershell there so I guess it can work fine with that but I didn't want to do something that some of you cannot follow so that's why I added TSX because TSX should be supported everywhere so let me just try one more time by using bun here so I'm just going to change this to bun because I already have bun installed in my project you can see that it's green so I have bun right so let's go ahead and do the following MPN run database seed I know it's funny we are using npm to run a uh bun script but you know let's just confirm that it works and there we go works just fine and then if you're using bun what you can do is you can also replace these npx commands with bun X commands so let me show you that as well this is just if you want to play with bun a little bit so now now I can go ahead and I can just do bun for example ban database studio and there we go that's it that opens the drizzle studio just like that using a much shorter command so that's if you want to use bun but don't worry I am going to continue with npm for this tutorial so I'm bringing this back to npx npx and I'm going to run npm commands the reason I'm doing that is because I'm waiting for bun to be a bit more covered in the development world when everyone is familiar with it so that we can uh you know so that everyone can work with it without any problems great so now that we know how to seat our scripts let me show you how you can completely reset your database because what we are doing here in the seed script is not resetting our database we are just removing uh the fields here so here's the thing you can do if you ever get stuck if you ever get any migration errors if while developing your skill you think that you've messed it up uh you know the drizzle push command is no no longer working you cannot fix it anyway here's what you can do leave your schema as it is go back into your neon or wherever your database is hosted and just remove your database and then I'm going to go ahead and create a new database I'm going to call it lingo again I'm going to click create and then what you can do is you can copy your connection string again if it has changed I believe that Ian doesn't change the connection string on database change only on Project change so my project is exactly the same so I believe my database URL does not need any changing uh so let me just go ahead and try this is my app running it is there we go so you can see how now I have an error relation courses does not exist because I've completely reset my database which means that I no longer have tables not just the records so our seed script oops our seed script in in the beginning removes the records but it doesn't remove the tables and the relations so that's why sometimes it's useful for development only to remove your entire database for me that's just faster obviously the proper way of doing this would be to create migrations which drizzle has amazing documentation about but again for development for my case I like to Nuke my entire database so I can just start over especially if I'm trying to try something out for a second so what we have to do once we've nuked our database is confirm that you're getting this kind of error when you do npm run Dev and then what you have to do is mpm run database push so this will now push all the new schemas inside of your uh drizzle so there we go changes applied so if I refresh my tables tab here there we go I now have courses and I have user progress here I still don't have anything inside side of here so if I go mpm run Dev here I'm still going to go ahead and be completely empty so that's why I'm going to prepare a new tab where I'm going to use my seed script to fill my courses so mpm run database seed there we go seeding the database seeding finished let's refresh and there we go so what I want to do now to wrap up this chapter is make sure you select at least one course here here and then let's go ahead and change the header well Spanish is a bad example let me pick something that is in Spanish for example French here uh so I want to change this to display the French flag I want it to say French here and I don't want to be these to be hardcoded to 105 I want them to read from my user progress uh record from the database so let's head back inside of our learn page page so make sure you have your app running here let's find the app main learn page. TSX right here and we are already loading the user progress data so we already have user progress and we already do this redirect to slash courses if we don't have user progress or if we don't have active course selected so now that we have active course selected we can do the following first let's do the user progress here so we can now remove this and we can pass in the user progress. active course instead and then for the hearts we can do user progress. hearts for the points we can do user progress. points and let's leave the has active subscription to false because we don't have that table yet there's nothing we can do here so let's go inside of the user progress and let's go ahead and do this to do which says replace with database types so I'm going to go ahead and remove this entire thing here and I'm going to write type of courses from database schema do infers select and semicolon at the end there we go so let me just align this here and that's how you add types from drizzle so now you can see that active course is exactly what we expect from our schema perfect so if I already try this there we go we have the French flag we have zero points and we have five Hearts so if yours is not working for any reason just confirm that inside of your active course you're using the image source for the source here so let me go ahead and go visit my schema so inside of my course I have image source field and inside of my user progress I have a relation with the active course so to access the image of the current course the user is taking you you would go through user progress. active course. image source exactly what we are doing here so just confirm you have those fields so now that we have our user progress here sorted we have to go ahead and resolve uh our header component which still says Spanish so go back inside of page here find the header and simply go ahead and write user progress. active course. tile there we go so let's go ahead and look at it now and now it says French perfect so if I go back and if I select Spanish now now it says Spanish with a Spanish flag if I go back and choose creation now it's says Creation with creation flag perfect so this little if check is very useful for us because this redirect counts as a return method right so if I didn't check for this part then what I would have to do is I would have to add this question marks here and question marks here and question marks here and then I would have to change my types to accept this to be optional and then that would cause problems with this so you can see how much times it saves us just the fact that we are doing an early return here so yes this is the same thing as writing return redirect you don't have to do it because if I go ahead and add a console log here I'm not sure if uh vs code knows this so it knows you can see that this code is unreachable so when I hover it says unreachable code detected so it knows that nothing will run after this redirect of course this will run because this is inside of a different scope than this if Clause perfect so we've wrapped that up we can now seed our database in different ways and we have Dynamic header here on the learn page as well as Dynamic points and hearts you also know how to nuke your database you know how to reset your record and you know how to seed your database with TSX and with bun great great job what I want to do now is wrap up our entire schema so far inside of our database folder schema here we have defined the courses and we have defined the user progress we have also defined the relations between the two using courses relations and using user progress relations so what I want to do now is wrap up my entire schema with everything else that needs to be added in here the reason I want to do that is because for the next queries which we need to load which in here are going to display the current unit the current lesson and all the subsequent other lessons as well as their status whether they un locked finished or unlocked we have to have all the necessary fields because the only way we can create such complex queries is by querying those relations in the database so let's go ahead and do the following the first thing I want to add is units so let me add that just below the courses here so export con units is going to be a PG table of units it's going to have an ID which is going to be a Serial of ID and a primary key we're going to have a title of each unit which is going to be a text title and it's going to be required meaning not null and then we're also going to have a description let's give that a text of description and not n as well so the title would be something like unit one and the description will be learn the basics of Spanish for example then we would have the course ID so we know for what course is this unit 4 right so if our description is going to be learn the basics of Spanish we have to have a matching relation with the Spanish course so let's go ahead and make this an integer let's do it like this course uncore ID and let's add a reference here so I'm going to write references and I'm going to open an arrow method and I'm going to provide it with course. id courses. id so we read from this constant above and then we're going to go ahead and Define on delete Cascade and let's add do not n at the end so it should all be in one line like this great and besides the course ID we are also going to have an order of this unit so we know how to render it and how to display it on the learn page so I'm going to pass this to be order and not null as well perfect now let's go ahead and let's create a relation with the units so I'm going to write const unit relations relations units let's extract many and let's extract one because we are going to need both but for the first one we just need one so let's write course to be one coures Fields let's go ahead and use the units course idid from above and let's reference that to courses. ID field right here that's all we can do for now so now what we have to do is we have to create lessons so we have unit relations here let's go below here and let's write uh const my apologies I forgot that we also have to add units to the courses relations here and let me just move the courses relations right below the courses like this so I'm going to go ahead uh and also add units here many units like this so make sure inside of your courses relations you have added units many units so courses can have many units but each unit can only have one course relation so make sure you have that setup working you can of course always visit my schema from the gith source code and I recommend that you always do that so you confirm that you didn't write anything wrong great so we have defined the unit and unit relations now what I want to do is write my lessons so let's write lessons to be APG table of lessons let's write ID serial ID and primary key now let's go ahead and give our lesson a title which is going to be a title text and a title and not null now let's go ahead and give it a unit ID so it has a relation so that's going to be uh an integer unitor id. references we're going to pass in units. ID on delete Cascade and not null perfect and we are also going to have an order order of a lesson as well so order is going to be an integer order and not now we could technically use the serial or integer incrementing ID for the order and that was what I did initially but later I found that that's probably not the most reliable thing to do and it's better to have something else to keep track of how we are going to order our lessons and display them to the user here right so lessons are going to be those little Islands which you saw in the beginning that user will be able to click on and the units are going to be the big kind of like chapters which hold multiple lessons inside so now that we have defined our lessons here we have defined that each lesson needs to have one unit ID so let's go ahead and create the relations for that so I'm going to go ahead below this and write export con lessons relations so let's write relations lessons let's extract one and many and we return an immediate object here like that and let's give unit a relation of One units Fields is going to be lessons. unit ID and references are going to be units. ID just like that so that's all we can do for now but now that we have lessons defined we can go back back inside of the units relation right here and let's call this units relations so if our uh model name is units let's call them units relations as well and lessons lessons relations so we are consistent so I prepared the many extract here but we didn't have anything to create a many to many relation so let's say that units can have many lessons inside uh uh my apologies lessons many lessons like that there we go so each unit can have many lessons but individual lesson can only have one unit and something is missing from the units relations and that is that I export this constant perhaps you have done this I have forgotten there we go now we have lessons and we have lessons relations but each lesson is going to have something else each lesson is going to have a challenge so let's go ahead and create that as well we can copy this in fact so let's just copy lessons now so we save some time and let's rename this to challenges and let them remove everything besides the ID so we're going to have an ID which is primary key we're going to have a lesson ID which is going to be an integer lessore ID references lessons. ID on delete is going to be Cascade and again not null all in one line like this besides the lesson ID we're going to have a type of a challenge so to create a type of a challenge it's best to use enums let's do export const challenges enum and let's use PG enum from drizzle dorm slpg core so make sure that you have imported PG Anum as I did right here from drizzle orm PG core so just where you have your PG table serial and text already inside of the PG enm here we're going to define a type and the possible types are going to be select and assist so in the future if you want to add a different type of challenge like a voice or listen or something like that you can just add them here and that will solve the backend part for it so inside of the challenges here we now have to define a type so we're going to use a type of challenges any like this and let me just be consistent so challenges with an e and in here I use challenges so let me just fix that little typo challenges Anum and we're going to store this in the database as type and again not now so this is required and then we're going to have a question which we are going to ask the user so that's going to be a text of question and not now perhaps question is a bit too specific because I am going to reuse that in two different ways in one type of challenge I'm going to reuse it to for example which of this is an apple and then the user will have to select the proper answer but in the assist it's going to be different the question is simply going to say apple and then the user will have to select the translation right so it's going to be a bit different so if you want to you can change this to maybe label but I'm going to stay true to my original source code so I'm going to leave question just so I don't create any problems and I'm also going to give a useful order field here so in case you want to store your uh challenges by hardest or by some other arbitrary value you can always do that so they are not you know added to the user in a random order you will have control which challenge will appear to the user first in a given lesson so now that we have defined this we have to create challenges relations so let me copy the lessons relations from here let's rename them to challenges relations make sure that you change the first parameter here to be challenges D structure one and many and let me clear this inside so we're going to have a lesson which is going to be one lessons with two s Fields is going to be an array of challenges lesson ID and references is going to be lessons. ID and now we have to go back inside of our lessons relations and we can now use the many property so write challenges many challenges with an E there we go so perfect we now have backto back uh backto back relations with challenges and with lessons and with units and with the courses in the end but some stuff is still missing so let's go ahead below the challenges relations and we have to create two more so we have to create challenge options and we have to create challenge progress so let's go ahead and copy the challenges tab right here and let's go ahead and create challenge options let's rename the PG table to challenge options and let me see do I have challenges misspelled anywhere I do have again challenge options it should be challenge options make sure you don't have this type post so this needs to be consistent right so I just copied and pasted this from challenges itself right so that's where we left off we renamed the constant and we renamed the PG table so what I'm going to do now uh is I'm going to go ahead and change this from lesson ID to be a challenge ID I'm going to change the integer to be an again I wrote challenge challenge ID challenge uncore ID which is going to use challenges whoops challenges. ID on the lead Cascade is correct instead of type well we're not going to have type in the challenges options so you can remove that the question is not going to exist instead it's just going to be text and we are going to store that as text in the database the order is not going to exist for challenge options if you want to control challenge options you can do that as well to nudge the user in the right direction maybe but these are basically uh answers which users can choose between right so I don't have the need to order them so in here I'm going to give them a correct option which is going to be a Boolean correct and not null and let's also import bullion from PG core so just make sure you are using this imports from PG core I just added Boolean make sure you didn't accidentally import it from uh my SQL for example great so we have the challenge ID relation with challenges. ID on theet Cascade we added the text we added correct and now we have to add an image source which is going to be a text of image undor source which is not going to be defined which is not going to be required because if the challenge type is assist in that case we don't have images to show the user and we're also going to have audio source which I also want to add I I also want to make optional because it is very useful and we are going to make it have sounds but in case it doesn't it should not exactly break the app right in case you don't have an audio for something you still see the text and the image if it exists so audio is not exactly something we should rely on great so we have the challenge options ready here now we have to copy and paste these challenges relations and create challenge options relations so let me change this to be challenge options relations and let's go ahead and use the challenge options column from above now let's extract just one we don't need many at all and instead of having a relation with the lesson we're going to have a relation with the challenge so we're going to use challenges the fields is going to be challenge options do challenge ID so this one which we defined here and the reference is going to be to challenges. ID itself there we go so we created challenge options relations and now we have to go back to our challenge Rel challenges relations where we have prepared the many extract so besides lesson challenge is also going to have a challenge options in a form of a many relation so let's add challenge options and there we go now each challenge can have a lot of challenge options but each individual challenge option can only have one challenge great now that we have defined that there is one last thing that we need to add which is the challenge progress so let me copy and paste this thing again and let me just okay let's keep the user progress here at the end instead let's copy the challenge options relations and the challenge options and let's rename the copied challenge options and the PG table challenge options to be uh challenge progress like this and let's leave the ID uh and let's go ahead and add a user ID which is simply going to be a text of user ID like that and in Let's uh and yeah let's leave the challenge ID to be exactly as it is so challenge ID challenge id challenge. id on theet Cascade and not now and very simple thing we're going to do is add one additional field completed Boolean completed do not now default false so this is how we are going to track which challenge has user completed and then we're going to use the challenge progress to later calculate the uh percentage completion of an individual lesson so every time the user selects a correct answer we are going to upsert or create a new challenge progress with completed true in the database and it's going to have a relation to the challenge ID like that and let's also add let's make this required so if we don't have the user ID um we can't exactly do anything with the challenge progress after all so let's make this not null uh I'm going to add a to do confirm this doesn't break it shouldn't but I notice I don't have this in my original source code but in here I'm noticing that it should probably be required so have this little to do here and now let's go ahead and let's create challenge progress relations like that so let's go ahead and use the challenge progress constant as the first argument here make sure you change that and it's going to have a relation to the challenge using the challenge progress. challenge ID and the reference is going to be the same so this can stay exactly as it is and now let's go back to our uh challenges relations so both challenges options in here we have the challenges and the challenges relations we have defined a new challenge options many now let's do challenge progress many challenge progress there we go we have wrapped up our entire schema there is one thing missing here which is the user subscription column but I want to leave that last when we are actually going to work with user subscriptions because if we write it now we're going to kind of forget what we've written but everything that we've written now is what we are going to build next so we do need these relations to work as they should so let's go ahead and check if we have everything in order so I'm going to go ahead and do the following I'm going to npm run database Studio because I want to see my studio here so let me open that up and there we go we have some problems relation challenges doesn't exist so let me close the drizzle studio and let me try running npn database push let's see if that is going to break the app will this work as smoothly as I expected and there we go I just got changes applied so this should mean that everything runs pretty smooth if you for any reason did not get CH changes applied here you can do what I did in the last chapter which is completely nuke your database so don't just run database seed which will simply remove the fields no go go inside of your neon or wherever you have your database running and completely delete the database so you purge all the existing tables and relations and then try running npm run database push again so I just run that first time now I'm running it the second time no need for that but I just want to see what happens there we go it still says changes applied so if I go and run database studio now let's go ahead and refresh this to see do we have any errors now we did not have any errors but what we do have is all the necessary Fields so challenge options challenge progress challenges courses lessons units and user progress also if there was a problem with relations here drizzle would have thrown and would have thrown an error but it didn't this means that we have coded everything correctly so if you're having any relation problems here I highly advise that you take a look at the GitHub source code or ask a question in the Discord but please search for it first perhaps it was already solved and then just go ahead and copy and paste each individual table until you notice one that you have written incorrectly and then you can see what is it that you have missed great so one thing I want to do is go into neon and confirm that I have those tables in there as well and there we go so I am inside of my lingo project inside of tables here I have challenge options challenge progress challenges courses lessons units and user progress perfect exactly what we need and let me just uh oh I just noticed something so challenge progress and challenge options should not be camel case they should be with an underscore in order to stay consistent I don't think it will break the app but let's stay consistent and let's use this ability which we have from here so let's find the PG table challenge options and let's change them to be underscore challenge options and then let's do the same thing for the progress so I'm going to do challenge unor progress like this so we should not have any camel case in the definition like like this so This camel case is fine but inside of the definition that goes into the database it should be with an underscore so here's what uh I'm pretty sure I'm going to have to Nuke my database now of course in production you would do a migration for this so I have to emphasize that right what I'm doing is just something I want to speed up uh for development so if I try running mpm run database push let's see if perhaps this can be resolved easily we'll see so mpm running and there we go so now dreel is asking me is challenge options created or renamed so I'm going to select using the arrow keys to rename the last option from challenge options camel case to challenge uncore options so I'm going to select that and then it's asking the same thing for challenge progress so it's not created it is renamed so let me select the last one challenge progress to challenge unor progress rename table let's see if that will be there we go all table conflicts resolved changes applied let's try this one more time so now if I refresh my neon here let's see what happens there we go challenge uncore options and challenge uncore progress and to wrap it all up let's also confirm that inside of my studio here so I'm just going to go ahead and refresh this and there we go challenge uncore options and challenge uncore progress again if you're having any errors here you can go inside of your databases here and go ahead and click delete and remove it and then click new database and try DB push again great great job so now that we have our schema finished I want to go back to our seed script and I want to create a couple of units lessons challenges and challenge options so let's revisit our scripts folder seed. DS so if you've done it like this like I have you should know the ID of each of your courses so that can be quite useful for us first things first first let's go ahead and extend our uh removal before seeding so let's await database. delete schema. units then let's go ahead and copy and paste this and let's delete our lessons and then let's go ahead and do that for the rest of our for the rest of our uh tables here so we have challenge options here we also have challenge progress here and I think that should be enough so make sure that you remove everything first in the seed script and then after we create our courses let's go ahead and do the following I'm going to go ahead and pick one course ID so I'm going to pick Spanish so that is ID of one and in here I'm going to write a wait database. insert schema. units. values I'm going to open an array and I'm simply going to add the very first unit here with an ID of one and course ID of one so this is Spanish right because I know that from above and then let's create a title with unit one description is going to be learn the basics of Spanish and order is going to be one there we go uh and it seems like I have to add a comma here there we go so now we should have a unit so let me go ahead and prepare those two so I'm going to mpm run database studio in one terminal here so let me just open that so we can keep track of things then I'm going to open a new terminal and I'm going to go ahead and do mpm run database seed so let's go ahead and see if this will work properly there we go let me just refresh this and now I have one unit and four courses so if I go inside of my courses here uh I should have the units for the first one which is the Spanish one if I go ahead and choose the second one I have nothing here so I only have it for the very first one which is Spanish if I click on units there we go unit one so let's go ahead and check it out actually inside of its own view here there we go ID of one unit one learn the basics of Spanish matching course ID order and a relation to the course but we do not have a relation to the lesson here so let's go ahead and do the following we're going to go ahead and extend our seed script here so we're going to create some lessons so again we now know the ID of our unit unit so we can use that as our unit ID so let's go ahead and write await database insert schema do uh my apologies. lessons. values and let's pass in ID of one unit ID of one which is going to be uh our unit one learn the basics right then we're going to to have an order of one and a title for example nouns and then you can go ahead and copy this and give it an ID of two keep the unit ID to be one and give this an order of two and make this verbs for example so we would have two challenges in this unit my apologies two lessons in this unit so we can try running our script again npm run database seed we're going to remove everything and then we're going to create some new things so inside of here we now have two lessons as you can see so let me go ahead and expand this there we go so as you can see we now have a relation with the unit here both of them should have the same unit there we go unit one and if I close this and check the second one there we go unit one as well let's check the units here and there we go uh one thing that I'm noticing here is that this is called lesson but I think this should be called lessons instead let me just quickly check that inside of my schema here so we are talking about the units table let's find our units table and perhaps we've made a mistake there we go so I wrote course here and I've probably used the single way uh of expressing lesson here as well when this should have been lessons because we're using the many relation so I'm going to go ahead and quickly confirm that in my original source code yes I use lessons there so I made a mistake find your units relations which should be just above your units table and make sure that when you use many you use the multiple uh type expression of the word so I'm going to quickly confirm that I do that in other places as well units many for Progress it's okay because it'll be progresses I guess so it's just easier for me to spell it like this so I'm fine with progress the same with I believe we also have challenge progress right so I'm fine with this not being in a expression of a multiple that's fine of course this won't mess with your code right but I think it's uh it pays to be consistent so there we go let me go ahead and do this I'm going to write uh colum many and I'm going to check all of them options progress is fine progress is fine units lessons challenges options progress there we go everything else is perfectly fine so let me save this schema now and I'm going to go ahead and shut down my studio here and let's see if I can fix this by running npn run database push so will this give me an option to fix the conflict by uh selecting the rename or is this going to be a bit more complicated perhaps nuking the database hm it seems seems like changes have been applied let's see if that is true ampion run database studio so will this simply rename uh this so if I go ahead and refresh this there we go now it says lessons looks like it was a very easy uh Conflict for drizzle kit perfect if you run into a problem you can always go into neon delete your database and simply run npm database push again again after you've created a new database if needed you might also need to change the environment key in your database uh URL field but for me it was just a matter of renaming it in my schema saving the file and running database push and now this says lessons as it should perfect and let me finish wrapping up my seed script here so we now know how to insert lessons and now that we know how to insert lessons we can do the same thing thing uh but for for uh challenges so let's go ahead and do the same thing so we can pick an ID for a lesson so let's pick ID one that will be nouns right so I'm going to do await database insert schema. challenges do values and I'm going to pass in an ID of one lesson ID is going to be one which is going to be our nouns and then we're going to have a type of a lesson which as you can see uses our enum so this is going to be a type of Select for example order of one and question is going to be which one of these is the man for example so let me just change this from not using double quotes but instead I'm going to use single quotes so that I can then use double quotes in the string like this so for example I want them to translate the word men into Spanish right which would be El ombre I believe uh please app uh excuse my pronunciation of that uh there we go so if you want to you can create uh another challenge here with an ID of Two and a lesson ID of two which would then be verbs right and then you can give this an order of two if you want to but I'm going to keep it simple and I suggest the same so let's actually do this let's remove the Second Challenge from here uh the second lesson let's just keep the first lesson here and let's just keep the first challenge for the first lesson here just so we have the same results right so I'm fine with having multiple courses here but let's keep the units one lessons to one challenges to one so we know exactly that each of these is only for the last one and now let's go ahead and do the following let's wrap it up by creating challenge options so schema. challenge options. insert my apologies. values let's give this an ID of one challenge ID is going to be one image source will be SL men. SVG which we don't have yet but we are going to have later correct is going to be true text is going to be L ombre and we're going to have an audio source which is going to be slore man. MP3 so that's how I'm going to label my audio files I'm going to prefix them with uh a language and then what they are representing and I'm going to show you how we're going to use 11 Labs AI to generate AI voices for different languages uh there we go so this is the correct uh challenge option so for this question which one one of this is the man I want them to select this option which would have this picture and which will say El ombre so let me copy this give this an ID of two and yes this is the challenge ID right so we created this challenge right here and it has an ID of one so this will represent uh which one of these is the man that is the question that this is answering for the same is true for this one but this one will be a woman correct is going to be false and instead of L ombre it would be luh M and let's change this to eswan MP3 and let's give it a third option so ID of three the challenge ID stays the same because these are all options for the question above right and this would be a picture of for example a robot and this is also false this would be L robot and we would have an es robot right here there we go so I believe that is fine uh this is enough for us to work with uh we can now pretty much render everything we need the one more thing we're going to have to do later when we get to that is we're going to create one more challenge with a different type so this is a type of Select later we're going to create a type of assist but make sure you choose select for this one because it's easier uh so let's go ahead and go inside of our terminal here and let me go ahead and open a new one and run npm run database push so this should add all the new values now let's just wait a second there we go changes applied Let me refresh my database studio and I should have there we go I have four courses I have one unit this unit has two lessons nouns and verbs but it seems like I don't have any challenges Let me refresh this to see if it is true it seems like I don't have oh because I ran the invalid command I should have run database seed not database push there we go mpm run database seed my apologies wrong command and now I should have the following I should have four courses one unit one lesson because we remove that so we only have nouns now and I should have one Challenge and this challenge should have three options inside and only one of them should be correct there we go perfect we now have our setup here and make sure you have the same if you don't have the same I want you to go to my GitHub and copy this seed script that I will prepare for you and as well as confirm confirm that your schema is working as well make sure you do all the necessary mpm run database push here's a quick reminder how our database push looks like so database push is npx drizzle kit push postgress of course if you were using my SQL your would be different right database studio is simply drizzle kit studio and our seed uses DSX and runs from the scripts folder so now what I want to do is close everything let me close this and go with mpm run Dev again so I no longer need to look at my database because I know everything here is correct and what we are going to do now is we're going to go ahead and select Spanish because that is uh the ID which we created in our seed script as the one which has all the units all the lessons and challenges so there we go course ID for my units is Spanish which means that I have to select Spanish in order to load all of my units here so that's what we are going to go ahead and prepare so let's quickly go ahead inside of our app folder main learn and let's go inside of page. TSX and now we have to create a query similar to get user progress inside of our database queries right here called get units so when calling this units I also want them to call all the lessons and challenges within and I also want to help us on the front end by normalizing our data in a way that we are going to introduce a new field called completed so that's not something I want to keep in the database so I don't want to constantly update that status whether we completed a lesson or not so instead this is what I'm going to do I'm going to use our field called course progress to detect sorry challenge progress uh in to detect whether we finished a specific lesson or not so let's start simple let's go ahead and do export const get units that's going to be our cache function my apology it's going to be a cached function that's what I wanted to say and it's going to be an asynchronous arrow function here and first let's get the user progress and let's to await get user progress if we don't have user progress or if we don't have active course ID let's simply return an empty array no units to load for this user then let's go ahead and get our data using await database. query. units. find many let's use where equals units which you can import from database schema so let me go ahead and prepare this like that so that it's easier to read so import units from database schema so ensure that the units. course ID equals user progress active course ID so that's which units we are going to load and then let's use the width attribute here to load the lessons so usually we would simply write lessons true but if we want to load the challenges within that lesson we have to extend this and use width again so let's write challenges again oh it looks like I have a misspell of challenges here let me go ahead and quickly fix that in my schema there we go I have challenges here so let me change this to challenges so it's proper make sure you don't have any typos as well and let's see if we will be able to easily resolve this so I'm going to shut down my app and I'm going to run empty and run database push let's see how easy is this going to be or are we going to have to Nuke our database there we go changes applied and let me just quickly check is my seed script working now looks like my seed script is still okay so inside of my seed I don't use that populated field challenge there we go so seed script is completely fine let's go back inside of my queries here and now let's use challenges there we go so with challenges and again withd challenge progress true so I want it all the way to challenge progress and now that I have this data I'm going to go ahead and create normalized data that's going to be data app we're going to get individual unit open a function inside of here we are going to create lessons with completed status so let's do units sorry individual unit. lessons. map let's get the individual lesson here and this is all of course in one uh line like this and then let's do const all completed challenges to be lesson. challenges. every challenge open an arrow function here and let's return challenge. challenge progress and challenge challenge progress. length is at least one but also challenge challenge progress. every progress of that individual challenge progress is completed so progress. completed there we go so then now that we have all completed challenges here for an individual lesson let's go ahead and return the existing Lesson by spreading it so simply spread the lesson and then completed is going to be all completed challenges so this is going to return true or false so we are checking whether every single challenge in this lesson has a matching challenge progress with a status of completed if that is true it means that we can mark this lesson as completed entirely so this is going to help us on the front end so I want to do this heavy computation purposely on the back end so then I can easily use the completed Boolean true or false on the front end and I can change the state according to that the this can of course be improved you probably already see how this is uh how this would increase the more fields we have right so feel free to modify this using reduce feel free to explore this uh using drizzle remember drizzle allows you to use uh practically SQL query Builders itself so if you know how to do this in SQL go ahead and do it uh it's probably uh much much better and faster to do it this way but since this is our first tutorial with Drizzle I want to go easy on us and uh use what we know how it works right so I'm going to go ahead and do it this way for now and now that I've returned the lessons here and the completed status it means I have I have these lessons with completed status so what I have to do now is return the existing unit and theend the new lessons which are going to be lessons with completed status lessons with completed status so let me see if I did this uh correctly or not my apologies not here but here this is where I'm supposed to add them so make sure that they are in the correct scope I'm going to zoom out just a little bit so you can see this clearly so we have normalized data this is the scope and this is uh where exactly does it end normalized data uh why is it not yeah basically this is where it ends but my uh or maybe I forgot to add an end this now I'm am a bit confused oh my apologies all right so this is our get unit function which ends right here let me add a semicolon here then this is our normalized data data map which opens this Arrow function which ends right here my apologies so inside of this data. map we create lessons with completed status in here we get a Boolean whether all challenges are complete for an individual lesson if they are inside of this map interation of the lessons we return that very lesson but we introduce a new field called completed with a Boolean true or false depending on whether the all challenge progresses have the completed status and then I am at the same time iterating over normalized data so the all the units here as well so then what I do is I use these lessons with completed status and I spread the existing units here but my apologies I should not spread the existing units I should spread the existing unit so make sure you don't do the same mistake as I do because this is iterating over that specific unit so we spread all the fields in that unit which is the ID title description course in order and we already have the lessons but I'm going to replace those lessons now so that all of my lessons have a new field called completed so then on my front end it's going to be very easy for me to just check in a DOT map whether a lesson is completed or not so that's what I'm doing that so I reduce the workload on the front end and then what I have to do is return normalized data there we go so that is my get units method perfect now that we have that we can go back inside of page and we can add units data so con units data is going to be get units and make sure you import get units from database queries right here and let's go ahead uh and let me in fact load get user progress first uh actually it won't matter since they're been a promise so but yeah let's go ahead and add our units from uh units data there we go now we have our units so let's go below the header here and let's write units map let's get the individual unit let's create a div let's give it a key of unit. ID let's give it a class name of margin bottom 10 so each unit has a spacing and in here I'm going to Json stringify my unit so let's try this out so I'm going to go and refresh this and I have to have my app running as well so let me click on Terminal here and do mpm run Dev and now let's go ahead and see our there we go Spanish we have a Ty title unit one and that unit one has lessons and that lesson is an array and you can see we have a title nouns and then in here we also have completed which is false because these challenges all have their challenge progress but as you can see challenge progress for us is an empty array because we never completed any challenge meaning that there is no way this lesson could have been completed and this is very useful for us now that we have this completed because we can easily change the design on the front end now and this is of course going to be cached so if we repeat this method across our app at the same time uh it's not going to uh make too many calls to the database again I recommend you do this exactly as I'm doing it the first time you're developing especially because it's a bit of a complicated uh normalization of the data here there so then later if you want to and I highly advise that you explore can you do this with the drizzle quy Builder if you can do this with pure SQL that would be amazing and much much faster or perhaps you can do this with reduce if you think that will be a more optimized Loop instead of a loop within a loop right so obviously this is not the fastest way of doing it but I think it's good enough for a tutorial so in the next chapter we are going to go ahead and turn this Json stringification into an actual UI design great great job so now I want to change this from a stringify Json into an actual UI but just before we do that I want to revisit my queries here inside of the DB folder specifically the get units query here one thing that I think is missing here is additional filtering for challenge progress so right now we are loading all challenge progress for all challenges which is correct but let's take a look at our schema so inside of our schema if we visit and find the challenge progress we see that we have something user ID which means that we can query by user ID and we should query by that so no point in loading this query and using challenge progress of other users so instead I'm going to go ahead and import user ID I'm going to destructure user ID from await out and we already have out since we use it in user progress here from clerk so I'm going to extend this if query so if I don't have user ID or if I don't have user progress and then in in here let's go ahead and extend this with awar equals challenge progress make sure you've added an import for challenge progress right here from database schema challenge progress. user ID needs to equal our extracted user ID from this await out method right here so right now if I save this and refresh nothing should change we still don't have the challenge progress and completed is still false great later we're going to see if we can actually use the order by here and use the order so I'm simply going to add a comment to do confirm uh whether order is needed so I'm going to add this to do and we're going to uh check that later great so we have this and now let's go ahead inside of our app folder inside of main learn page. PSX inside of here we are rendering our units so now I want to change this Json and render a unit component we don't have that yet so we have an error here but let's already prepare some items let's pass in unit ID let's pass in unit order let's pass in the description unit. description let's pass in the title let's pass in the lessons and let's go ahead and passing the active lesson for now let's make it null we're going to add this later and active lesson percentage for now is going to be zero as well so now let's go ahead and create this unit here so inside of the learn folder I'm to create a new unit. TSX component and I'm going to go ahead and create the props for it first so the props are going to have an ID which is a number or a string if you're using uyu ID order is going to be a number title is going to be string description will be a string as well lessons open parenthesis are going to be a type of lessons from database schema in for select but we are normalizing them which means that every lesson of ours is also going to have a completed Boolean and all of that together is going to be an array so make sure you add an array at the end then we're going to have an active lesson which is going to be uh a type of lessons in for select and we are also going to have its relation with the unit so let's add a type of units from database schema do infer select or it can be completely undefined and active lesson percentage is going to be a number so make sure you've added the Imports for lessons and units from database schema so that you can use their infer select and then let's export const units here my apologies unit and let's assign the props here and let's return a div now let's go back to our learn page and let's import the unit from do/ unit so the same way we added an import for the header in here I have an error for the active lesson so instead let me write undefined here because we support undefined as we've written right here in the active lesson props so now I'm going to go ahead and extract ID order title description lessons active lesson and active lesson percentage and now let's not use divs here instead we can use fragments and I want to go ahead and do the following I'm going to create another reusable component called Unit Banner and I'm going to pass in the title and I'm going to pass in the description prop so make sure you have these two and now we're going to go ahead and create the unit Banner inside of the learn folder as well unit db. CSX so let's create our props here which are going to have a title which is a string a description which is a string and let's export unit Banner here let's assign these props and let's return a div let's go back inside of the unit and we can import unit Banner from do/ unit banner and now we should no longer be having any errors and now let's go ahead and actually make this visible by D structuring the title and the scripture from the unit Banner now let's give this div a class name name of full width rounded extra large background green 500 padding of five text of white Flex items Center and justify between now inside of here I'm going to open a new div with a class name of space y 2.5 and inside I'm going to render an H1 element and write title inside and then below that a par paragraph with a description inside and there we go unit one learn the basics of Spanish let me change this to be an H3 element instead so it shouldn't exactly be a header and now I'm going to go ahead and give this a class name of text to Excel and font bulb and in the paragraph section I'm going to give it a class name of text large as simple as that and then what we are going to do is go outside of this div encapsulating our H1 element and the description and we're going to add a link from next SL link let's add a closing tag to it and let's give it an HRA to go to slash lesson and then inside of here let's use a button component so make sure you've added the button component uh the button import from our reusable components here inside we're going to add an icon not book text from Lucid react so I'm going to move that to the top here let's give notebook text a class name of mr-2 and let's write continue and for the button let's give it a size of large let's give it a variant of secondary and let's go ahead and give it a class name of hidden but on LG is going to be visible so if you want to see it you have to zoom out a bit there we go now it should be visible LG is going to be Flex border das2 border Das bottom is going to be four an active border-bottom is going to be two so let's go ahead and see how this looks I'm going to zoom in a bit there we go so if I click continue here I'm redirected to a 404 page which right now doesn't exist but it is going to exist in the future so we do have a kind of a edge case here I suppose if you want to you can change this to only display on two Excel maybe or Excel let's see if that exists it does so let's see how that looks like so here it's still visible yeah maybe this is a bit better so it only shows on very large screens because there isn't really a need for this button because the same effect is going to happen if we click on an individual lesson here right so I think yeah this is okay so let's go ahead and go back inside of our unit component here because we are finished with the unit banner and inside of here we're going to go ahead and create a div with a class name Flex items Center Flex column and relative and then inside of here we're going to map over our lessons so let's extract the lesson and the index of the lesson then inside of here let's open a method and let's define is current by using lesson id lesson. id to be equal to active lesson question mark. ID and con is loged is going to be if lesson is not completed and if lesson is not current so we are not going to allow user to select a lesson in the future and we can do that quite easily by determining has the current lesson we are iterating over already been completed if it has we are not going to lock that lesson user can always go back and practice it but if the user never finished this lesson and if this lesson is not the current lesson that can only mean that it is a future lesson so we are going to lock that for the user and finally let's go ahead and return a lesson button like that so let's go inside of the learn component and let's add a lesson dasb button. DSX now in here I want to mark this as used client because it's going to be interactive let's create type props here with an ID of number index of a number total count of a number as well lock of an optional Boolean current of an optional Boolean and percentage of a number and Export const lesson button here and let's assign these props and for now you can just go ahead and return a div component like that let's go back inside of our unit and let's import the lesson button from do/ lesson button now in here I'm going to pass in the key to be lesson. ID I'm going to pass in the ID to be lesson. ID in IND is going to be index total count is going to be lessons do length minus one current is going to be is current locked is going to be is locked and percentage is going to be active lesson percentage so we are going to use this total count later so that we can determine whether this has been finished and where does it stand in comparison to other lessons great so now that we have that let's go ahead and simply render uh for example well we can just render the ID let me destructure these props here ID index total count locked current and percentage there we go you should see a number one inside of this representing the ID of this lesson so what we have to do next is style this lesson button now this is how I want to style my lesson button so styling it is easy but positioning it is difficult this is what I'm talking about so this is the finished app as you can see every single one of these lesson buttons has its own position and this is calculated in a way that it will go one two out one in and then back inside so it would go this as many lessons as we have so we're going to have to do this mathematically right so let's go ahead and do the following let's go back inside of here and let's define uh the logic for this so we're going to start with defining the cycle length to be eight then we're going to create a cycle index IND by using a modulus operator on the cycle length and index then let's define the indentation level now I'm going to check if cycle index is less or equal than two that means that I'm moving right from 0 to two so I'm going to create an indentation level here to be cycle index else if cycle index is less or equal than four I'm going to move back from the center from two to zero so indentation level equals 4 minus cycle index else if cycle index is less or equal than six I'm going to move from left to -2 so now we go in the other direction so indentation level is = 4 minus cycle index else we are going to complete the cycle and go back to the center so indentation level cycle index minus 8 now let's calculate the right position on this using this indentation level here so const right position position is going to be indentation level and times our spacing so you can experiment with this depending how far wide you want elements to go from one another I found 40 to be uh the best looking one you can of course experiment yourself now I'm going to add a couple of more useful elements here const is first is very simply going to check if index is zero const is last very simple is going to check if index is equal to the total count of all of our lessons and const is completed is very simply going to check if we are not on a current one and if the this one is not locked that's how we know it's completed and then let's create a dynamic icon if is completed we're going to use check icon from Lucid react so make sure you add check from Lucid react let's also add a crown and a star so check crown and Star from Lucid react now in here is completed is going to do check otherwise if is last it's going to do Crown otherwise it's just going to be a star and let's do an HRA if is completed that means the user can click on this and they can go back to slash lesson slash the ID of the lesson otherwise user is just going to go to/ lesson and from there we are going to load whatever is the current active lesson so we are not going to use slash ID in order to go to the current active lesson that's just going to be slash lesson and then we are going to decide which one that is so I'm following the Dual lingal logic if you look at their app architecture they do the same thing so if you go and practice previous lesson they use IDs inside of their URLs but if you click on your current lesson that you need to practice which you have not finished yet they just use slash lesson and then they determine which one to load so we are doing the same logic here you're going to see that later right now this is a 404 page but later when we create logic it's going to make a bit more sense so before we start styling this I want to install a package react circular progress bar so let's go inside of here and do npm install react circular progress bar progress bar is spelled together like that now that we have this let's go ahead and add an import for this so I'm going to import circular progress bar with children from react D Circle circular progress bar and now we also have to add react circular progress bar slist SL styles. CSS so it has the Styles let's also prepare a CN library from lib utils and let's import a button from components UI button and let's wrap it up by importing link from next SL link now let's go back inside of our actual return method here and let's wrap the entire thing in a link component the link component is going to go to our Dynamic HRA constant now let's give it an area disabled of locked so the link doesn't work if the lesson is locked and let's also use the style here to disable the point of events if we are locked so if we are locked we're going to use none otherwise outo if you want to you can collapse these elements so that they are more readable like this now inside of the link component I'm going to open a div here and we can remove this ID so our div is going to be our wrapper and we're going to use it to position so class name is going to be relative but we are going to be using style here for some Dynamic stuff so the right is going to use back right position constant pixels margin top is going to check if is first and if it's not completed it's going to be 60 otherwise 24 so the reason for that is is that the current lesson is going to have a little floating popup like a little tool tip saying start so when it's first we need to move it just a little bit away from this Banner otherwise it's going to hover over the banner and it's just not going to look that good great now let's go ahead and do that little uh floating Banner so if we are current let's go ahead and create a div here and let's go ahead and kind of hack this so I want us to hardcode the current so let me go inside the unit current I'm going to do this go back inside of the unit. DSX find the current and for now do true pipe pipe is current and I'm going to add to do remove hardcoded true so I want you to trigger this to be true the current has to be true right now so we can show this uh so we can show this right here so let's just add an alternative here div something there we go so you can see how something is not rendering so now if I write current current is rendering so that should be the same thing for you make sure you put true here so that then when you destructure the current from the props for the lesson button you can see what you're working on right now let's go ahead and give this div a class name of height 102 pixels and a width of2 pixels and a relative class name then let's create a here so this is going to be our floating little element here this div is very simply going to say start let's give this div a class name of absolute minus stop minus 6 is this correct absolute all right minus stop minus 6 left- 2.5 PX of 3 py of 2.5 border of two font is going going to be bold and uppercase text is going to be green 500 background color is going to be white rounded extra large so we have some roundedness animate Das bounce tracking wide and Z index of 10 there we go you can see how we have a nice little start floating element here what's missing is a little little Chevron here at the bottom so let's create it just below this we're going to create a self-closing div this self-closing div is going to have class names to create a Chevron so let's give it a class name of absolute left 1 and A2 minus bottom minus 2 width zero height zero border X8 Border X transparent border top eight transform and minus translate dx-1 and A2 and there we go you can see how we now have a little Chevon here and you can see how this one is bouncing and it's going to be bouncing just above our current active lesson so now we solved this little bouncing element let's go ahead and create the actual uh Circle so we're going to be using this import here I've given up on trying to pronounce it every time so just copy that from above go outside of this div which has the start text so outside like this render that and it's not going to be a self closing tag because it's going to have children so first let's pass in the value which is going to check if number is n on the percentage default it to zero otherwise use the percentage then let's go ahead and also pass in some Styles here so styles are going to look for the path let's go ahead and write a stroke here and let's use 4 a80 and let's use Trail stroke E5 E7 EB and then inside of here let's go ahead and use the button component which we've added an import for and let's go ahead and give this a size of rounded so make sure that you have added this go inside of your button component and check if you have the size of rounded there we go rounded full then go back inside of the lesson button here and besides that give it a variant prop with a Turner so if it is locked we're going to go ahead and we have to create a new variant called locked so for now leave it like this and then add secondary here and let's go ahead and give this a class name of fixed height 70 pixels and fix the width of 70 pixels as well and the Border bottom of eight now let's go ahead and create an icon element here which is going to be a self closing tag so this icon already exists because this is a dynamic constant which we have created from here so uh let's go ahead and let me just see if I can uh okay this is not in half I thought this might be in a hex code but it's not okay never mind let's go back to this icon and I want to give it a class name let's make it Dynamic so using CN you should have CN imported from lib utils use H 10 with 10 and then if it is locked go ahead and write F neutral 400 text neutral 400 stroke neutral 400 otherwise fill primary foreground and text primary foreground comma now let's check if is completed bill is going to be none and stroke is going to be four so not four pixels just four there we go let's go ahead and save this now so now what I want to do is create the locked variant so let's revisit our button component and let's go ahead and just above default let's create lock let's give it a BG neutral 200 then let's go ahead give it text primary foreground hover BG neutral 200/90 border neutral 400 border bottom four and active border bottom zero there we go so now we have our locked state right here and let's just go ahead and see where we uh how do we get locked so we get it from above oh so let's do it like this I'm going to remove this to-do from here I'm going to bring this back to is current and I'm going to change the constant from here so this is where I'm going to write true or do the comparison so this is the too remove later right and then there we go so this is how our first one is going to look like right so let's try this let's go inside of our seed script here and and in here where we Define we have our lessons here so how about we create two lessons so give this an ID of two keep the same unit ID give it an order of two and give this verbs for example so I'm going to go ahead and do npm run database C here and let's see if that's going to uh make ours a little bit different here so both of this should now be active yeah so perhaps we've created a little bit of a problem here with with our hard coding but this is what I want you to see you can see how they have different icons now so only the last one has the crown representing the end right others will have a star and you can see how this one moves with an offset so let's go ahead and create a couple of this right so I'm going to go ahead and give this an ID of three and an order of three the title really doesn't matter so let's go ahead and give this a four an order of four then this is going to be five and let's see how that is look how that looks like I just want to create a full cycle perhaps we're going to need uh eight for that let's just see or five is enough there we go 1 two 3 four five so you can see how we have a nice first half of the cycle so if I were to create five more then now it would go in the other direction right so then that's what I wanted to sh and now it looks funny because all of these have a start right but we are going to change that later so we can already go to the lesson button here and we can not to the lesson button uh to to unit right here and we can remove these to do remove later and go ahead and remove that right now so now all of this should be locked I believe there we go so all of these now have just this alternative something text so we have to create uh that as well but this one is going to be easier so for this one what you can do is just copy the button so go inside of this circular progress bar with children and copy the button component with the icon inside and all of the logic and go ahead and instead of rendering a div just render that button like that there we go so now uh it looks like these have a completed status I believe that's simply because we don't have the proper fields that we are sending in here so these would technically be locked right but they are not locked right now because we are lacking the logic right so we are going to go ahead and do that later and then they are going to be locked uh if not if we made some mistake I'm going to go ahead and make sure that we fixed that but there we go we have created this logic now so what we're going to do next is we're going to go ahead and create the logic to load the current active lesson which we currently not have so right now if I go inside of my page here you can see that the active lesson is undefined so we have to create a method which will load the current active lesson and its percentage and then we'll see if the other lessons are going to change their status or did we make a mistake in the logic great great job so now let's go ahead and let's change this active lesson and the percentage from being undefined and hardcoded to zero to use actual data from our database so we're going to have to revisit our queries so let's go inside of here and let's go ahead and create a few new ones so I'm going to go here at the bottom and I'm going into export con get course progress it's going to be cached it's going to be an asynchronous function so open an arrow function here and let's go ahead and extract user ID using await out from Clerk and let's get user progress from await get user progress now let's check if we don't have user ID or if we don't have user progress ress active course idid in that case you can just return null there is no progress we can load for this user now let's do const units inactive course that's going to be await database query units find many let's order by units make sure you have them imported right here so you need those let's go down here units and let's destructure ascending and let's return ascending using units. order then let's add a filter equals units. course ID and user progress. active course ID and then let's add with lessons and let's add order bu to the lessons here so let's extract the lessons and ascending and return ascending lessons. order so the same way we did with units order let's also add unit to be true and challenges and let's also add their challenge progress where the current user ID equals the challenge progress user ID this is similar to what we did with our get units challenge progress. user ID equals user ID from our Al method there we go now that we have units in active course let's find the first uncompleted lesson so that's the lesson that we are going to show to this you user as the one they are currently active on so con first uncompleted lesson is going to go over units in active course it's going to use flat map get the individual unit and return unit. lessons here and then it's going to use find and it's going to find the lesson where some of its challenges are uncompleted so let's return not challenge challenge progress so if we don't have challenge progress at all that could be a candidate for the first uncompleted lesson or if challenge. challenge progress. length is zero so this is in one line like that and then let's go ahead and return active lesson to be first uncompleted lesson and let's also add active lesson ID as first uncompleted lesson question mark. ID just for ease of use great so now we have something to call for our course progress but we are not done yet so what we need now is to create a get lesson method so let's go ahead and do that I'm going to go here at the bottom now going to export con get lesson get lesson is going to be cached method it's going to be an asynchronous function as well let's go ahead and extract the user ID so not cached just cash from await out and let's get course progress this time from await get course progress and here's one thing I forgot get lesson will have an optional ID which is a type of number or if you're working with uyu IDs it's going to be a string I'm working with a type of Serial so it's a number so this time we're going to be using get course progress so if user didn't pass an ID then we're going to load a lesson which is located as the first uncompleted lesson in this unit for that course so user can all we're we're going to reuse this method when user wants to practice so if they click on a specific lesson and we have an ID of that lesson then we're just going to load that lesson otherwise we're going to assume that we want to load the first uncompleted lesson for the user so let's define that by writing lesson ID to be either the ID from the parameters or course progress. active lesson ID if we don't have any type of lesson ID so neither from the parameters nor from our course progress that means there's nothing we can load and now let's go ahead and write const data to be A8 database query lessons find first where lessons make sure you add the import for lessons from DB schema here so let's go all the way down here who lessons. ID equals to lesson ID constant which we have defined here and ensure that we always have at least one and let's add with challenges let's order this challenges so let's use order bu here challenges ascending and let's use ascending challenges. order let's go ahead and include the challenge options and let's include the challenge progress as well so we know how much of these of this lesson has the user completed so only where challenge progress do user ID matches the user ID and let's see what did we do wrong here challenge progress do user ID do we have user ID here we do have but something here seems to be incorrect so we do have challenge progress so if I just write challenge progress true that is fine but if I add where that's where it gets stuck so let's see do I have an autoc completion for user ID I do have that can I manually type something I can oh so we don't have a check whether user ID is perhaps undefined my apologies so let's go ahead and do the following in here if we don't have user ID we are going to break early and return null no need to even try to get course progress if user is not authenticated we're not even going to waste our resources on this so we're just going to break and return null so now that we have ensured that in this part of the code we have user ID we no longer have have that error in our where query great so let me just add these commas here because I need to have them and now that we have that let's go ahead and check if we don't have data or if we don't have data. challenges that means there is nothing we can display for this lesson so again we're going to break early and return null and if all of our tests have passed let's normalize these challenges array so I want to uh do the similar thing that I did when when loading my get units where I normalize my data with the completed field so let's do the same thing for the challenges here so it's easier for us to work with them on the front end so con normalized challenges are going to be data. challenges map let's get individual challenge let's define whether they are completed or not so a challenge is completed if we have challenge do challenge progress and if challenge do challenge progress do length is larger than zero and let's return challenge completed again it's Challenge and let's pass in completed to be completed or you can use the shorthand operator like this and now let's go ahead and finally return data so we are loading our entire lesson here right but we're going to replace the challenges array with our custom normalized challenges here so let's write challenges normalized challenges there we go and let's be consistent and let's just add one more check here so I'm going to add if we so if we have challenge progress so let's do it like this if we have challenge progress if challenge progress length is larger than zero and if challenge. challenge progress. every let's get progress so if every progress is completed that's also what I want because uh if you take a look at my up uh previously normalized challenges here I think we do uh the same thing so let me just check that here there we go so in here I do the same thing so I check for challenge progress for challenge progress length and also for Progress comp completed and inside of get course progress let me see if I should extend that to here as well so first uncompleted lesson challenge. challenge progress I think this is fine because in here we are doing the opposite in here we are looking for the first uncompleted lesson so I think there's no need to uh check if they are oh well I guess what we could do is we could also do ch challenge uh do challenge progress. sum progress progress do completed equals false so I guess we could do that so then what would happen is we we we're going to check if we never had any progress initiated or if we uh have some progress but for some reason it's false right so I think that should be fine but I am going to add a little too here uh if something does not work check the last uh if clause which is this one and I'm going to copy this and I'm going to write the same thing here just because this is not exactly true as my source code so maybe it will break something later but I doubt it I think we improved upon my source code by doing this great so now that we have this we can create one last method which we need which is to load the lesson percentage so let's export con get lesson percentage to be cached a synchronous method is going to get course progress from await get course progress if there is no course progress or course progress active lesson ID return zero because lesson percentage should return a number so now let's get the lesson using await get lesson which we defined above and we can simply pass course progress active active lesson ID so technically we don't even have to pass this because get Lesson by default we'll use that but let's be explicit and let's pass it this way if there is no lesson we are again going to return zero and then let's do con completed challenges to be lesson challenges. filter challenge let me just collapse this so it doesn't break into a line so challenge challenge. completed that's our filter and then we can create our percentage using math.round and we can now uh use the following so we can do completed challenges do length divided by lesson. challenges. length in this one times 100 I think that should be it and just return percentage there we go so you can see how normalizing our challenges has helped us so we can just easily filter out those that are completed and then we divide the completed challenges by the total challenges in this lesson multiply them by 100 and we round the number to get the percentage of to get the percentage of how much this lesson has been completed there we go so when I hover over this there we go it always returns a number perfect now that we have get lesson percentage we have our get lesson and our get course progress we can head back inside of page. DSX here and let's add those to our uh promise here so I'm going to go ahead and I'm going to add course progress dat data to be get course progress so make sure you add that import I'm going to add lesson percentage data to be get lesson percentage from queries and I think that should be it so make sure you have added the get course progress the get lesson percentage the get units and the get user progress from your database queries so these new ones which we just created so now let's add those uh to our promise all here so we have user progress and units let's go below them and let's add course progress data and lesson percentage data so after units we are getting our course progress and after course progress we are getting our lesson percentage and now we can go ahead and pass the active lesson here instead of undefined to be course progress. active Lon now this will have this will give us an error here so what we can do oh let's just go ahead and first check whether we have course progress so if we don't have course progress we can just uh redirect again to slash courses here so there we go passing course progress. active lesson inside of here and go ahead and pass in lesson percentage here there we go all right so something has changed this is now unlocked but all of these are marked as completed so that seems like a bug so I'm going to debug what's going on here uh but I think this is okay let me just check one thing so this has unit it seems uh when I was developing this initially for some reason I got a typescript error here so right now I'm not getting it but in case you are this is how I fixed it so I wrote as type off so let me zoom out for this as type off lessons from database schema so let's go ahead and import that from database schema here I'm going to add it to the top I wrote lessons. infer select and then I extended that manually here and added unit type of units schema so we have to import this as units schema because we have units here already so we can't add that import from the database schema so I'm going to import units as units schema so we have an alias here so type of units schema. infer select and let's add pipe undefined so that's what I had to do for my active lesson here right now it seems to work fine without this type but for some reason my typescript has lost its uh scope inside of map that was at least during the first iteration perhaps I have a newer typescript version now maybe it was a bug fix uh nevertheless if you don't this is how you fix that so just before I wrap up the chapter I'm going to go inside of my queries here uh where are my queries and I want to check if I have any typos challenges I do so I just don't like this typos it's supposed to be challenges with an E so let me just use the challenges here uh but now I have an error here so order by challenges implicitly has an any type oh if I just reload my window will that fix the typescript error yes so it was just some weird type error here and challeng again there we go no challenge errors here all right so it seems like we are having a little bug here these are being displayed at least to me as finished so I'm going to have to debug a bit why that's happening and we're going to resolve it in the next chapter uh and one more thing I want to work on is instead of this get units we are loading these units and these lessons and challenges but I think we need to do this too I need to add the order the same way I've added the order right here when loading I believe my get lesson yes you can see how here I use challenges and I use their order in ascending order so that's what I'm going to do uh in the next chapter we're going to go back inside of this uh get units so I think right now even if I added more of this they would be in correct order simply because my seed script has serial incrementing IDs so obviously it's working fine even without me using the order by Method but we're going to play around with that later uh priorities for now are to check why some things are uh displaying as finished when they obviously should not be and then and we're going to go ahead and do the other stuff nevertheless great great job uh so after that we're going to go ahead and resolve this 404 page which is going to be our lesson page so I found out why our lessons are appearing as completed so it is because this first lesson has some challenges so if you go inside of seed here let me close the courses we have units and then we have lesson here but we have only created the challenges for the first lesson we never did it for all the other lessons thus these query don't really work as expected so this is what you can do uh well technically in production this should never happen you would never publish a lesson which doesn't have a challenge but nevertheless find your get units method inside of the queries and then go ahead inside of the normalized data and more specifically go ahead inside of lessons with completed status and in here we are very simply going to do an early return so if unit. lessons my apologies if lessons. challenges individual lesson. challenges. length is equal to Z in that case you can go ahead and early return the entire lesson and completed is false and once you save that there we go you can see how all of this without any challenges have turned to lock the fields so this is now looking like something we would expect great so you can do this early return if you want to uh again I don't have this in my original source code and you saw it from the original demo that it works just fine when all the lessons actually have some challenges in inside of them right but since we're using this hacky script uh some of them don't have it and then we have to do additional checks like this but perhaps it could be a good check if we miss it and yes later we are going to work on an admin dashboard so you won't have to use the seed script at all so now what I want to build is the learn page when you click on continue or start right now you get a 404 so what's crucial for the learn page is that your uh get course progress is working so make sure that your first item here is selected right otherwise you are not going to be able to develop the lesson page so let's go ahead and create that 404 page I'm going to go inside of my app folder and I'm going to create a new folder called lesson as simple as that inside of that I'm going to create my page. DSX and let's export the lesson page just like that so this one is outside of our main folder so it's not going to share the same layout so now if you try and click on a start button you are redirected to a lesson page same is true if you click on the continue button here so if you are not go ahead and revisit those components and ensure that the link components redirect to slash lesson let's go ahead and start developing this now so we already have a couple of methods which we can reuse here so let me turn this into an asynchronous method and let me prepare the lesson data here to be get lesson and let's import get lesson from database queries let's import user progress data to be get user progress from database queries and that's going to be it for now let's go ahead and use await promise. all and let's add the lesson data and the user progress data and then we can finally extract the lesson itself and the user progress and now what we are going to do is we are going to check if there is no lesson or if there is no user progress we are going to redirect the user from next SL navigation so let me add this to the top and separate the two Imports we're going to redirect the user back to the learn page so that's why I said it's crucial that your get lesson is working so our get get lesson will rely on the get course progress if no ID has been passed so your get course progress needs to be working it needs to be able to find the first uncompleted lesson in the unit so I'm going to go ahead and click here and I am not being redirected meaning that this is working it found the first uncom completed lesson great so let's go ahead and do the following let's go ahead and Define the initial percentage here we are going to need this later so lesson. challenges here filter get the individual Challenge and then simply do challenge. completed and then length and divide that by lesson. challenges. length time 100 like that so we get the initial percentage we are going to need some initial stuff here for our component called quiz which we are going to create now so let's Create a quiz component here let's pass in the in initial lesson ID to be lesson. ID let's pass in the initial lesson challenges to be lesson challenges let's pass pass in the initial Hearts to be user progress do hearts let's pass in the initial percentage to be initial percentage and user subscription to be undefined because we don't have that yet so now we have to create our uh actually let's make it null instead so let's go ahead and create our quiz component now inside of the lesson folder create a new file quiz. DSX what's crucial about this is that it is going to be a client component so that's why we are passing these props to serve as initial data and then we are going to modify them to go into a state and then that state is going to be changeable like hearts and stuff so let's go ahead and Define type props initial percentage is going to be a number initial Hearts is going to be a number initial lesson ID is going to be a number initial lesson challenges are going to be uh a type of challenges let's not misspell challenges from database schema. infer select and let's open this and let's add our normalization fields which are completed which is a buum and we're also going to have the challenge options from each and every one of them which is going to be a type of challenge options in first select and array at the end and this entire type of is going to be array in itself like that and besides that we're going to have a user subscription which for now can just be any I'm going to add to do replace with uh subscription DB type let's export con quiz component and let's the structure the props so let me first assign the props and then we can destructure all the necessary props which are the initial percentage initial Hearts initial lesson ID initial lesson why does it not exist anymore initial lesson ID initial lesson challenges and user subscription right so those are the ones we need and let's return a div here quiz there we go let's go back inside of our learn my apologist lesson page and we can import quiz from dot quiz seems like we are having a type error here so we're going to go ahead and fix that quickly so in here we have lesson challenges and we have the completed Boolean so that should be okay it seems like we're also having challenge progress could that be uh what's causing the progress here let's see the expected types comes from property initial lesson challenges which is declared here on typ all right but what is the exact issue let's try and find out something here is incompatible and these types of errors are definitely not very helpful so let me try and do the following how about I wrapped these entire thing inside of parentheses like this there we go so once I WRA this in parentheses this then triggered a proper array like that so does it work now oh yeah yeah that makes sense because this this array means that just this additional part of iner Select is an array right so when I hover in this yeah just this part is an array that doesn't make sense so the entire thing should be wrapped inside of parentheses so we separate that and then that is an array and our type and uh we no longer have any type errors great right so now inside of this quiz component I want to go ahead uh and first thing I want to create is a header component so I'm going to remove this and I'm going to use fragments and let's go ahead and create a header component here let's pass in the hearts to be Hearts Let's pass in the percentage to be percentage and has active subscription for now is going to be double exclamation points user subscription question mark. is active so there should be no type errors here because we Define this as any but is active is going to be one of the types later you've probably noticed that I'm using hearts and percentage but in here we only have initial percentage and initial Hearts so let's immediately resolve that I'm going to add Hearts set hearts and I'm going to import used state from react because I can do that because I have used client at the top so ensure that you have used client at the top for this component and passing the initial Hearts as simple as that and do the same thing for percentage set percentage we are going to use the initial percentage there we go now we no longer have these prop errors here so now what we have to do is create the head header component so let's go ahead and create a header. DSX here and let's go ahead and create a type props Parts is going to be a number percentage a number as well has active subscription is going to be a bullan let's expert cost header let's return a div saying header and let's assign this props here let's immediately extract them so Hearts percentage and has active subscription let's go back inside of the quiz component and we can now import the header from slhe header so don't accidentally import it from the marketing page or somewhere else and now you should see a small text which simply says header here great and one thing that I forgot to create was the layout page and luckily it's very simple nothing complicated so inside of the lesson folder let's create our layout. vsx so we push our content a bit so our lesson layout is going to have children so let's just quickly create a type props here with our children which are react react node now let's assign the props and inside very simply we are going to wrap the entire thing in a div we're going to give this outer div a class name of flex Flex call and H full and the inner div which will render the children is going to have a class name of flex Flex Das call height full and width full like path great so that is our lesson layout now let's go back inside the header component here and I want to mark this uh actually we don't have to mark it as used client because it's used in a quiz component which in itself already created a boundary between the client and the server meaning that subsequent components inside of it which are not children but as a prop can be and will be client components by default so no need to add that here great instead we can immediately replace this from a div into a header let's give it a class name and let's do some responsive responsiveness here so on large we're going to have a padding top of 50 pixels otherwise we're going to have a padding top of 20 pixels PX is going to be 10 we're also going to have Flex Gap X7 items Center justify between so we are aligning our items which are going to exist in a moment Max with for this header is going to be 1,140 pixels MX AO to push those contents to the middle once the max width has reached and full width there we go now you can see how our header will not expand after a certain amount exactly the thing we did if you remember in our marketing page and we also did that in the learn page with our layout so we Ed this trick with Max with an MX AO a couple of times already and now let's add our X from Lucid react let's go ahead and style this so I'm going to go ahead and give it a on click for now to be an empty Arrow function let's set to do add on exit and let's give it a class name of text slate 500 hover opacity is going to be 75 and let's do Transition and cursor pointer there we go we now have our x button right here you can see the hover effect on it when we hover now we need to add a component from shat and Library so let me go ahead and uh I I will shut down my drizzle Studio I don't even know if you have it running if you can if you do just shut it down and let's use MPX chat cnii at latest add progress that's the component we need next so there we go we now have that component uh so let's go ahead and add it so progress here from components UI progress so not from radics from compon component UI progress like that and the value here is going to be the percentage there we go and then let's go ahead and create a div here with a class name of text uh rows 500 let's use that Flex items Center and font bold let's add an image component from next SL image and I'm going to go ahead and give it a source of/ heart.svg I'm going to give it a height of 28 and I'm also going to do a width of 28 and I'm going to give it an out of heart and a class name of margin right of two like that and then I'm going to check if the user has an active subscription in that case I'm going to render the infinity icon I recommend that you import it as Infinity icon not as Infinity because again you can do this but Infinity is a reserved keyword in JavaScript so I'd rather you use Infinity icon here so Infinity icon and let's give it a class name height six width six and stroke three inside of square brackets and otherwise simply render the hearts there we go we have a nice gamish like header here so let's try something let's go inside of our layout here my apologies inside of quiz and let's modify this a little bit so I'm going to write 50 pipe pipe initial Hearts there we go so that changes the hearts to 50 great and let's do the percentage now so 50 for the initial percentage oh there we go so this is now 50% one thing that I think is missing is that we actually uh use the proper color for this one right so let's go ahead and do that I'm going to go inside uh of my progress component which we've just added using the chassi and CLI tool so I'm going to go inside of the app folder my apologies components UI progress right here and we're going to go ahead and find the indicator here and instead of using BG primary let's use BG green 500 how how about we do that and there we go now this is looking much much better perfect so now that we have this uh I want to go ahead and I want to create an exit model so something that I have added a too inside of my app lesson header I believe I've written a to-do here so that's what we're going to do next we're going to create an on click which when we click on is going to open a popup to confirm to the user that they want to leave this page and before we wrap up let's just go inside the lesson quiz here and remove this 50 for the initial percentage so that was only needed so that we can test out whether it's working there we go perfect it is responsive it has a maximum width we are making some great great progress here great job so now I want to create the ex it model which appears when we click on the x button so let's go ahead and add some things I'm going to go ahead and add npx shat CN UI at latest at dialogue after the dialogue we are also going to do npm install two stand after that we're going to go ahead inside of my repository find the public folder and find mascot unor set go ahead and download this asset and then drag and drop it inside of your public folder like that perfect you can close the GitHub repository let's go ahead and create a new folder we are going to call it store inside of store let's create use exit model. TS let's go ahead and import create from to let's write type exit model state is open is a Boolean open is a void and close is a void and now let's go ahead and Export const use exit model let's add create from syst let's add give it a typ type of exit model State and let's extract set and let's return an immediate object with the default is open of false open of an empty Arrow function which will call the set and set is open to true and close which will set is open to false like that and then let's go ahead and create our exit model component so I'm going to go ahead inside of my components folder here and I'm going to create a new folder called models inside of it go ahead and create exit D model. DSX let's go ahead and Mark this as use client and let's export const exit model like this and now let's let's import everything we need from the dialogue so from at/ components UI dialogue so we are going to need the dialogue itself the dialogue content the dialogue description and the dialog footer header and title like that let's also prepare our button component from do/ UI button or slash components UI button and I'm also going to add use exit model from store use exit model which we've just created so we're going to use the two to control our model State let's prepare an image from next image let's repare use router from next navigation and let's prepare use effect and use state from react now let's go ahead and let's define the router here by use router let's define client and set is client or just set client use State false let's actually make this since this is a Boolean let's make it is client and set is client like that and let's the structure is open and and close from use exit model now we're going to do a little trick with use effect here where we are very simply going to set this client to true on Mount like this and then we're going to check if this component has not yet been turned into a client component we are going to return now so once the use effect fires it means that it's rendering on the client side so just because it has used client does not mean it's not rendered on the server side used client simply indicates a boundary uh to the client it doesn't mean that this is not rendered on the server so it's just not a server component but this can cause hydration errors because of the way we are controlling our models we are controlling it through tand tand I hope I'm pronouncing it right you don't have to to control your models using sush tand it is simply a method that I prefer because I can use this hook in any client component and call it from absolutely anywhere Some people prefer wrapping their components inside of models and using a specific children as a trigger I prefer it this way it's simply more usable for me but I do have to suffer with this potential hydration error which I have to fix using this method and then let's go ahead and return the actual look of our component so for that I'm going to add a dialogue here let's give it an open of is open which we can extract from the use exit model and on open change to Simply call our close method and now I want to go ahead and I want to go to our main layout in the app folder right here and just below the toaster go ahead and add an exit model so do that very simply from components exit model like this and now I want to go ahead and go inside of the use exit model from our store folder and change the E open to be true by default I'm going to add too change back to false the reason I want to change it to true so that you can actually see what you're developing so let's go ahead inside of the exit model and let's add the dialog content let's give the dialog content a class name of Max with MD there we go you can now see your dialogue here let's add a dialog header here and let's create a div with an image component let's give this image component a source of/ mascor sad.svg an Al is going to be muscut and let's go ahead and give it a height of 80 and let's do the same thing for the width and there we go now we have our mascot here inside of this div let's give this a class name of flex items Center full width justify Center and margin bottom of five outside of this div encapsulating our image let's add a dialog title which will simply say wait don't go and let's fix this by escaping this using an appos like this so instead of an apostrophe we're going to use the this sign like that and it's the same thing but we don't have an error or you can just write it like this if you don't like this and let's give this dialog title a class name of text Center F bold and text to excel and then let's go ahead and create a dialogue description here with a uh U let's copy this appost apostrophe so you're about to leave the lesson are you sure so we are asking the user to confirm their action let's give this a class name of text Center and text base and let's go outside of the dialog header and let's add a dialogue to their component let's give this a class name of margin bottom of four let's go ahead and create a div here with a button which will say keep learning and let's just not misspell this button so we have added an import from the button right here in our component's UI button so this will say keep learning and let's give this a variant of primary let's give it a class name full width and let's give it a size of large let's also give it an on click or to call the close method so if you want to you can collapse all of these so they are more readable let's go ahead inside of this div wrapping the button and let's give it a class name of flex Flex column Gap y4 and with full let's copy and paste this button and let's call the one below that and inside of this one let's give it a variant of danger outline instead of keep learning the text is going to say end session and on click is going to be an arrow function which will call be close but also router. push back to the learn page like that there we go that is our close model now go back to use exit model State remove the to-do comment if you wrote it and bring this back to use false and then let's go back inside of our header component specifically inside of our app folder lesson header and now let's go ahead and destructure the open from use exit model which you can import from store use exit model and we don't have to put use client here even though we are using a hook because again header component is rendered inside of quiz which by itself is a client component already so those that is also true for the header itself if you really want to you can manually add use client but that only makes sense if you plan on reusing header throughout different components but I strictly want to use this header inside of the quiz component so for me this is just fine and and let's very simply add an open here and remove this comment there we go let's try it out so now when I click on the x button there we go we have a model wait don't go you're about to leave the lesson are you sure if I click keep learning it just closes and everything is fine if I click end session there we go I am redirected back to the learn page great great job so the last thing we finished was the exit model which opens when we try to click on the close button in our header component so what I want to do now is go back inside of my quiz component so that is located right here in the lesson let me close these other things so inside of the lesson folder where we have the page layout and our header go back inside of the quiz component which is our client component here and let's continue developing uh content below the header so let's open up a div and give it a class name Flex one let's go ahead and create another div with full height Flex again item Center and justify Center inside of here we're going to go ahead and open one more div before we add our header so on large we're going to define the minimum height to be 3 50 pixels on large we're also going to define the width to be 600 pixels let's give it a full width otherwise PX of 6 on large PX is going to be zero because we already have defined the maximum width let's also Define Flex here X column and let's do Gap Y2 now finally inside of here let's render our H1 element and inside let's just go ahead and render uh my question or for example this will be which of these is uh an apple something like this let's go ahead and style this H1 element so it's going to have text large on LG it's going to have text 3 Excel text is going to be centered LG text is going to be start font is going to be bold and text neutral 700 and then let's go ahead and open up a new div here and now in here we are actually going to render our chall component so let's go ahead and do the following so first I want to change this title so I'm me just add a little to do here to do challenge component so I want to go ahead and change this from being hardcoded into being an actual title constant so let's Define con Title Here to uh check for the challenge type so first things uh first thing that we have to do is we some how have to get the current challenge so let's go ahead and do the following we're going to go ahead and create a state challenges and let's go ahead and write US state and let's passing the initial lesson challenges and now let's find the current challenge using the active index so we have to do the active index as well so let's write const active index and set active index so this will navigate what challenge the user is currently on so use State let's open up a arrow function here and let's write con un completed index and let's go ahead and use challenges from above do find index find the individual challenge which is not completed so challenge challenge do completed not challenge. completed so make sure you've added the exclamation point here and then in here let's return if we have an uncompleted index uh my apologies if uncompleted index is minus one in that case the active index is zero meaning simply load the first active challenge otherwise load the first uncompleted challenge so if user goes back to this lesson we're going to to load the first uncompleted challenge we're not going to make them have to reset the entire lesson again so we're going to remember uh where they left off and now what we can do is we can define a challenge to be challenges from our state here which we've defined from initial lesson challenges and let's simply use the active index and this is how we are going to control which challenge is currently active so later we can just move the active index by one or we can uh move it by or we can reduce it by one and then it's going to be the previous one so very simple navigation of challenges and then we can create this constant here so let's go ahead and write challenge. type is assist in that case the title is going to be select the correct meaning so let me collapse these otherwise if our type is uh select in that case we can use challenge. question instead so now let's render the title in here instead there we go so you can see how now we loaded the first challenge so let me just confirm that I'm going to go inside of my terminal here and I'm going to MP and run database studio so let me open that up and in here I should have only one Challenge and it should be a type of Select and here it is this is the question which one of these is the man and then I have challenge options El om and L robot so that's what we are going to load so let's go ahead and uh confirm confirm for yourself that you are able to load this first challenge so let's go ahead and recap how this is working to help you debug just in case it's not working for you so it lies in these initial lesson challenges so inside of my page. DSX right here we are using uh the get lesson method so confirm inside of your get lesson which you can get from database queries here that you're actually able to load the fine first lesson and their challenges and once you can do that that you can go ahead and pass that right here lesson. challenges and basically what we are doing right now is we are loading the first in the array of challenges so we do have these this logic right here for to find the first active index but since there are no completed challenges at the moment I know it's going to be zero so this is the equivalent of being challenges zero so it's loading the first one in the array and if I take a look at my first challenge in the array right here it's the only one that I have so that's why this is a type of Select and that's why we render the this question right here which one of these is the man right that's how that is working but if it is assist it's going to be a little bit different right so our question is going to be used in the form of a little question bubble which our mascot is going to ask the user and the uh and instead we're going to hard code the title to say select the correct meaning right so it's going to be a little bit different you're going to see once we create the assist alternative so make sure that while you're developing this part of the tutorial you have a challenge which has a type of Select if you change it to assist it's going to be a little bit different so inside of your CED script make sure that your challenges here have a type of Select when you are creating them great so now that we have this let's go ahead and let's create a little component called question bubble so this is only going to work for type assist but we are going to go ahead uh and showcase that so let's go ahead and write if challenge. type is assist in that case and only in that case render a question bubble component and passing the question to be challenge question now let's go ahead inside of the lesson folder and create question- bubble. PSX inside of the question bubble let's go ahead and import image from next slash image let's write type props here to accept a question which is a string and let's export const question bubble it's going to accept these props right here and let's D structure the question let's go ahead and return a div here and now we can go back inside of our quiz component and we can import the question bubble from do/ question bubble so this is what I want you to do while you are developing this just change this from challenge type assist to challenge type select so you can see the question bubble so right now I'm not seeing anything because it's just a div but if I write question bubble inside there we go you can see how I have a question bubble here so we are going to pretend that this is for type select I'm going to go ahead and immediately add a comment to do change back to type assist so we don't forget this later so I just want you to see what you are developing let's give this div a class name Flex items Center Gap X4 and margin bottom of six now inside of here we are going to add our image component which we have added an import for above and let's import SL mascot. SVG let's give it an ALT of mascot let's give it a height of 60 a width of 60 as well and a class name hidden LG block and then copy and paste this image and now we're going to create uh a mobile version so this is going to be width and height of 40 and it's going to be blocked by default but hidden on large devices and there we go you can see how now my little mascot here we already have the masc so it's used inside of our navbar folder and our sidebar folder so you can find it um files you can find it in the public folder uh if if any chance you don't have this you can go into my GitHub repository find the public folder and use mascot. SVG or any other character that you want there we go this is now my little mascot here now this little mascot is going to ask our user a question so let's create a div here and lender a question inside so now it's repeating the same question so it doesn't make a lot of sense but it is going to make sense later when you see the full demonstration of the assist type of question of challenge let's give this div a class name of relative py2 PX4 border to rounded extra large text small and large text base and now inside of this div create a simple self closing div and in here we're going to create an absolute minus left minus 3 top 1 and a half width zero height 0 border D x-8 border dx- trans transparent border top 8 transform and minus translate minus y 1 and a half let me zoom out a bit so you can see and one more rotate -90 so we created a little Shevon here so it looks like it's coming out of our little mascot so it looks like a little bubble similar to the one that we have on our landing page here you can see how we have this little Chevron pointing at the star so now we have the same thing pointing at our mascot that's it that's our challenge uh our question bubble component here so you can now go back to the quiz and we can change this back to assist by now it should have disappeared for you and then you can remove the to-do comment now let's head back to our quiz component here where we changed this back to type assist and just below this we are going to render our challenge component which we don't have yet so let's go ahead and pass this the options of options and in order to find the options we're going to have to derive them uh from the current challenge so we do have the current challenge right here you can call this current challenge if that's easier for you but I just like to refer to challenge throughout the code for me that's simpler if you want to make it clearer for yourself you can change the constant of this uh from challenge to current challenge but just make sure you change uh all the necessary types as well but I'm going to keep this as challenge So Below this let's go ahead and get the options to be the challenge question mark do challenge options or an empty array so let's go ahead and pass in the options so that is now okay and now let's go ahead and pass the on select for now that's going to be uh an empty Arrow function status for now uh is going to be hard code Ed to be correct so let's make this correct and selected option for now is going to be null disabled is going to be false and type is going to be challenge. type so if you save this challenge is going to throw an error so let's go inside of the lesson folder and create challenge do PSX and now let's create type props here let's pass in the options which are going to be a type of challenge options from database schema in first select and an array on select is going to take in the number and return a void status is going to be one of the following correct or wrong or none selected option is going to be an optional number disabled is going to be an optional Boolean and type is going to be type of challenges from database schema in first select and let's simply gather the type itself let's export want Challenge and let's destructure the props here and let's return a div saying challenge let's go back to our quiz and import the challenge component from do/ challenge let's resolve the selected option to instead say undefined is that okay so that is okay and let's change the status to instead be none by default so we are later going to test the correct and wrong variants let me go ahead and destructure the options on select status selected option disabled and type and then what we are going to do here is we're going to create a div and let's write class name CN we can import CN from lib utils so class name is going to be dynamic DC depending on the challenge type so let's write the default classes first this is going to be grid and GAP two and then let's add a comma if type is assist in that case grid equals one so we want to take the entire space of our grid otherwise if type is equal to select we are going to order them next to each other so grid calls 2 on mobile file but on large we're going to use grid Das call- repeat but open square brackets so we're going to write a custom uh pattern repeat auto- fit comma min max 0 comma 1fr make sure there are no spaces when defining this LG grid calls so when you hover over this you should see the exact underlying CSS so let me zoom out so you can see there are no spaces anywhere inside of these square brackets so no space after the comma you can see how this no longer works no space after the comma here no space after parenthesis nowhere so it needs to be all connected via some kind some type of string great and now inside of here we are very simply going to iterate over our options so option. map let's get the individual option and its index and in here for now we can create a div and we can render Json stringify option there we go so we have this key error that's fine but you should be seeing ID challenge ID 1 challenge ID 2 there we go text L ombre and we have head here so basically we do have these uh challenges challenge cards these challenge options rendered so now instead of uh rendering divs I want to render card elements so let's passing the key to be option. id id is going to be option. ID text is going to be option. text image source is going to be option. image source shortcut is going to be open btic index + one so yes you're going to be able to use your keyboard to select a specific option and let's write selected to be if selected option is equal to option. ID whoops option. ID on click for now is actually it's going to be we're not going to change this letter so this is going to be on select and we're going to pass in option. ID and status is going to be status audio source is going to be option. audio source and disabled is going to be disabled and type is going to be type and now we have to create the card component which is the last inside of this list here so inside of the lesson folder where we have the challenge already so let me close others in here create a new card. TSX and this is going to be the last nested component that we need to display the challenge so export const card here return a div card but before we add it let's create typee props here and let's go ahead and accept everything we need so with an ID which is a type of number when or if you're using uu ID it's going to be a type of string just a reminder image source which is a string or null audio source which is the same thing so both can be optional text which is a string shortcut which is a string selected an optional Boolean on click is going to be a void disabled is going to be a Boolean status is going to be an optional correct or wrong or none type is going to be type of challenges from database schema in first select and specifically select type there we go now let's assign the props to the card props and let's start destructuring all of those ID image source audio Source text shortcut selected on click status disabled and type it seems like status doesn't exist that's because I misspelled it so status there we go and now head back to challenge and import card from SLC card let me just separate these Imports and there we go seems like we have no errors at all and you can see how now we have our cards here rendered because in my drizzle Studio I should have three challenge options there we go these are the three challenge options and they all have this single challenge which I'm currently on so now let's go ahead and style this Challenge and make it look like something so I already know we're going to have to use our CN import so let's add that CN from lib utils here and for this main div I'm going to give it an on click for now to be an empty Arrow function and then let's write a class name here let's add some default classes so height is going to be full border is going to be two we're going to have rounded extra large border bottom is going to be four pixels and hover BG black sl5 padding is going to be four on large padding is going to be six cursor is going to be pointer and when the element is active border bottom is going to be reduced to two so we are going to replicate our button type of element right there we go now let's go ahead and give this Dynamic uh types so if this is selected we're going to go ahead and change this to use border Sky 300 BG sky is going to be 100 then we're going to have hover BG Sky 100 so it's stays that way besides this we're going to have selected and Status being uh correct so let's go ahead and prepare that and then we're going to have to handle the other states so we have status is wrong and we have is uh status oh we only have correct and wrong and the last one instead is not going to be selected it's going to be if this is disabled so let's now prepare this so for this one uh I think I can just collapse it like this so it's easier for me to write in one line you don't have to do this so if it's correct border is going to be green 300 then I'm going to have BG green 100 and hover BG green 100 as well now if it is wrong we're going to have border rows 300 BG row rows 100 and hover BG rows 100 let me just see if this is correct um BG green 100 okay I think this is correct so let's try this out so if I go back inside of my quiz component if I change this from status none to correct uh oh it also has to be selected so let's go ahead and simulate that we have to simulate that in the challenge component so selected here where is it selected let's hardcode this to be true and add pipe pipe here so I'm going to add a comment to do remove hardcoded true there we go so this is how our uh correct card instance looks like so I can zoom out a bit so to see how it see how it looks like this okay looks fine now let's go back to our quiz component and let's change this from status correct to status wrong there we go and none looks like this so this is how it looks like when uh the status is none and when we simulated selected to be true so if I remove true then this is how it looks like and uh the if I go back and change this to wrong it has no effect because we are not going to show all elements as wrong we are only going to show that the element which is selected and if we notice that it is wrong so now let's go back inside of our card component here and let's wrap it up by creating a disabled the variant here so that's going to have pointer events none and hover is going to be BG white and last one if type is equal to assist in that case go ahead and write LG padding of three and full width that's it now let's go ahead inside of here and let's check if we have image source in that case go ahead and render a div here inside r an image component from next SL image so make sure you've added this import here and give this image component a source of image source a fi property and an Al of text and give this div a class name of relative aspect Square margin bottom of four Max height 80 pixels LG Max height 150 pixels and full width so you can see how now I have these uh El ombre l and a robot images here but I don't have them inside of my source folder my public folder so inside of your database these are the image sources which I have added SL men. SVG sloman . SVG and/ robot. SVG so I'm going to head into my public folder and I'm going to add this image sources so I'm inside of my repository inside of the public folder and let's find man.svg there we go so let me just quickly uh download this one so these are from K game assets they are cc0 licensed and I highly encourage you to donate if you want to support his work link is in the description woman. SVG so that's another one which we need let's download that and we also need robot. SVG in the public folder there we go let's go ahead and download this one perfect now I'm going to go inside of my public folder here and I I am simply going to drag and drop all three of those so man.svg woman. SVG and robot. SVG ensure that all of these are inside of your public folder let me go ahead and expand this we can close these things now there we go so inside of my public folder I have woman I have a robot and I have man so now if I refresh there we go I now have images for my characters now let's revisit my card component so inside of the lesson folder card component is the one we are currently working on so we stopped at defining this image source here we defined that that's fine uh if yours are still not loading uh double check that you did not accidentally name them something else it needs to match this perfectly right of course later in production you can change this to not be in your public folder but you would probably use some kind of CDN right uh but ensure that inside of your seed script you are passing the image source this is basically your url right so inside of my challenge options here make sure that you're using the correct image source SL man.svg woman. SVG and robot. SVG and then everything is going to work fine if you have those matching in your public folder and you can always double check in your drizzle Studio to confirm now that we rendered the image let's go ahead and create a div here with a class name of CN let's pass in the default Flex items Center and justify between and then let's dynamically add if type is assist then let's add Flex row reverse inside of here I'm going to add if type is assist again and we are very simply going to fill the space by adding an empty div so we are using a trick for a justify between here so our elements look properly centered when type of a challenge is assist otherwise we can simply go ahead and render a paragraph here with our text there we go l L and robot let's give this paragraph now a class name of CN text neutral 600 text small large text base if it is selected we're going to change this to use text Sky 500 if we are selected and if status is correct in that case let's prepare this and let's copy it and do the same case for wrong now I'm going to collapse this you don't have to do it but I'm doing it so I can write it in one line we're going to write text green 500 for wrong I'm going to write text rows 500 great and now that we have this let's go ahead uh and let me just see miss something here so we have Flex item Center justify between uh so I guess this is fine let's go ahead below this paragraph and let's add a div here and let's render the shortcut and now let's add a class name here CN again so in here on large width is going to be 30 pixels and height is also going to be 30 pixels on large otherwise on mobile it's going to be 20 pixels so let's define that border is going to be two Flex items Center justify Center so I want to position these numbers in between rounded is going to be large text is going to be neutral 400 on large text is going to be 50 pixels on mobile text is going to be extra small and font is going to take the semi bold effect there we go so now this indicates that I should be able to press number one number two or number three and that should be the equivalent of clicking on individual items just like that and now we have to go ahead and do the same thing for the selected so let me go ahead and copy uh these States from the paragraph above so add a comma here there we go so if it is selected instead of text Sky let me add an empty line here empty line here and empty line here so I just want you to do the same thing so very simply just create a base uh so this is how it's going to look for you like this so first let's go ahead and change the style of this short cut if the current item is selected so we're going to change the border to Skype 300 and next to Sky 500 if we are correct we're going to change the border to Green 500 and text green 500 and if we are wrong we're going to change the border to Rose 500 and text Rose 500 there we go so that's our card element let's play around with this again so go inside of challenge here simulate the selected by adding true pip pipe there we go this is how it looks when selected looks pretty good and now let's go inside of quiz and change uh the challenge status from none to wrong there we go this is how it looks like when it's wrong and let's do one last success or correct my apologies there we go perfect so all of these are working let me switch it back to none if one of yours is accidentally not working confirm that inside of your challenge your status completely matches so it should match what you're passing to the card right here perfect let me go back inside of the challenge and remove the simulation of selected now there we go so we now have uh we we are now successfully rendering our cards here what we have to do now is we have to create the logic so that when we click on an individual element of this card first thing that I want to happen is I want the audio to be played pronouncing what is written in the text here so in the next chapter we're going to learn how to use 11 Labs AI to generate voices in different languages and then we're going to have to create a logic uh which will show which will actually select the our option we're going to have to create these shortcuts and then we're going to have to create a component from where we're going to be able to confirm our choice and then we would go to the next question or we are going to lose a heart great great job so let's go ahead and make these cards play a sound so this is what I want to do first I want to show you how you can use 11 Labs AI to generate voices so first things first head to Google and search for 11 la apps once you are inside and created an account you're going to see a dashboard similar to this and now in here you can go ahead and play around with the uh person saying the voice you can go ahead and play around uh with the voice settings in here and then inside go ahead and create a couple of phrases for example I'm going to go ahead and create uh L Ombre I'm going to go ahead and click gen generate right here and there we go I have El ombre right here so I'm going to go ahead and play this you will not hear this because I don't have audio output configured right now you might hear it in the background but it's going to sound similar to something uh like you heard in the demo in the introduction video if you don't like this voice you can go ahead and change it to something else I think I might have used Charlie for example so just go Ahad head uh and try it out until you find something that you like you can also configure the voice settings for something else so you don't have to configure the language itself right it should detect it automatically you can see that I'm using 11 multilingual right here so you can see the languages it's supporting and Spanish is one of uh one of them I believe so yeah 29 languages great uh uh and then just go ahead and click download and this is the way I do it so I go ahead and throw this inside of my public folder and then I give it a proper name right so I don't want this long name instead I do the prefix of the language and then I do underscore men like this so this is the sound that I'm going to use for translation of man in Spanish so let me go ahead and show my terminal here so I have my drizzle Studio running here so in here there we go you can see my audio source is slore manwoman and robot so go ahead and use the 11 Labs AI to create one for El ombre one for La so let me go ahead and click generate and yeah you should have 10,000 of completely free of these so now you would download uh lamare right and you would do the same thing and then you would do L robot and click generate as well there we go so three of these and you can download the latest two or if for any reason uh 11 Labs is not working for you you can go inside of my public folder and in here I have some other stuff like apple boy girl Man Robot woman and zombie uh we are also going to work with some other sounds like finish and stuff like that so if you want to you can also go into my public folder if you don't want to generate uh your uh yourself what I'm going to do is I'm going to go into my public folder and drag and drop them inside of here there we go so I have prepared Three MP3 sounds for me so I have a sound in Spanish for men robot and a woman and these sounds match exactly what I have as audio source files inside of my database esor Man esor Robot and _ woman so now I can go ahead and also confirm that in your scripts seed right here there we go so make sure that your uh audio sources are matching make sure they are named correctly in the public folder and I don't use any subfolders so they are directly accessible at the root great and now that we have that let's go ahead and let's install a package so I'm going to shut down my drizzle studio now and I'm going to install react use so this is going to be uh a collection of packages for us here so let's go ahead and go back inside of our card component inside of our lesson folder so this is the last place where we worked where we added the little shortcut in all the different selected and Status variants so now we're going to go ahead and create this onclick method right so let me show you this while demonstrating uh that I'm going to create uh my handle click here so handle click is going to be use callback from react so make sure you've added use callback the reason we need use callback is because this is going to serve as a dependency in one of the event listeners from our newly installed react use hooks so otherwise I wouldn't add use callback but we are going to use it as a dependency later and in here if this is disabled I'm going to break this method no need to do that and then let's add disabled in our Dev dependencies um sorry the the dependency array and then let's do on click here so very simple and let's add the on click here and here's what I want to do now I also want to add uh my audio file so we can do that very easily thanks to our new react use which we've just installed so in here simply go ahead and import use audio and also use key so we are going to use both of those let's prepare the use audio one just above the handle click go ahead and the structure an array from use audio and inside of here simply add source to be the audio Source property right there we go audio Source right here uh and let's go ahead and see how we can make this Dynamic so yes audio Source can also not exist so can I just do a pipe pipe and empty string there we go looks like I can do that easily and then let's go ahead and the structure audio and then let me show you uh how this looks like can I uh I'm not really satisfied with this documentation but basically we don't need the second argument so I'm going to use an underscore underscore but we do need a third argument so that is controls like this so if you just wrote this then that's not going to work because we don't need what's in the second one right that is the HTML media state but we need controls like play pause seek volume mute and unmute and the first one is in react element which we need to add somewhere in our app l L anywhere so if you want to you can wrap the entire thing in a fragment and add it at the beginning but I'm going to for example just add it here so it's just going to be an audio file an audio element that's it it's not going to uh show it's not going to change the way your card looks in any way so just add an audio element above your image source like I did and then let's go ahead and do the following inside of here I'm going to do controls do playay so I'm going to play this audio source so let me go ahead and add the controls and let me wrap this up by adding the use key let's use the shortcut so our shortcut if you remember from The Challenge is very simple the number uh the current index plus one right so it starts with zero so this one is one this one is two this one is three so I'm mapping that shortcut to an event listener use key from react use so when we click on number one on our keyboard I want to call handle click like that and in the last one I'm going to go ahead in the next one I'm going to give it an end an empty options and then in the last one I'm going to use this handle click here as the dependency array and now if you try it out if you have this setup correctly your audio source files should play so I'm going to try it out now and I just forgot one thing uh so I'm using handle click but I never passed it to the UN click here so let's add handle click here there we go and let's click here there we go I I don't know if you're hearing that maybe in the background of my laptop right but I don't have audio output let me try pressing on the number one one on the keyboard there we go number two number three perfect so for me all of these are working as expected great so now that this is working for me uh let me go ahead and actually use um a visual indicator that I have clicked on something so right now we play the controls and we call the uh onclick prop which comes from The Challenge which calls the on select right here but if we go into our quiz on select is simply an empty Arrow function so let's go ahead and create the select functionality head back inside of your quiz component here and let's go ahead and just below the active index let's add selected option and set selected option that's going to use State it's going to be a type of number like this and let's go ahead now uh so yeah it could be a number but why is it not giving me an error because yeah it's also undefined so should I Define undefined I mean if you can you can if you want to you can add that looks like this works just as well yeah because by default it's going to be undefined so let's go ahead and use the selected option and set selected option now so we can do do that very simply uh inside of our onclick function sorry const on select method so on select is going to accept an ID which is a number if you're using uu IDs is going to be a string so if we are uh if the status is anything other than none we are going to break the method uh and I believe we also need to create a state for our status here so status set status use state by default is going to be none and let's go ahead and give it a proper types here so I'm going to go in the challenge so it can be a type of correct wrong or none so go back into the quiz here and paste those States here there we go so this is how it looks like in one line it should be either correct wrong or none all right and by default it is none so if it is anything other than none I'm not going to allow the user to select something again so status none just means that user has not submitted their choice they can still change it but they have not submitted it right if the status is wrong or incorrect the user needs to press either on retry or next so they can't change their selection if their status overall is correct right you're going to see uh how this works later and then let's simply do set selected option and pass in the ID and now we can go ahead and use on select here and pass it here and let's change the status from being hardcoded to use the constant which we have just defined and let's pass in the selected option here there we go let's try it out so if I click l ombre there we go a sound has played and now a lombra is selected lamare a robot let's try with out using my mouse there we go all of these are working perfect so now that we can select both visually and uh audibly our choice let's create a footer which we are going to use as confirmation of our choice so head back inside of the quiz component where we just added the select option uh on select and all of those things here and find the end of of the last div but still inside of our fragment here and create a footer component so we don't have this yet so don't import it from anywhere let's give it a disabled Prof if we don't have a selected option let's give it a status of status which we have and on check for now let's make it an empty Arrow function let's go inside of the lesson folder and create footer. DSX and now in side of here let's go ahead and add some imports so I'm going to import use key and use media from react use I'm going to import check circle from lucd react and X circle from Lucid react as well let's prepare our CN library and let's prepare our button component from components UI button let's define the prop now so the props for the footer are going to be the oncheck method which very simply is a void status can be correct or wrong or none or an additional one for the putter completed so completed is if we finished the entire lesson disabled is going to be an optional Boolean and lesson ID is going to be an optional Boolean as well let's export con footer here let's define the props and let's return a footer element let me go ahead and extract on check status disabled and lesson ID now head back to your quiz component in here and import the footer component from slf footer the same way we did uh with the header right here so so we are not going to use the lesson ID at the moment uh instead this is what we are going to do so we're going to go ahead and style this so let's go ahead and give this uh a class name of CN the default classes are going to be LG height of 140 pixels then a height of 100 pixels on uh mobile devices and Border top is going to be two border my apologies there we go then if status is correct border is going to be transparent and BG is going to use green 100 and if status is wrong border is going to be transparent but it's going to use rose 100 as the background color now inside of here open up a div and let's give it a Max width of40 pixels full height MX outo Flex items Center justify between PX of 6 LG PX of 10 so we are just going to push it a bit so it suits the same uh spacing as our uh header right here so we are using that Max with trick so it doesn't expand more than it needs to and now let's go ahead and create a button component here so we already have this imported and if status is none we are simply going to render the words check let's go ahead and give it some props so disabled is going to be disabled class name is going to be ml- aouto so it's always in our right corner onclick is going to be on check size is going to be dependent on our query and we have repaired the use media for react use so the reason I'm doing this like that is because we could technically do it through class names here but I want to use the uhu I want to use my prop size and I can't do that uh you know by using LG or MD so what I can do is make use of our react use since we already have it here so const is mobile is going to be used media and I'm going to go ahead and write in parenthesis this is in strings so in parenthesis Max width of 1,24 pixels so if it goes above that uh is mobile is mobile is going to be triggered uh so let me just go ahead and see uh did I break something here uh what is our error here unexpected token footer oh so I just have to pass in uh is Mobile in that case it's going to be small otherwise LG I think that should there we go so that now works perfect and variant if status is wrong it's going to be danger otherwise it's going to be secondary there we go so now let me just copy and paste this a couple of times so only one status can be at one time so we don't have to do the nested tary instead we can just do this if it is correct the button is going to say next if the status is wrong the status is uh the button is going to say retry if the status is completed it's going to say continue and let's remove the last one it was a duplicate uh perfect so we have that and now what I want to do is I want to add my use key here so let's go ahead and do this let's give it a use key when we press on enter I want that to work the same way as if user pressed on a button so so I want this to feel like a video game right that's why we have shortcuts that's why we have use key and let's add on check in the dependency rate there we go uh but we are not done yet so now what we have to do is we have to play a bit um with our statuses here and showcase different stuff so let's go ahead and pretend that we got a correct status so above the button here go ahead and write if status is correct and if we have a uh if status is correct we're going to go ahead and render a div here with a class name of text green 500 font bold text base LG text to excel Flex and items Center and we are going to render a check circle from Lucid react with a class name of height six withth six on LG height 10 and with then on LG and Mr of four and then I'm going to render nicely done so let's go ahead and pretend that we got the status correct let's check that out let's go back inside of the quiz component here and let's go ahead and check the default status from none to correct and there we go take a look at our footer so now it says nicely done and we have this nice badge it has a nice little background color and the button is disabled yes but uh It also says next so let's go ahead and bring this uh to wrong now so there we go you can see how now it has the red color it's disabled but it's missing the message for the user so let's go ahead and go inside of the footer here uh and let's copy this case for correct and let's do it for wrong now instead of check circle is going to be X Circle so we already have these imported from Lucid react instead of text green 500 is going to be text rows 500 and instead of nicely done whoops I changed the incorrect one make sure you're changing the one which says wrong so text rows 500 x Circle and instead of nicely done it's going to be try again there we go you can see how this looks like when the user selects a wrong choice try again perfect uh and now we are missing one more case so let's copy the last one here and this is going to be if status is completed so let's go back to our quiz let's change our status use State Back To None again and let's go ahead and give this a status of completed pipe pipe status so I remember to remove this later right so right now I'm going to hard code the status to be completed uh all right let's write out so if status is completed in that case I'm going to render a button component which is going to say practice again let's go ahead and give this a variant of default and then I'm going to go ahead and give it a size is mobile is going to be small otherwise large and on click what I'm going to do is very simply use window. location. HRA and I'm going to change it to vex SL lesson and I'm going to use the lesson ID prop as simple as that there we go so this is how it's going to look like once we reach the Finish screen so it doesn't make a lot of sense because only the footer right now is in this state uh of being finished uh but later we are going to change this uh screen as well so if you're wondering how will this footer ever receive the status complet completed if we defined here that it can only be this three don't worry it's because we are going to reuse the footer in a different way so for now just bring this back to be the status and there we go now this is the default State we have the check button but it is disabled because we have not selected anything and there we go the moment I select something this is enabled if I refresh this again it is disabled so only when I select something I can click on check so what we have to do now is we have to develop the actual uh on check function for now though I just want you to confirm that you can hear the sounds from your cards that you can visually see them being selected that your shortcuts are working and that your responsiveness is looking fine and that you also got all the results that I did when I changed the status on the footer for wrong correct and completed great great job job so now let's add the ability to actually Mark a challenge as complete so once we add our selection and click on check I want the app to give us feedback whether this choice is correct or incorrect so let's go back inside of our quiz component in the lesson folder and in here I want to prepare a method called on next so let me go ahead here let's actually do this below these two constants so const on next is very simply going to call the current set active index is going to get the current and it's going to increment the current by one so just a very simple reusable method so we don't have to write this every time and now we're going to go ahead and Below on select we're going to create our method const on continue so on continue will trigger every time inside of this on check on the footer so let's already replace this empty Arrow function with on continue so on continue will be able to be fired even if the answer is wrong or if the answer is correct alongside status being none and there's a reason for that so first things first we are never going to be able to continue if there is no selected option at all so let's break that function immediately then if status is wrong and the user presses a button in the footer we are very simply going to allow them to retry because the the button in the footer will simply say retry so we set the status Back To None and set selected option to undefined and we break the method the Breaking part is important so make sure you do that and then let's copy and paste this and do the same thing if the status is is correct so if the status is correct we are very simply going to call on next meaning load the next question or challenge set the status Back To None and set selected option back to undefined great now we're going to handle the third case and that is if we don't have neither wrong or correct status so let's find the correct option so options do find option option do correct so now we know which option is the correct inside of our list uh pass from the props here because we added the options from this challenge here and now let's go ahead and do the following if we have a correct option and if correct option. ID is equal to selected option which we just chose let's go ahead and console log correct option and then let's go ahead and handle the else statement where we are going to log an error incorrect option let's go ahead and try this out so I'm going to go ahead and open my uh my inspect element here and I will select El ombre let me just hide this and click check there we go this is the correct option if I select lir that is an incorrect option if I select L robot that is an incorrect option again perfect so our code is working uh let me go ahead and demonstrate why this is working in the drizzle studio so npm run database studio so in here let me just show you we have the challenge options and only one of these challenge options is labeled as correct which is alone om so that's why when I selected L ombre it compared the ID of one with our correct option which we searched for in the array right here and we found all right that is the matching ID great and you can also do this if there is no correct option we can just break the method right there is no need for us to check if there is a correct option it there always needs to be at least one correct option otherwise our code can't even work so technically telling user that their option is incorrect is not correct because you know we are the ones who forgot to add the correct option so what I want to do now is I want to create uh I want to create a method that will be able to actually Mark a challenge as completed or not so let's go ahead and close this let's go inside of our actions here and let's create a new file challenge uncore progress. TS let's mark this as use server so this is quite important make sure you do that and then in here let's go ahead and write export const upsert challenge progress which is going to be an asynchronous method which will accept the challenge ID which is a number now in here we're going to go ahead and destructure the user ID from await out from clerk nextjs if there is no user ID we are going to uh throw new error and let's just properly write the if Clause we're going to throw a new error here writing unauthorized otherwise let's go ahead and let's get the current user progress using a wait get user progress from database queries so I purposely named this current user progress and not user progress because we're have we're going to have to import the user progress later from the schema so I don't want to create any conflicts with the consant here and uh that's going to be it for now I'm just going to go ahead and add a to-do here handle subscription query later so we don't have that yet we're going to do that later if there is no current user progress we're going to go ahead and write throw new error user progress not found so we need the user progress user progress is what holds the current active course ID the hearts the points and the active course if that doesn't exist during this upsurt challenge progress there is nothing we can upsert now let's go ahead and let's find our challenge which we are trying to upsert so challenge await database from database SL drizzle so let me just separate this St there we go so database. query. challenges find first where equals from drizzle orm make sure you add the equals compar comparison challenges from database schema so you need to have the challenges here challenges. ID needs to match the challenge ID which we have passed from our uh prop here if you're working with uui this this would be a string you can see how that gives me an error because I'm using serial inside of my schema so I need to use a type of number if there is no challenge to absurd we're going to throw new error challenge not found now let's go ahead and let's extract the lesson ID so con lesson ID will be challenge lesson ID so the what is the lesson of the current challenge which we're trying to find and then let's go ahead and write const existing challenge progress so await database query challenge progress find first inside of here we are going to write where and so make sure you import and from drizzle orm the same way you added the equals so let's use the end so end is used to combine multiple filters so we're going to have two equals inside of here let's go ahead and write for the first one if challenge progress which we need from database schema so make sure you added the challenge progress alongside challenges in the database schema import so challenge progress. user ID needs to match the currently logged in user ID and challenge progress challenge ID needs to match the challenge ID from our props there we go and now we using the existing challenge progress we can know whether the current um modification of or viewership of this challenge is a practice or not so if this is user's first time creating an upsert and creating this model challenge progress it means that they are in an active lesson but if this challenge progress already exists that means that user is practicing in again on this challenge so we can derive that so let's write cons is practice and we can simply turn the existing challenge progress into a Boolean by using double uh exclamation points like this so now we can simply derive uh uh the status of this practice uh the status of this challenge so is this the practice or is this a live first time seeing user challenge simply by deriving it from whether the existing challenge progress exists or not so this is what we are going to do now if current user progress do hearts is equal to zero and if we are not practicing so on practice we won't prevent the user from practicing if they don't have hearts left the hearts are only going to be affecting the current active lesson so if user has no Hearts left and this is not a practice we're going to return an error which will simply say hearts and I'm going to add a to-do here not if user has a subscription so we currently don't have that implemented but I'm am already adding a to-do to remember that and now let's go ahead and do the following let's write if this is just a practice we are going to await database update challenge progress do set and in here I'm going to write completed true so that's the only thing I want to update just in case completed was somehow false right so challenge progress. ID comparing with existing challenge progress. ID so when you do a database update you need to chain it with a whereare and inside ofwar you would pass your query here so let me go ahead just and separate this like that there we go so that handles the update of the challenge progress for the practice and now let's also update the uh let's also update the user progress so this is the a server action upsert challenge progress that would get fired if the user answered a correct uh answer so in practice mode we also have to increment the users's hearts and also for every correct Choice user points will get incremented by 10 so we have to do a wa database update and we have to update the user progress here so let's go ahead and set and let me see if I have user progress imported I don't make sure you have user progress from database schema here so we need user progress. set hearts are going to be math.min so we're going to use the lower of the two values so current user progress. Hearts Plus One but if user already has five Hearts I don't want them to have six hearts because the maximum value is going to be five for hearts right so what math.min does is it chooses the lower of the two values so if current user. progress Hearts results to six it's not going to give user six hearts instead it's just going to fall back to the default five Hearts right because in our schema here we have in the user progress right here uh default of five Hearts it would be a good idea to create a constant to replace this number five here and here to stop being a magic number and instead make it a constant like default heart or maximum Hearts something like that to make it clearer and let's go ahead and increment the points so there are no limits for the points at least not in my iteration of this app in yours in production it might be a smart idea to somehow limit the points so we updated the hearts and the points and what we have to do now is write. where again equals user progress user ID so make sure that this user progress here is the one you're importing from database schema make sure you didn't name these constant user progress so now we're going to use this uh user prog actually we don't have to use this user progress we can simply match it with the user ID which we have there we go uh and before we go outside of this uh is practice if Clause let's do the following let's revalidate the path slash learn so let me go ahead and import this here next cache revalidate path we are going to revalidate the path learn lesson quests leaderboard and also back/ lesson lesson ID so we are going to revalidate all paths which use any of this values like hearts points the current active lesson EAS practice all routes like that and these are the ones that we need great and then we can go ahead and go outside of the if Clause oh yes let's not forget to return this right so I'm not going to revalidate any I'm not going to redirect anywhere I'm simply going to break the method this is very important make sure you are breaking the method inside of the is practice if clause and now outside we don't have to write any additional else we can just go ahead and write away database insert challenge progress so this time we are using insert instead of database update so in here we use database update for the challenge progress but in here we are inserting it for the first time so let's write challenge ID user ID and completed to be true that's it so where do we have the challenge ID from we have that from the prop we have the user ID from the uh out from Clerk and we set the completed to true because we know that we are calling this when user answers a correct question and let's do await database database. user progress U my apologies database. update user progress do set and in here let's call points print user progress. points + 10 and we also have to write where user progress user ID matches the user ID from our out let's go ahead and just copy this revalidation here and that is it there we go so that's how we are going to control our uh success correct answer choice so if you want to you can extend this upsert challenge prog press and besides accepting the challenge ID you can also accept the option ID and then you can do another validation here on whether the choice was correct so let me go back inside of my quiz lesson here so you can see that we do the correct option check here on the front end so that's great because it we immediately know whether it's correct or not right the user doesn't have to wait um but it might be a good idea to also pass that to the back end uh we'll see for now I'll just do it like like I am uh doing it so let me do the following go back into the quiz component and import use transition from react now in here let's go ahead uh let me go to to the top here and let me add start transition and uh is pending or my apologies is pending and pending and start transition it doesn't matter the naming but the order matters and let's add use transition here so let me just see if this is the correct order yes this is the transition start function and the pending should be a Boolean perfect and now inside of the on continue method which we've just started developing here so far the only thing we've done is added a console log for the correct option so what we're going to do now is the following we are going to add start transition here and in here we're going to go ahead and call upsert challenge progress so make sure you've added an input for the upsert challenge progress from action challenge progress and in here we are expected to pass a challenge. ID and let's use do then here let's get the response and if response question mark. error is equal to Hearts I'm going to add a console error here missing hearts and I'm going to break the method so what I'm doing here here is I'm checking for this error right here where is it there it is if challeng if current user progress that Hearts is zero and if this is not a practice we will return an error Hearts so in here I check for that error and for now all I do is show a console error later we're going to create a model which is going to show that otherwise uh if this method did not break we continue forward and what we do is we set status to correct and then let's call set percentage and let's call the previous percentage previous plus 100 divided by challenges do length like that so we increment the percentage by one of our what by one complet one new completed challenge uh and if initial percentage is equal to 100 so what does this mean if we loaded a challenge a lesson and if the initial percentage of the entire lesson is 100% it means that all challenges have already been completed which means this is a practice so what we are going to do here is we're going to update the hearts on the front end as well because we know we've updated them in the challenge progress here so if it is a practice in here I already know that I have added some new new Hearts here so now I want to do the same thing here and I can use the exact same method with math Min so I'm going to do math.min here and I'm going to add previous + one and limit it to five there we go so in practice we get new hearts on the front end and let's do a catch method here and call toast from sonor so let me just add it here toast. soner where was I and I'm going to pass in an error here something went wrong please try again and here's what I want you to do before you try this out so for now actually no this is fine you don't have to do anything let's go ahead and try this out do I have the app running I do so in my drizzle Studio I currently have no challenge progress so nothing exists yet so if I select Al and click check uh there we go so something just happened really quickly let's go ahead and refresh our challenge progress there we go we have a challenge which is completed and we have the matching relation which says which one of these is the man so let me go ahead and open this there we go which one of these is the man and we selected a correct uh a correct option for it perfect so now what we have to do is we have to go and there we go we even have a sign that this is now completed perfect but now we have to uh we update this so it shows a finish screen as well right but first let's go ahead and let's go back let's see if I can go back I cannot go back currently because we are using a different URL to show the practice uh lesson so let's see what is the best way to go forward with this I think the best way to go forward is not to handle the practice but to still work inside of this current active lesson in order to do that we're going to have to add some more challenges so let's go inside of our scripts here inside of seed and let's just add more challenges so in here I have these challenge options right and I have these challenges so let me add another challenge here so lesson ID can stay the same the type can be select and the let's go ahead and let's actually change the type here to be assist because both should work let's change the order of these challenges so this is an order of one this is an order of two and the question here since this is a cyst it's simply going to say for example the man because remember our assist will be a mascot asking a certain phrase like the man and then the user will have to select from the list of options so change the second and and yeah let's also change the ID to number two so we have challenges ID 1 id2 less than ID one there we go let's go ahead and do the following now so we now have these challenge options for uh this is for the first challenge so let's copy the entire thing you could technically do it all in one but let's separate it it's easier so now this is ID 1 2 3 so this one would be ID 4 this one would be ID five and this one would be ID of six and then the challenge ID here would be two in so this wouldn't be that question this would be just the man right and this would be the correct one and we don't need an image source for this one we would just need an audio Source this is again challenge id2 we don't need an image source this is a oh and this would be the opposite I believe or perhap oh yeah you can also play around okay let's just continue uh working as we are right now challenge ID here is two image source doesn't exist uh I think this should be fine now and let's go ahead and I already want to prepare the third challenge here so if you want you can just copy my seed script you don't have to write this yourselves so let me add this this will be the third option here so the lesson ID here uh is is one and the third option would be select again the order of this is number three and the question of this one can be which one of this is the uh robot for example so we have this handled we have this handled let's copy the first one again and let's go to the bottom here the challenge ID here would be three I believe yes the ID is three which asks which one of these is the Rob bot so inside of my latest insert of the challenge options the challenge ID three would ask which one of this is the robot so this would be false this would be false as well and this would be true uh and I believe we we don't have to write IDs for challenge options they are self- incrementing and we don't need access to them later so yeah let's let's make this easier for us we don't need to write IDs for inserting challenge options we know they're going to be incremented so let me go ahead and remove them just like that but we do need to write IDs for these ones above because we need to map them to the challenge ID but we are not using the challenge options anywhere further and yeah let's change all challenges IDs from one to three that's also important otherwise the code is not going to work so let's try try that out now if I go ahead and go into the terminal and uh let me shut down the drizzle studio and instead run npn databases seed now I think this should work just fine there we go seeding finished looks like everything is working so if I just hit refresh now I should be redirected to the courses page I will select Spanish because that's the only one I have and let's go ahead and see this now is if I click El homra now elra and click check there we go now you can see that we have some progress and we were not immediately redirected back so now this is actually working great and if I go back now and click and session I should have a progress here as well and I do and if I go back here there we go you can see how I'm redirected to the uh Second Challenge which is our assist type of challenge so there we go select the correct meaning this one says the man and I have to select El ombre again if I click check this one is correct and there we go I can now go further and if I click next manually there we go I loading the last question which asks me which one is the robot so if I click check again there we go so we just have to handle this last part where we finally finish the challenge we have this weird redirect so I don't want it to be handled in that way so what I want to do now before we wrap up this chapter is run database seed again again I'm going to leave uh a separate seed script so I'm going to have multiple seed scripts in my project probably I'm going to call this one you know testing the lesson seed or something like that or maybe I will just have seed one two three and four and just find the one which looks the most like this one or you can simply use my git commits inside of my project so in my GitHub every single git comit that I do matches the chapter exactly so so you can just find the exact commit for this one but don't worry there are going to be enough seed scripts for you to try out if you're having problems writing your own and now let's go ahead and let's go back inside of the app folder lesson in inside of quiz here we've handled the correct one right but what we are missing is handling the invalid one so uh let's go ahead and do the following let's also make use of this pending State we are not using pending anywhere so let's find all the places where we have disabled false so we have one right here where we render our challenge so let's change the disabled to be pending and then in here let's add pending or selected option for the footer like that so we've handled the correct option now it's time for us to handle uh the incorrect option so in here to handle that I want to do the following I want to go inside of my actions user progress we already have upsurt user progress but I want to create a separate method here called reduce Hearts it's going to be an asynchronous method which accepts the challenge ID which is a number or a string if you're working with uu IDs let's extract the user ID whoops the user ID from await out and then let's go ahead and write if there is no user ID we can immediately throw new error here unauthorized and then let's go ahead and get the current user progress using await get user progress which is above and let's go ahead and write a too get user subscription so we don't have that yet uh then let's go ahead and let's find the existing challenge progress so const existing challenge progress is going to be await database. query. challenge progress. find first where we're going to use an end operator so make sure you have added end from drizzle omm and also equals so we are going to need both of this but for now let's use the end one uh so in here in the reduce Hearts method we're going to use end and then we're going to use two equals so if challeng chenge progress from database schema so make sure you have added the challenge progress from database schema alongside your user progress this is a new file this is user progress not challenge progress this is where we are writing reduce Hearts so if challenge progress user ID matches the current user ID so that's the type of existing challenge that we want to find but also if challenge progress do challenge ID matches the challenge ID from the props which we just passed so that's how we know that we found the existing challenge progress and that's how we can determine whether something is a practice or not so if this is a practice it means that we have an existing challenge progress so on the front end we are using the percentage initial percentage 100 to derive whether something is a practice or not but on the back end we're going to use the existing challenge progress state so if is practice we can simply break this method and we can return an error here practice that's it and we're simply not going to do anything right if you want to you can display something on the front end to indicate uh to the user that they will not lose Hearts by making a mistake in practice uh or you can simply ignore it there we go uh and now let's go ahead and let's do the following so let's do some additional checks if there is no current user progress we can throw new error here user progress not found also notice the difference between this error that we are throwing here and this error so this is an actual error that I want to completely break this app so I this is critical the user progress needs to exist this one is not exactly an error this is a normal API response imagine it like that and instead of having data we pass in a field called error all right I'm pretty sure there are arguments that this might not be the best practice and I'm specifically talking about naming the field error right so this is completely fine but the field could have been named something like reason maybe the reason I broke this the reason I returned this I returned this this early but I personally like error I know how my API works I know what to expect so the error will not cause any problem in my uh architecture right and we do the same thing in the challenge progress I believe so if user doesn't have enough Hearts we don't break the app we just return a response with a filled error which we read later so we know what to tell the user why didn't this method go through whether these errors these errors means you know I can't even attempt to do something this is even a security issue at this point if you don't have a user progress how did you even get to this point right uh and now let's add a to-do here handle subscription so again if uh if it's not practice and user has a subscription we are not going to reduce their hearts so this is a method for reducing Hearts so if uh there is a uh subscription active we will just break the method and let's do another one if current user progress. Hearts is equal to zero there is no point in reducing the hearts even further so we will just return an error Hearts again meaning you don't have enough hearts for me to reduce Hearts right and let's go ahead and do the following let's do await database update user progress do set and let's pass in the hearts math. Max this time current user progress do hearts minus one or zero so we are doing the opposite logic of up uh of giving uh of adding additional Hearts which we did in the upsert challenge progress so in here we are choosing the larger of the two values right so in case the hearts is already zero and we decre decrease that by one that's going to be minus one we don't want to show that the user has minus one Hearts we want the minimum to be zero so that's why we're using the larger of the two values and zero is larger than a potential minus one so it's going to choose zero that's why in here we're using math. Max and we have to add a where here so where user progress let me just see uh you need to have user progress imported from database schema I don't know if I told you that already or not so you can do the query user progress. user ID and user ID from the structuring wait out I think we already have user progress right we have challenge progress challenge ID yeah so I I don't think you've added that then or perhaps you used it somehow but just ensure you have challenge progress and user progress from database schema otherwise you will not be able to do your queries properly uh all right so we have that finished uh and now what I want to do is revalidate things so let's revalidate path shop learn quests and leader board and let's also go ahead and do slash lesson lesson ID do I have the lesson ID I don't have lesson ID but I think I should have it here so let's go ahead and learn how we can destructure it so very easily if we have the challenge we have the lesson so we have the challenge ID here but we don't load it anywhere so is there a smart way I can do this where do I do this so immediately after my uh current user progress so how about I do this const challenge await database query find first sorry challenges find first where equals challenges from database schema so make sure you have added challenge progress challenges and user progress from the database schema so now that I have the challenges from my schema I can do the query challenges. ID and I can make sure that it matches the challenge ID from my props like that and if there is no challenge here I'm going to throw new error here challenge not found and let me just not misspell The Challenge and then I can destructure the lesson ID from challenge lesson ID so yeah it's a good thing to check this because there's no point in reducing Hearts if the ID for the challenge I'm trying to do it doesn't even exist something is obviously wrong in that case there we go so we now have a method to reduce our hearts so let's go ahead and let's use it in our quiz component so go inside of the app folder lesson and in here we have the quiz component and let's go in the else here start transition reduce Hearts from user progress so let me go ahead and add that here reduce Hearts is the one we've just created we are going to pass in the challenge. ID then we're going to get the response so again if response error is Hearts I'm going to add a console error here which will simply say uh Missing Hearts the same way we have that above and I'm going to break the method so breaking the method is very important and now what I'm going to do is I'm going to set the status to be wrong and then I'm going to go ahead and do the following if there is no response error so why am I doing this if there is an error in my response which is not Hearts because handled Hearts above so the only two types of error that can be is the subscription error or practice error so if any of those two are happening there is no point in me reducing the hearts on the front end right so only if no I no longer got any uh errors in my responses meaning that my reduced Hearts method has surpassed its practice and it has surpassed this and it will surpass the handle subscription that means it has gone to this point which means the back end has reduced the hearts so now I have to reduce the hearts on the front end using set Hearts previous value math Max so the same thing previous minus one and zero to ensure we never go below zero and catch here toast. Sunner uh toast. error something went wrong please try again there we go so let's go ahead and try our uh function again so now we are handling the incorrect States as well so you if you run npm run database seed uh and click on learn you should be able to select the course from the start again so this is my first time there we go let's go ahead and try and decrease our hearts so I'm going to select a robot here click check you can see how I had a nice little disabled field now because I added the pending State there there we go this is incorrect and look at this my hearts are now four what happens if I exit the session there we go they are four again if I refresh my page they are four again perfect and you can see that I have made no progress here so what happens if I try and make this mistakes again there we go I'm making mistakes I'm making mistakes again and I want to prepare my uh console here because I want to get that message that will tell me that I don't have enough Hearts so there we go I'm at zero Hearts so I'm going to click retry again and now it doesn't matter if I try selecting uh a correct answer there we go I have an error missing Hearts if I try trying selecting a wrong answer it shouldn't matter missing Hearts again perfect so that's what we're going to handle in the next chapter we're going to handle improving the feedback on missing Hearts we're going to show the popup which is going to say Hey you have missing Hearts uh you can upgrade to Pro to get unlimited hearts or you can purchase hearts at the shop or what we're going to do uh is H we sorry not or and we're also going to handle some audio effects when we get a correct answer and when when we get the incorrect answer and we also have to the Finish screen so I don't want what I don't want us to just get redirected I want us to see the Finish screen uh nevertheless great great job so there are two things we have to add the first one is audio feedback to our correct or wrong answers and the second one is to resolve the redirect we are getting once we uh select the correct last answer because we want to see a finish screen let's go ahead and go inside of of our terminal here and let's run npm run database seed this will reset the database so that we can try out our challenges again so let me just refresh this there we go I can now select my course and I can go into the first lesson so I want to get feedback if I select this uh option for example so here's where I'm going to G gather my files from so going inside of my public folder you can find the link in the description for my GitHub repository or you can use any audio files you want so I used freesound.org to find cc0 licensed audio files which means you can use this without copyright and without attribution if you are wondering so you need to find a correct audio file so let's go ahead and find this one so I'm going to download that then let's go ahead and let's find incorrect select that one and download it as well and you also need to find finish. MP3 so those three audio files are needed for our quiz and then what you would do is you would drag and drop finish Incorrect and correct inside of your public folder right here so let's go ahead and use them to play the sounds so we already know how to do that because we did it in our card component in the lesson we used use audio from react use to load the audio file so let's go ahead and let's load that now I'm going to go back inside of my quiz component here and let's go ahead and do the following let's go ahead and D structure an array from use audio and let's add the source to be slash correct that VAV let's import use audio from react use so let me move that right here and now that we have added that let let me just find it let's go ahead and destructure our uh correct audio so that's going to be the HTML thing then we are going to not use the second argument so I'm going to call that underscore C for correct and then I'm going to have correct controls which I will use to trigger play or stop then I will copy and paste this I'm going to rename the correct audio and correct controls to incorrect audio and incorrect controls I will rename the underscore C to underscore I representing that we are skipping over the second parameter in this destructured array for the incorrect controls and name the source file incorrect so just make sure that the correct and incorrect match exactly what you have in your public folder there we go correct and in here I have incor correct with the same extension and now I need to find a place where I'm going to uh use this correct audio and incorrect audio so go ahead and find the return method and in here very simply just go ahead and render the incorrect audio and correct audio like that so you need to render them somewhere and now that you've rendered them you can use them you can use the correct controls and incorrect controls so find the on continue method here and this is the first if Clause if we selected a correct option so in here where you set the status the correct after you've confirmed that there there are no hard errors go ahead and do correct do correct controls do playay and the same thing in the else uh Clause after the if one where you set the status to wrong you are going to use incorrect control controls. playay so now let's try this out I'm going to refresh this and let me select uh an incorrect option here I will click on check and there we go if you heard it in the back background of my microphone there was an incorrect sound now let's go ahead and click retry and let me click again for the success one there we go so both of these are working for me so what we have to build next is the end screen uh right now if we try it out so if I go ahead and I select all the answers I need and I go all the way to the last one what happens is I get immediately redirected so the reason I get redirected is because we revalidate the learn page uh no we revalidate the lesson page right so in here in my actions challenge progress we revalidate the learn page so what does that mean well let's go inside of our learn page so inside of app my apology I'm not talking about this is not problematic the lesson page is problematic so when we re when we revalidate the lesson and we just finished the last challenge what happens inside of the lesson here inside of our page we do something called uh user progress and get lesson get lesson is the problematic one so this gets revalidated meaning these two methods are called again so then we H we go inside of this get lesson and what we call is get course progress and inside of this get course progress we attempt to fetch the lesson that the user is currently on well what happens is that the user after they finish the last challenge is no longer in that lesson that they are currently seeing so the revalid ated output of that new uh lesson page currently holds a completely new lesson because of this query right so we find the first uncompleted lesson right and the lesson that the user just completed the last challenge in is no longer the first uncompleted lesson so our get learn method here which uses the get course progress finds the next uncompleted lesson and that is technically fine because in our page here we are very careful so we only pass the initial data so our user interface doesn't change the user still sees the the lesson they last left on but here's what where we arrive to a problem inside of our seed script right here if you take a look at it inside of scripts seed. TS we have all of these lessons right but the only lesson which is actually production ready let's call it like that is the first lesson the second lesson right here has an ID of Two And if I just close this for a second and if I look at my challenges there is not a single challenge which has the lesson ID of two that subsequently means that there are also no challenge options which relate to the lesson of two so then when we go inside side of our queries here what happens is that my get course progress method cannot find the first uncompleted lesson so both active lesson and active lesson ID are undefined or null and then inside of this get lesson method this becomes undefined right actually it becomes null and we also didn't pass any ID in here so that is undefined as well so let ID is undefined and then this returns null in the revalidation of our uh lesson path and then this entire page gets revalidated we hit this page right here and we redirect to slash learn so that's why that happens so now we have to find a way to resolve that so technically this is only an issue in development because in uction you would never have this kind of seed script where you have a lesson uh where where are lessons here they are where you have a lesson which doesn't have its subsequent challenges right later when we create an admin dashboard that's not going to happen because we're going to have a flow of how we create things but our seed script right now kind of created these lessons to be purely um you know what's the name for it present ational right that's why we created all of these additional lessons but none of them actually have any challenges so that's what's going on inside of here so what we can do right now is we can try and do the following so let's what happens if I go ahead and not do this so now we have a lot of issues like this so how about we create how about we just handle that right what if I do lesson. challenges so this is going to be kind of an ugly solution here so I'm going to wrap lessons. challenges and add an empty array here that resolv that and then I'll do the exact same thing here so I'm going to make sure that these lesson do challenges are always un array and then in here I will add this and this and that's one way of doing it but then we also have to change the types so let's not do it this way let me bring this back I'm not going to do that instead so I'm kind of experimenting now because this is a very specific problem that we have here for for development so maybe the easiest thing to do right now would be to go back inside of our seed script and simply add the challenges for the second uh lesson right here so how about we do that so we didn't have to delete anything in the lessons we just have to add just one more challenge inside of our new lesson so this is what we can do let's just uh copy this exactly as it is keep the challenge options as they are right so all of these challenge options are for the each of the three challenges inside of our challenge script so let's go all the way to the end here where we have the console log seting finished and in here let's create some new challenges so I know that there are three challenges which means that this ID is going to be four and then this ID is going to be five and this ID is going to be six so then I can change this lesson ID for each of these to not be one but to be two instead and I think this should already be enough so just the fact that we have some new challenges being inserted for the lesson ID 2 which is no longer nouns but verbs now if you revisit your lessons in here there we go you can see that the title two is verbs and the unit can stay the same right that doesn't matter so let's go ahead and quickly try that out so I think that should be enough because if I look at my queries in the get course progress here yes all we care about is the challenges so as long as we have the challenges and they don't have the challenge progress it should be able to find the first uncompleted lesson so let's go ahead and try it out here so if I go ahead and run mpm run database seed again let's see if that is going to work there we go so that seems to be fine if I refresh now I'm going to choose my language again so Spanish and let me go ahead and just finish this entire course here so I will select the correct meaning and let me go ahead and wrap this up and there we go now when I reached the last one what happened is that right now my new active lesson has completely changed I have finished this lesson my new active lesson is the second one which we just modifi the seed script for but visually the user is still seeing this one and what will happen once I click on next is an error why does an error happen well this is why so if you go inside of your uh quiz component so inside of app quiz. THX right here in here uh we have an on next method which will simply update the current index so technically when it reaches the last one it will update the index to a number higher than the length of our challenges so challenge will be active index something like uh four right but we only have three challenges so that means that the challenge becomes undefined so here's what we have to handle also if you click save here you're going to get redirected to this weird page that's because this actually revalidated hot reload revalidated our app and loaded the next challenge but completely ignore that for now so this is just development stuff all right this is what we have to do now above this const title here you have to go ahead and run the following you have to run if there is no challenge if there is no challenge you will return a div here which will very simp please say hello actually finished the challenge there we go so let's go ahead and try this again if I go inside of my terminal and if I run npn run database seed again now let's quickly see if that is going to work so I'm going to go ahead go back inside of my Local Host 3000 continue learning here Spanish start and let me go ahead and try this again so I'm going to completely finish this challenge L robot and let's click next and there we go no longer do we have an error instead we have a text which says finished the challenge perfect so that is what we are going to be working on now we're going to style this div so it actually displays that we have finished a challenge so in case you cannot see this just do npm run database seed again and make make sure that you don't do any subsequent refreshes and you should be able to see this page right here so this lesson page uh this finished challenge page should only display um it should only display uh uh temporarily once the user refreshes they're no longer going to be seeing the finished page instead it's going to redirect them and load the next challenge or if there is no next challenge or lesson it will simply redirect to the learn page so that is proper behavior from our app it's just a bit hard to do this in development mode with such incorrect data which we have to seed like this so I hope that isn't too confusing right so let's go ahead and prepare an image we need here so I'm going to open my public folder and we need an image from my public folder here uh let me go ahead and click on public we need finish. SVG so let's go ahead and download that so just drag and drop finish. SVG inside of here and then let's go ahead and go back inside of our quiz component here and we're going to go ahead and replace this with a fragment instead and then in here we're going to open a div and let's give it a class name of flex Flex hole Gap one y of 4 LG Gap y of 8 and I think if you do any kind of save uh oh it seems to still be working here okay but this is what I want to do so to make this easier for us to develop feel free to refresh and this will load the next uncompleted lesson challenge thing we just did and hacked in our seed script so do this go ahead and run again npm run database seed this will reset the entire thing so you have no course progress no user progress no challenge progress and then we're going to hard code this to be true so true or not Challenge and I'm going to add too remove true so now go ahead and choose your Spanish course again and simply go into the first Challenge and what you should see is a completely blank screen so now you can see exactly what you're developing because none of this not the header not the challenge nothing will load so we can just focus on developing this there we go I think that's a way we can handle this so all right we've added on large gap Y is eight uh let's add a Max width uh of LG Max with uh sorry MX outo text center items Center justify Center and full height now inside of here I'm going to add my image component so make sure you've added an IM image from next SL image and let's go ahead and use this so I'm going to give it a source of Slash finish. SVG an out of finish and a class name of hidden LG blocks so only on mobile height for this one is going to be 100 and width is going to be 100 as well so it should not be visible now if you're on small device uh if you expand it should be visible but I'm on mobile here so now I'm going to copy and paste this image I will keep everything the same but I will reverse these two actions so hidden on large and uh visible on mobile and I will reduce the vidth for this in half there we go so now I have this little confetti here perfect so let's go ahead below that and add an H1 element which will say great job let's add a break here and let's write U you've completed the lesson and let's go ahead and resolve this by using an appas sign there we go now let's style this H1 element by giving it text extra large on LG text 3 Excel font bold and text neutral 700 like that and now outside of here we're going to open a div create a class name of flex items Center Gap X4 and full width and then inside of here what we have to do is we have to create a component called uh result card so let's go ahead and write a result card here like that if you save you're going to get an error because result card doesn't exist but let's give it a variant of points and let's give it a value of challenges. length times 10 right so we are going to uh know how many points the user earned in this lesson by counting the amount of challenges that the user completed which if they reach the Finish screen is going to be the total length of the challenges and we're going to multiply them by 10 because 10 is how many points we get for each challenge we know that inside of our challenge progress here we increment the points by 10 so yeah it would be a good idea to add this into a constant uh something like points per Challenge and then use that instead of a magic number that would be a tip I would give you if you went into production with this so now we have to create the result card so let's go inside of the lesson result dcard do vsx and let's let's go ahead and create a type props here to accept a value of number and a variant which can be either points or Hearts Let's export con result card here and let's go and assign the props we can then dist structure the value and the variant and let's return in here a div and under our variant here let's go back inside of the uh quiz component and you can import result card from SLR card the same way we did with header footer Challenge and the question bubble and now in here you should see a label which very simply just says points so let's go ahead and copy and paste this below and now what we need to add here uh is the result card for hearts and the hearts are very simply going to be uh Hearts which the user currently has which we hold and synchronize with the back end using state so we know that these hearts are going to be uh up to dat there we go so we now have points and hearts let's go inside of the result card and let's properly style this so I'm going to prepare some imports which I know we will need which is an image import from next image and we are also also going to need CN from lib utils that is it let's give this div a class name of dynamic CN the default classes are going to be rounded to excel border two and full width and the dynamic ones so if the variant is points in that case we're going to use BG orange 400 and Border Orange 400 if the variant is hearts in that case BG is going to be rows 500 and border is going to be rows 500 as well so we have that resolved in our variance here then in here we can remove now this and instead we can open up a new div with a class name again Dynamic let's write the default classes which is padding of one and a half text is going to be white rounded is going to only be available on the top and it's going to be extra large font is going to be bold text is going to be centered uppercase and text is going to be extra small and then let's write if variant is equal to hearts in that case background is going to be row five heart 100 if variant is equal to points in that case BG is going to be orange 400 and then finally inside of this div we can write if variant is equal to Hearts we are going to render Hearts left otherwise total XP there we go so this one says total XP and this one says Hearts left and then inside of here we're going to open another div and then in here let's go ahead uh let's go ahead and uh give it a class name again of CN default is going to be rounded to excel background color is going to be white items is going to be Center which means we also need Flex justify is going to be Center as well adding is going to be uh six so we add some space to it like that font is going to be bold and text is going to be large and inside of here render the value there we go so we have uh 30 points and five Hearts left and now let's add some Dynamic uh values here so if variant is equal to points in that case text is going to be rows 500 if variant is equal to Hearts oh this is the opposite sorry so this should be hearts and this should be points and then in here text is orange 400 uh there we go so total XP 30 Hearts left five what we have to do now is we have to you reuse our hearts and points SVG so you should already have that in your public folder let me find it there we go heart. SVG and where is it points. SVG so we can reuse that we already use that inside of our user progress component in here nope not this one user progress component in here we have the image for points and heart if you don't visit my public folder and simply find the necessary images but you should already have that if you are up to this part of the tutorial so image source in here is going to check if the variant is points let's actually do if the variant is Hearts we're going to use slhe heart. SVG otherwise SL points. SVG so a very simple Turner check here remember to include the Slash and now we can use that image source just above our value here so let's write image here which we've already added an import for Al can just be an icon Source can simply be a result card my apologies uh image source constant which we have just defined from above height is going to be 30 width is going to be 30 and class name is going to be Mr 1.5 and there we go that is our finished screen right here perfect and now we also have to add a footer but we already have the footer so we have to do a very very easy job here let's go back inside of the quiz component here and in here outside of this last div but still inside of the fragment we are going to reuse our footer component so no need to import anything because in the quiz component we already have the footer it's right here it's already imported so we just importing it again here and now we're passing in the lesson ID which is our lesson ID uh but we are not storing our lesson ID anywhere so let's just go ahead and do that so we have to prevent this from being revalidated so the same way we store our hearts and percentage and challenges let's add lesson ID set lesson ID US state initial lesson ID just as simple as that and we actually don't need set lesson ID it's never going to be changed from our side it's only going to be updated from the outside uh there we go so lesson ID uh it seems like I have given our lesson ID an incorrect type in our footer but that's fine we'll come to that in a second let's give this a status of completed and on check it's going to be an empty Arrow function here and there we go let's go back inside of our footer let's give our lesson ID a type of number or a type of string if you're working with uh uyu IDs and look at this now so now you can see we have a practice again button which will redirect the user to SL lesson slash the ID of the lesson so if they want to practice this again because if we cause a Refresh on this page remember it is simply going to load the next lesson for them and if we click continue what should happen is we should get redirected to the uh we should get redirected to the learn page so let's go back inside of our quiz component in here and do I have a router added here I don't have a router so I need to add a router real quick let me add const router use router from next navigation so I'm doing this in the quiz component and I have imported a router from next navigation make sure you check that yours is not from next router that is not going to work and it's going to cause errors now that you have your router component here you can go inside of this little to-do which we to do hack which we have created here and very simply go ahead and add router push slash learn there we go that is it that is our finished section but we are missing two more things here so the first one is the confetti so let's do that let's go inside of our terminal here and let's run npm install react confetti let's wait for this to install then go to the top here and you are going to import confetti from react confetti and then find this fragment here and you're going to render this just inside of this fragment just above the div render the confetti so it's a self closing tag you're going to give it a recycle option of false a number of pieces is going to be 500 and twin duration is going to be 10,000 and one more thing that we need to add here is we need to import from react use let's add use window size and then at the top of our app just above the router let's go ahead and let's extract the width and the height from use Windows size and then we can assign that to the confetti here so width is going to be the width and the height is going to be the height and there we go here you can see my confetti so when you if you try and look at the confetti while you are resizing it's not going to work so make sure you refresh and try it out so you can see how it gets kind of stuck but it will eventually even out uh when you Tred out in an actual environment right so I had this bug as well but it does get evened out eventually so one last thing that we have to do here is we have to make use of our finish. MP3 so if you remember we've added three files at the beginning we added correct we've added Incorrect and we also added finish. MP3 if you didn't it's in my public folder go and download it so now what we have to do is we have to add one more use audio here so I'm going to add it here at the Top This one is going to be simpler so use audio and we're going to go ahead and give it a source of slf finish. MP3 autop play is going to be true here and then what we have to do is very simply uh use finish audio like that and what we can do then is simply render that just above the confetti and if I save here there we go it's quite loud don't get surprised you should be able to have finish audio inside so every time you refresh You're Going to Hear That Sound perfect so now let's go ahead and let's remove this if clause and let's actually uh make this work correctly so only if we don't have this challenge is that going to appear there we go so if you want to you can uh let's just do this together so we ensure we are both on the same slate let's go MP and run database seed again so we reset our database we clear everything we need let's go back inside of here continue learning select Spanish here and let's finish this together so I'm going to select this there we go and let me go ahead and lose one heart for example so I want to make a mistake there we go I've made a mistake now let me select the correct one here let me select the robot here and now when I click on next there we go I told you the confetti are going to be fine we have the confetti we have the sound we have the hearts left and we have the total experience again perfect if you click continue you will get redirected to uh the uh learn page but if you click on practice again you're going to get a 404 but confirm that your url has redirected you to SL Lon SL1 so it will take the ID of your current lesson perhaps it's not one for you if you did some changes in the seed script for example uh but just confirm that it redirected you to the correct place and in the footer here you can confirm that right here so lesson lesson ID and I'm purposely using why am I using window location. hre we could technically do this with router but I think I've purposely used this to cause a full refresh of our app perhaps that's why I did that um we'll see you can try out with router. refresh but for now keep it this way I think there might have been a reason why I did it like this uh great so that's what we're going to do next we're going to enable the user to practice a lesson and remember there are two more things we have to do we have to implement a heart model to tell the user hey you're missing uh some hearts and we have to add a new model for practice to tell the user hey this is a practice lesson great great job so now that we have a finished screen I want to implement some models so let's go ahead and reset our database so I want you to be on the same uh challenge that I am so npm run database seed this will reset all of our progress here and when we refresh the page we're prompted to choose a course again and then you should only see the first challenge here so in here I want you to make mistakes I want you to lose your hearts up to the point where you can no longer get this success or uh incorrect confirmation so there we go I am on two hearts now and I am officially on zero Hearts now so even if I select the cor correct choice now there we go nothing happens because I don't want to indicate to the user whether this is correct or incorrect right they are not supposed to know all that's supposed to happen is that they get an error so let's go ahead and create our hearts model so for that I want to go inside of the store I will copy the use exit model so let me copy and paste this uh like this and I will call this use Hearts model. THS and now I will replace the name of the exit model in all instances and simply put Hearts model so Hearts model State use Hearts model and hearts model state in here so now let's go back inside of our components models let's copy the exit model and let's paste it again and in here I'm going to go ahead and and write uh what's it going to be uh Hearts model. TSX like that there we go and let's rename this to Hearts model and we are no longer going to be using store use exit model but instead we're going to be using use Hearts model which means we have to import use Hearts model from there as well and we have to replace this to use Hearts model there we go so now I want to go ahead and change this to not use mascot sad instead I I want to use mascot bad. SVG we don't have that yet in our public folder so let's prepare our public folder right here go inside of my public folder and copy the mascot bad. SVG so let me go ahead and click on this there we go and simply download this file this is from Kenny game assets and now that we have this mascot bad right here let's go back inside of our hearts model here so let me just close this there we go uh and in here we're going to change the title to say you ran out of hearts and in the description here we're going to go ahead and write get Pro for unlimited parts or uh purchase them in the store and now let's go ahead uh and let's create two buttons here so this one is going to say get unlimited hearts and this one is going to say no thanks so I'm going to change this one to be primary outline and I'm going to change the on click to very simple very simply call close like this and get unlimited Hearts is going to be calling the on click here the variant is going to stay primary and now we have to create the on click here so let's do it above the return here so constant on click is going to very simply close the model and do router push to the store like that and now that we have our hearts model we can go inside of the app folder inside of our main layout here and simply use it just above the exit model let's add Hearts model so make sure you import that from components models Hearts model so now we have to find a way uh to use the hearts model so let's go ahead inside of our quiz component in the lesson there we go let's go all the way to the top here and right here I'm going to add the open from use Hearts model there we go and I'm going to remap the open to Open Hearts model so it's explicit and I've imported this from store sluse Hearts model and I'm just going to add it here there we go and now we have to find a way to fire the use Hearts model so let's find where we catch in our on continue here we catch an error which currently console errors missing Hearts so now we're going to replace that and show the hearts model and now do the same thing in the else where we do reduce Hearts if response error is Hearts instead of console error we're going to open that and break the method so let's try it out now if I select this and click check there we go you ran out of Hearts get Pro for unlimited hearts or purchase them in the store if I click no thanks I'm simply uh get this closed but otherwise I can click this and it will redirect me to a 404 page perfect so that is one thing finished uh so now let's go ahead and do the following let's go inside of the terminal and do npm run database seed again so this will bring back our hearts and reset any progress we've made so let me just go ahead and continue here I will select Spanish and now what I want to do is I want to answer these items correctly so let me go ahead and answer all of this correctly all the way to the end there we go now we have our finished screen and now when I click continue I cannot go back to this one because it's in 404 so now let's go ahead and let's implement this lesson ID here so there actually isn't too much work to do here and I'm going to show you why so this is what we have to do we have all the components we need right we just have to uh kind of modify the logic so let's go inside of the lesson here and in here we're going to create a new for folder lesson ID and in here we can go ahead and copy this page and paste it in here so I want it to be very similar just like that and now inside of here change the quiz to go to do do/ quiz because it's going in outside of this lesson ID folder and instead of this being lesson page let's call it lesson ID page like that and Export default the lesson ID page and now in here we're going to create a type props which will accept the parameters and accept an lesson ID which is a number or a string if you're working with uu IDs and then in here we can accept those props and we can structure the params and then we can pass those params to the get lesson because remember get lesson can accept an ID so we can very simply just pass the pams lesson ID just like that and then we don't have to worry about this revalid a and all that stuff so everything else can stay exactly the same this is lesson ID lesson challenges we have the user progress we have to add the user uh so let me just add a to-do here add user subscription so I don't forget that and I will also uh copy this comment and paste it in the original lesson page here so I just don't want to forget that there we go uh and I believe that should pretty much work already so if I go ahead and click this now there we go and you can see how I have my initial uh completion here great so there is some things we have to work on here but you can clearly see how this is a practice so we are doing a lesson that we already completed right so I can now say if I click here then I should be redirected to SL lesson which is this weird semi lesson which we did in the previous seed scripts right we we didn't add any challenge options we just added challenges for the second lesson so that's why it's appearing like this so there's a clear difference between the old one that I can go through which has all the necessary information so what we have to do now is we have to resolve this so I don't want this to appear full if it's a practice I want to give the user ability to also you know fill up this bar as they would usually but in order to indicate to the user that this is a practice lesson so they don't get confused I want to implement something called a practice model so let's go ahead and do that we can do that by going inside of the store again let's copy the use Hearts model and let's call this use practice model and inside of here let's rename our hearts model state with practice model so we have practice model State use practice model and practice model State here everything else can stay the same uh I addit the typo here great and now go inside of comp components models and you can copy the hearts model here paste it here and rename it to practice model. TSX let's rename this new practice model just make sure you are inside of the practice model right don't accidentally rename your uh correct component so this should be practice model like that and the import should use the practice model as well which means the import here is use practice model which means that in here we use the practice model great so now let's go ahead and do the following so in here we're not going to have any onclick so we can remove that this will stay the same instead of mascot bad we're going to use heart.svg let's change this height and withd to be 100 and change this alt to be heart and this is not going to say you ran out of Hearts instead it's going to say practice lesson and the description is going to say use practice lessons to regain hearts and points you cannot lose hearts or points in practice lessons so practice is another way that user can regain the points and in here we don't need to have two types of buttons so I will remove the one that's giving us an error I will change the variant of this one to Simply Be primary uh I will leave everything as it is and it will simply say I understand right there is nothing they can pretty much do be besides you know acknowledge what we have written and we don't need router so we can remove that there we go very simple and now we can go inside of our app folder layout and we can do the same thing so let's import practice model from practice model there we go and add it just below parts model there we go so now we have to revisit our quiz so we can actually fire app that model so let's go inside of lesson and in here inside of our quiz component and I want to open that practice model so let's go ahead and import from react use let's import use Mount here so I only want this to happen on Mount yes we could have used use effect but use mount for me from react use is simply simpler because I know it will only behave on Mount right and I would rather use an abstraction around use effect than just use use effect to be completely honest with you so in here what I need is I need initial percentage so let me go ahead and do this let me uh add use practice model so make sure you added this import from store use practice model and I will just move this uh here use practice model so the same way we added use Hearts model and now we have this error so let's just replace this one to open practice model and now in here we're going to fire use Mount which we've added an import for we don't need a dependency array here so this is an abstraction around use effect and we can just check if initial percentage is 100 that means the user is visiting this and they have already completed it right so we can open the practice model there we go so now when I look at this one it says a practice lesson use practice lessons to regain hearts and points you cannot lose Hearts all right so I have a little typo here uh where is my practice model there we go you cannot lose hearts or points in practice lessons perfect so let's go ahead and just try something out now so well we actually let's not reset this entirely so first thing I want to resolve is my uh progress here so I don't want the percentage to appear full so let's do let's do it like this let's go inside of our quiz component and in here somewhere we should have the initial percentage there we go so we are using the initial percentage in here so let's go ahead and do this we're going to not just assign it like this instead we're going to open an arrow function here and we're going to return if initial percentage is 100 that means this is a practice lesson Lon so I'm going to pretend that the user is at zero completion because I want to give them an ability to continue learning from zero like it's a pretend lesson right otherwise if it's anything less than 100 that's what we are going to keep as the initial percent uh percentage and there we go so now you can see how this looks like perfect and let's see what happens if I go ahead and select an invalid uh question here so if I click check there we go I have five Hearts because this is a practice lesson which means I cannot lose points here perfect and if I go back we won't really keep saving progress on a practice lesson right so that is reset we only keep progress on the currently active lesson so this is what I want to try now before we wrap this part up let's go ahead and do another mpm run database seed so we can go back to our learn page right this is a 404 whoops so back here so let's select the Spanish course and let's lose some hearts so I'm going to go ahead and select an invalid option here an invalid option again uh okay this is fine and now I'm going to select the correct one so there we go this is correct this is correct like that and the last one is correct so I have finished this now and I have three hearts left but if I go back back to my practice lesson now there we go I can use practice lesson to regain Hearts so if I try wrong one again I stay at three perfect but if I select the correct one I should gain there we go I just gained a heart back to four perfect and this way I can completely regain my hearts and it should never go above five and it doesn't perfect and if I click practice again there we go we reset the lesson perfect oh I remember why I use window location.href now I remember so in my footer last time I think we've discussed why do I use window.location.href here the reason I use it so is that if you click that while you are on this uh route this little practice model never fires so I just kind of used the hack to re-trigger the use Mount uh you know no I I I like it but if you think you if you don't like it you don't have to use it that way you can use router push safely but you are going to lose uh an appearance of this model uh in the end of the lesson uh great so we handled that what I want to handle next is the shop page and the subscription page right so I want to do that next so we can finally purchase and exchange our points for hearts and also a subscription using stripe and after that we're going to do the leaderboard and the quests and see what we have left great great job so now let's go ahead and let's create our shop page which will be used to purchase uh lingo Pro and also to exchange our points to refill our hearts so it's this page right here/ shop which is currently a 404 page so I'm going to go ahead inside of my app folder and I'm going to go ahead and go inside of main here and I'm going to create shop and inside of here I'm going to create a page. DSX let's go ahead and return this so shop page and return ative shop page there we go so we kept a part of our layout so you can see that we have the sidebar here and what we are going to do now is we're going to give this div a class name of flex Flex row reverse Gap 48 pixels and PX of 6 then inside of here I'm going to add the sticky wrapper and inside of there I'm going to add our user progress component there we go so we are using those reusable components which we initially used for the learn page so in here we originally had the sticky wrapper and the user progress right so we are doing the same thing we're reusing our components so we are reusing them here as well great but something is missing for the user progress so let's turn this shop page into an asynchronous method and then what we can do is we can call our user progress so let's go ahead and Define user progress data to be get user progress from database queries and then we're going to go ahead and destructure the user progress from await promise all and we're going to pass user progress data here so later we are going to add the actual subscription here so let's go ahead and let's first protect our page so if there is no user progress or if there is no user progress. active course in that case we can just redirect the user from next navigation to/ courses so just import redirect from next SL navigation and now we have to give our user progress some properties so active course is going to be user progress. active course and hearts is going to be user progress. Hearts points is going to be user progress points and has active subscription for now is going to be false and there we go if you expand this now you should see a familiar screen now we have to develop the feed part so for that we are going to go outside of the sticky wrapper here and we're going to add a feed wrapper so let's go ahead and add the feed wrapper from our components the same way we did with the user progress in the sticky wrapper so all of these are reusable and inside of here we're going to add first a div with a class name of full width Flex Flex column and items Center and then in here we're going to go ahead and create an image component from next slash image so make sure you've added that import and we're going to go get ahead and give it some properties so source for this one is going to reuse our shop. SVG Al is going to be shop inside of here and height is going to be 90 as well as the width so let's try it out here uh why is nothing appearing here there we go so we are using the same icon that we have in our sidebar a big shopping bag icon and then below that that I'm going to open an H1 element with the text shop and a class name text Center font is going to be bold text is going to be neutral 800 text is going to be 2 Excel and my is going to be six below that we will add a paragraph spend your points on cool stuff so later you can add some more things here if you want to so let's give this a text muted foreground text Center text large and margin bottom of six there we go so now we have our shop page here and below the paragraph We are going to add the items component which we don't yet have so let's go ahead inside of the shop page we're going to create items . TSX let's mark this as use client because our original page is not our original page is an asynchronous server component so we have to create a client boundary here and let's write type props here to accept the hearts which is a number points which is a number as well has active subscription which is uh a Boolean and let's export const items here items and let's destructure the props so that is Hearts points and has active subscription there we go now let's head back inside of our shop page and we can now import items from do/ items and now we have some type errors here because we need to pass some necessary props so so for this items let's pass in the hearts which are going to be user progress. hearts and let's go ahead and do the same thing for points and then let's go ahead and write has active subscription for now let's make it false and a to do add subscription so we don't forget that and now we can focus on our development inside of here so I'm going to change this from a div to an unordered list and I'm going to give it a class name of full vidth I'm going to create a div here and the class name of flex items Center full width again padding of four Gap X of four and a border top of two inside of here I'm going to add an image component from next SL image so just make sure you've added that import remember this is also use client so make sure you've tagged that source for this image is going to be heart.svg so we are reusing an image we already have no need to add anything new an Al is going to be heart height is going to be 60 the same as our width there we go and that is it besides the image uh add a div element which will very simple have Flex one and a paragraph refill parts and the paragraph is going to have text neutral 700 text base and also on large it's going to be text extra large and font is going to be bold like that and then outside of the div we are going to add our reusable button component so just make sure you add that to the project this button is going to have a couple of items here so first let's go ahead and do this let's use the hearts so if the hearts are currently full we are going to render full otherwise we're going to render an element of div last name Flex items Center and I'm going to render an image so we already have that the image will have a source of points. SVG so we already have that as well the alt is going to be points as well height is going to be 20 same as the width and then inside we're going to render a paragraph 50 so that is the cost to renew Hearts there we go so we have that now and now for this button well it can actually stay as it is like that uh and let's go ahead and just give it a disabled prop if hearts are equal to five there we go so now we have a field to refill our hearts but it says full and it is disabled so let's do the following let's go inside of our terminal here let's run mpm Rand database seed like that and in here let's now just refresh our page if you refresh on the shop page now it should redirect you here the reason it does that is because inside of the page here we do that if there is no user progress or active course we redirect the user to courses so in here I'm going to choose Spanish again and this is what I'm going to do so I'm going to go ahead and uh I'm going to select uh I'm going to make some wrong choices here right so I'm going to go ahead and reduce my number of Hearts here and then I'm going to go ahead uh and finish this chapter actually you don't have to finish you can just lose Hearts right there we go I have three hearts now uh oh and yeah now if you try and click on these icons they should lead to slash shop which should redirect you to here so yeah there's many ways we can go to the shop page and there we go now I can click on this button which shows to refill the hearts for 50 points great so now now that we have that let's go ahead and create the functionality for that uh the only thing that uh I'm seem to be missing is uh I only have 20 points so I'm going to go ahead and modify this so I'm going to pretend that instead of costing 50 points how about I pretend that this costs 10 points so let me create a little constant here con points to refill will be 10 and then I'm going to use that throughout of here throughout here so instead of saying 50 this I'm going to render points to refill there we go perfect there is also another way I want this button to be disabled and that is if my points are less than points to refill right so now right now it's enabled because I have 20 points because I answered a couple of correct choices but if I change this to 50 then you can see how my point my refill hearts now here says uh it's disabled right so let's bring this back to 10 so that I can actually uh refill my hearts and now we have to create a function which will do that so let's go ahead and do the following let's open up pending and start transition from our used transition Hook from react so make sure you've added an import for that and then in here I'm going to add Con on refill hearts and if the it's currently pending or if hearts are equal to five meaning they are full or if points are below my points to refill in any of that case I will not allow the user to refill their hearts otherwi otherwise let's go ahead and let's start the transition so now let's go ahead and click on this uh let's assign that here so on refill Hearts to be called one click here and let's also add another case here so we have this how about I extend this it's also going to add be if we are pending like that so if we are pending or if hearts are already full or if there are not enough points in any of those cases we are going to disable the button to refill the hearts and now what we have to do is we have to create the refill Hearts method so let's go ahead head inside of the actions inside of the user progress here and at the bottom let's export const refill Hearts make sure this is an asynchronous method let's extract the current user progress from await get user progress we should have that from this method uh we should already have that imported there we go get user progress so we have that here if there is no current user progress throw new error user progress not found if current user progress. hearts are at five we are going to say Throw new error parts are already full and if current user progress. point is less than and then we would have to reuse this so how about we export this points to refill in the items component and can I import it from there there we go so I just added an import here I'm going to separate this right to do move to constants folder file right so right now uh actually I'm not sure if this is a smart thing to do because this is a server action so hm uh let let's let's keep it simple I will not export that from here I will just copy it and I will just assign it here and maybe leave that to do move alongside item component constant into a common file so let's keep that here as well just in case there we go so let's do that and in here I'm going to throw new error not enough points so we already have all of this checks on the front end but this is our API route it's not actually it's a server action but it should behave exactly like an API route so you have to do all the checks that you would do in your uh usual backend stuff right and now let's await database update user progress so you should have user progress not be defined as a constant user progress when using it like this needs to be imported from database schema so we are telling drizzle which schema to update that's what we're doing here so user progress. update set hearts are going to be refilled back to five and points are going to be current user progress. points minus points to refill there we go so minus 10 in our case and of course we need where equals user progress. user ID matches current user progress. user ID and then let's call revalidate Path to/ shop and since hearts are used in a lot of routes let's also do them here as well and lesson actually let's just do this I think this is enough there we go so that should be fine now so we now have a method called refill Hearts so let's go back to our items component on refill Hearts start transition and let's call the on refill Hearts here from actions user progress and let's execute it and. catch tost from soner error something went wrong and let's import toast and move it here there we go so I have three hearts currently if I purchase them there we go now I have 10 points and five Hearts perfect so our method to refill Hearts is working what we have to do next in the shop is Implement stripe subscription which we're going to do in the next chapter great great job so now let's go ahead and let's create a user subscription alongside our refill Hearts option for the shop page so the first thing we have to do is revisit our database schema file in here let's go all the way to the bottom here and let's export const user subscription it's APG table which we are going to store as user underscore subscription in the database table and then let's define its Fields so the ID is Going to Be A Serial ID and primary key user ID is going to be text user ID not null and unique we're going to have a stripe customer ID which is going to be text stripe underscore customer underscore ID not null and unique then we're going to have stripe subscription ID which is going to be text stripe underscore subscription uncore ID again not null and unique let's copy and paste this so this one is going to be stripe price ID with a text of stripe price ID and this one is just going to be not now no need for it to be unque unique and let's copy the last one which is going to be stripe print period end and we are going to store it as such so it's going to be stripe underscore current uncore period underscore end and again uh this one is not going to be uh unique just not null and all of this so far except the ID are going to be serial or sorry are going to be text next right except the last one which is going to be a time stamp so make sure you import time stamp from PG core so I've just added Tim stamp alongside my Boolean integer PG Anum PG table serial and text great so we have that now let's just confirm that there are no typos so I should have stripe one let's see 1 2 3 four five six seven eight instances of stripe none of them are misspelled customer that seems fine so let me just try customer then let's see if I have any subscription Mis types I don't price and current period and all of this is fine ensure that your stripe customer ID is unique your user ID is unique and your stripe subscription ID is unique as well now that you've added that go inside of your terminal and run and run database push and this will push it to Neon database so you have all the new Fields here let's just wait a second for this there we go changes applied so you can do npm run database Studio here and this should show drizzle having a user subscription there we go so ID user ID stripe customer ID and you should not have any records for that at the moment uh great so we can shut this down now and how about we start configuring our stripe configuration here so let's do mpm install stripe in our project and let's go ahead and create a lib for that so I'm going to go inside of the lib folder and create stripe. TS let's import Stripe from stripe a package which we've just installed let's export con stripe to be new stripe and let's use process. environment. stripe aior key like that let's put an exclamation point here so we don't have this type error let's give and yes we don't have this yet we're going to add it in a moment let's add an API version so depending on when you're watching this video uh once you add the quotes you're going to see the autoc completion so just use whatever it AO to completes or just use what I have in this video and typescript is true now let's go ahead and acquire the stripe API key so I'm going to copy that from here I'm going to go inside of my environment file and I'm going to paste and prepare that inside of here and we now have to obtain this uh by going to stripe itself so let's go ahead and do that go to stripe.com and visit your dashboard here so if you don't have any project it's going to tell you to create a new one uh otherwise if you have projects you can go into the upper right corner upper left corner and click on a new account and there we go so I'm going to call this lingo I'm going to create an account this is all going to be in test mode so you don't have to worry about that I'm going to click for API keys for developers and in here we should have the secret key so let's click reveal the secret key and let's click again to copy it let's go back inside of here and let's paste the key I put it in annotations like that now we have our stripe API key um perfect so what I want to do now is I want to keep the stripe tab open so I'm going to go ahead and just keep this in the developer tab now I want to head back to my shop page so that I can complete the UI for purchasing a lingo Pro subscription but just before we do that I want to go ahead and find inside of my public folder in my GitHub link is in the description find unlimited. SVG image which we're going to use to uh as you know as a premium uh icon so let's go ahead and drag and drop unlimited. SVG inside of my folder here and let's go ahead inside of my shop page. TSX so the page we recently created and in here we can now uh actually start filling the gaps where we added the to-do right so in here we've added a little to do I believe add subscription so this is what I want to do now uh I want to create a method called get user subscription so let's go ahead and do this let's go inside of our database qu is let's go all the way to the bottom and let's export const get user subscription it's going to be cached it's going to be an asynchronous arrow function which will first extract the user ID from a waight out you should already have a weight out imported here from clerk if there is no user ID simply return null otherwise let's get our data and let's await database query user subscription which we now have F first where equals user subscription. user ID and our user ID there we go so make sure you've added an import for user subscription inside of at/ database schema so you need to add this import so you can use it at for querying here if we don't have subscription very simply return null otherwise let's see if this subscription is active so remember just by having a subscription that doesn't mean that the user is uh a pro user so when we add a functionality to cancel the subscription there's not going to be a web hook for that right canceling the subscription will simply tell stripe to not renew the subscription next month so the only thing that matters for us is is the stripe current period end value still valid that is the only thing determining whether the user is subscribed or not not just their user subscription model so dat let's check if we have data stripe price ID and if we have data stripe current period and question mark get time let's put a little uh exclamation Point here at the End plus and now let's go ahead and use a constant daycore in milliseconds so let's define that very quickly here so con day in milliseconds is going to be 86 underscore 400 underscore 0 0 0 so this is the equivalent of just writing 86 400 0 0 but this way it's more readable you can do that in JavaScript you can separate by underscore so we are going to add an extra day of buffering just in case so if the current period End plus an extra day is still surpassing today's date that means this subscription has officially uh expired right uh sorry is active not expired and then let's go ahead and spread the data and let's turn is active into a Boolean by very simply giving it two exclamation points at the end so once the user cancels their subscription this is active will eventually turn into false right when a user cancels you don't immediately block access to your app you have to wait until a period they already paid for ends cancelling just means they've canceled a renewal now if they refund well that's a whole another thing to handle but in this tutorial we're just going to handle the purchasing and the C canceling of the tutorial uh of the strip great so now we have a get user subscription method so now we can go back inside of our page here and let's go ahead and add user subscription data to be get user subscription from our database queries so let me move that here and then let's go ahead and add that for the second item in the array and then we have user subscription there we go so now let's go ahead and pass that in the has active subscription so we can already do that here so I'm going to go ahead and pass user subscription question mark is active and I'm going to turn this entire thing into a Boolean by adding two exclamation points so if you're wondering why do I have to add this to little booleans if I already do that here well because we are using the exclamation point here because there is a possibility that the user subscription is null so if it is null the result is going to be null so I have to nulli bullan that by adding two exclamation points right so we solved that and now we have to do the exact same thing here so let me just copy that here uh if you want you can also like Define a constant is pro and then you can reuse that instead there we go is pro is pro and remove the Too Perfect and now we can go back inside of the items here and then we can uh work further on this so let's go ahead uh and do the following I just want to create the UI for now so we already have one button let's create a second one so go outside of this div but still inside of the UL and inside of here give this div a class name of flex items Center full width let me just add this to the top here so full width padding is going to be four padding top is going to be eight Gap is going to be four on X AIS and Border top is going to be two and in here I will use an image component which we already have imported I'm going to give it a source of Slash unlimited. SVG which is a image we've added recently inside of our public folder here so you should have unlimited. SVG if you don't visit my public folder and add it so we have The Source let's give it an out of unlimited let's give it a height of 60 and a width of 60 as well and that should be it for the image and now besides the image add a did with a class name Lex one and a paragraph unlimited parts and let's just copy from the text above this class name right here it's exactly the same so let me just paste that here there we go and now outside of this div Plex one we have to add a button element which will check if we have active subscription in that case it's going to say active otherwise it's going to say upgrade and now let's go ahead and give this button a disabled on pending or as active subscription and now we have to give it an on click on upgrade so now we have to develop the on upgrade method so it's going to be similar to on Hearts here on upgrade is very simply going to start the transition right and now we have to create a server action which is going to create a stripe checkout URL so let's let's go ahead and do that I'm going to go ahead and close everything for now uh let's go inside of actions and let's create our user subscription my apologies so user subscription. THS mark this as use server that is very important and then in here uh let's go ahead and let's just quickly do one thing first so I want to go inside of my lib utils right here so inside of your lib folder where you have stripe you should also have utils and in here we're going to create one reusable function called absolute URL which will accept the path and which is a string and it will simply return a combination of an environment variable called nextore public uncore appcore URL and then it's going to combine that with the path so as simple as that now let's go ahead and let's go inside of our environment file and let's add the next public app URL so copy it from here go inside of your environment file paste it here and write HTTP loal host 3000 so we need to do this this way because we need to pass the absolute URL to the stripe API so it knows where to redirect because if we just tell stripe URL hey redirect to/ shop that means nothing to stripe web hook it doesn't know what is the absolute URL it needs to go to so we have to do it that way so let's head back uh inside of our uh we are inside of the user subscription right right here let's go ahead and import out and current user from at clerk next tojs let's import the stripe line library from s/b stripe let's import the absolute URL from s/ lib utils and let's import get user subscription from at/ database queries then let's define the return URL to use the absolute URL and simply go to back to/ shop so this is going to be HTTP Local Host 3000 SL shop and then later when we deploy we're going to have to change the environment variable to be https you know um my dummy website blah blah blah so that's how it would look like right so let's go ahead and go to export const create stripe URL that's going to be an asynchronous server action which is going to serve for both creating the initial checkout URL and also the settings URL for the subscription so let's extract the user ID from await out let's extract the entire user from await current user if there is no user ID or if there is no user throw new error unauthorized now let's get the user subscription by doing await get user subscription which we have added an import for if we already have a user subscription and if we already have user subscription Dot stripe customer ID that means that we instead of returning a checkout URL we'll return a checkout portal URL so let's go ahead and write cons stripe session to be await stripe billing portal. sessions. create customer is going to be user subscription. strip customer ID and return URL is going to to be the return URL which we have created right here so that's why we need to pass that and then what we going to return to the front end is an object data which is going to be stripe session. URL that's the only thing we need and this method ends here for users that already have a subscription right here so even if their subscription expires we still don't want to return the checkout URL for them we're just going to return a we're just going to return the uh what's the name the checkout portal the billing portal right uh and billing portal doesn't work right away the first time we are going to try it we're going to get an error in the terminal because we have to enable it for development but it's a a oneclick thing so don't worry and now we have to write the code to create the similar stripe session but to give us a checkout URL so let's write const stripe session a wait stripe checkout sessions create mode is going to be subscription payment method types are can be whatever you want so in here you have a ton of PayPal uh I have no idea what these things are honestly I'm just going to add credit uh card right you can go ahead and add whatever you want here and customer email is going to be user which we have from our await current user from clerk so user email addresses the first one in the array email address line items are going to be an array quantity is going to be one price data currency is going to be whatever you want I'm going to use US Dollars product data is going to be an object with the name of lingo Pro and a description of unlimited Parts unit amount outside of the product data but inside of the price data object is going to be 2,000 so that is equivalent to $20 and recurring is going to be interval month so just repeat this is equivalent of 20 Us doll right uh great so we have that and here's a very very important thing without this this payment will not work at all so outside of the line items array you need to add metadata and in here passing the user ID which you've extracted from a weight out above so that's how we are going to know once our web hooks fire which user purchased something otherwise we have no way of knowing so now let's add a success URL to go back to the return URL and cancel URL to go back to return URL it's the same thing and then finally return data strip session. URL there we go we are ready to try this out now let's go back inside of our uh shop page let's go inside of the items and in here let's use create stripe URL method so you can import create stripe URL from user uh actions sorry from actions user subscription and then let's go ahead and do the following so create uh stripe URL do then get the response if we have response data in that case do window.location.href and response. data because that's going to be a URL string let's also add a cat with a the toast from sunar whoops so we already have toast error something went wrong just in case that happens uh let me just align this like that great so that should be it do we already have those we already have those great so let's see if this is working so right now if I click upgrade I should get redirected to a stripe checkout page and there we go I'm redirected to the stripe checkout page but right now if you try so with stripe you always have test cards right so this is an example of one 42 42 42 42 42 42 so just type in 42 in that order until you fill it up and then you have to enter a date in the future and you can use any three numbers for the CVC anything for the name so this is a test card which will always succeed so you can see the test mode being written here and you can see the $20 per month and you can see Unlimited hearts and the name lingo Pro when you click pay And subscribe nothing will happen so right now this will succeed this will be a payment for you and it will just return us back to the shop page that's it but there is something missing here obviously and that is to create a web hook which is going to catch the payment and then using where is it using our metadata which we've added here user ID we are going to detect which user just successfully initiated a payment and then we are going to change create a user subscription data for that user so the reason this isn't done with a DOT then promise or await is because payments can take some times right there there are security checks there are you know 3D secures and sometimes they it just takes some time to process it but as long as you have your web hooks in place and the correct metadata no matter how long it takes for the banks to process for your customer eventually your code will work and create a proper database table for them so that is the next step we now have to create a web hook so let's go ahead and do the following the web Hook is going to be an API route so let's go inside of the app folder and create a new folder called API and inside of it we are going to create a folder called Web Hooks and then inside of that another folder stripe and finally instead of page. DSX route. DSX so that not DSX DS that will represent an API route so now you can visit SL apiweb hooks SL stripe and that is a request and which type of request that is defined by this export asynchronous function and if you want it to be get it would be get if you want it to be post it would be post patch patch so in our case it's post and let's make the request a typle of request here and now let's destructure the body by using await request. text so text is quite important here and now let's get the signature to ensure that whoever is trying to access this web Hook is an actual application that we expect in our case that is stripe so let's use headers from next headers to get the stripe Das signature as string so that's what we are expecting let's go ahead and Define the event to be a type of stripe from stripe do event so we are now importing Stripe from its original package not from our library let's go ahead and open a very simple try and catch here so let me prepare the catch and in here we are going to attempt to structure to construct the event so let's write stripe. web hooks. construct event and I forgot to import our Stripe from lib stripe so let's prepare that so stripe web hooks construct event so this is some additional Security checks so using the body the signature from the headers and one last thing process. environment. stripe webhook secret that's how what we are going to do to ensure that whoever is trying to access our web hook route is the confirmed user so there is pretty much no breaking this right no no way to manipulate this otherwise let's return a new next response which you can import from next server so let me add that here the next response is going to return a very simple web hook error error. message and let's import Let's uh get the error like this the error will also be a type of any and let's also Define the status to be 400 there we go so the status is quite important in a web hook because web hook will uh know what's going on depending on the status so make sure you give it a uh breaking status right now we have to get the stripe web hook secret so let's go ahead inside of our environment file and let's prepare that stripe web hook secret here and now we have to get it in a very certain way since in production it's just a matter of getting it from the console but in development it's a bit harder because are working on Local Host so our website is not publicly available for Stripes so they cannot really send a web hook to a random Local Host computer right your ports are not exposed to the internet thankfully and hopefully they are not so now what we have to do is we have to go ahead and learn how to set up that so inside of your stripe dashboard click on the web hooks here and click test in a local environment and first things first you need to download the CL so click on this link here and whatever you are using Homebrew AP yum scoop Mac OS Linux windows or Docker follow the instructions and make sure you have stripe available in your terminal after you have stripe available in your terminal you're going to run the First Command stripe login let's go ahead and do that so I'm going to go ahead and go inside of my terminal here and I'm going to run stripe login that will give me an URL link so I can press enter now and you can see how it opened that and in here I have my code nice I don't know what it is but it's it's a code that I just have to confirm is the same right it's just a security that no one else is trying to connect but me so I can see it's the same code so I just click allow access and then go back in here and you should see that it says completed and now what you have to do is you have to copy this stripe listened forward to so that's going to simulate our web hook but we have a little mistake here so first of all we are not using this port and this is not our endpoint so our endpoint is the following localhost 3000 sl3000 API SL web hooks SL stripe so that is our endpoint let me close the this running there we go so stripe listened forward to Local Host 3000 API web hooks stripe that is what we are expecting so you can confirm that in here there we go app API web hooks stripe so that is our endpoint and now this bold text is your stripe web hook secret so just paste it inside of quotes like this make sure there are no spaces at the end or the beginning of your secret so just make sure you are able to select exactly what's bolded here and there is one thing we have to do before we try out the next thing so we have to enable this to not be protected by any authentication at all because stripe web hook has its own authentication you saw that here so you see how we construct the event this is the authentication for the web hook right so let's try it out we have to go inside of the middleware and alongside having a slash as the public route we also have enable SL API SL web hooks SL stripe to be enabled as a public route and now what we can finally do is make sure you have your app running so mpm run Dev make sure you have uh stripe listened forward to running make sure you have the uh web hook key added in your do environment variables right here as stripe web hook secret make sure that inside of your uh web hooks post make sure it's a post method your web hook needs to be a post method make sure there are no misspellings in the stripe signature and no misspellings right here a lot of people make this mistake please copy and paste that from here that's the only way you can ensure there are no typos and now let's go ahead and do the following let's copy the third step which is just uh triggering a random CLI action so I'm going to open my third terminal here and I will just pay that paste that here and there we go it says that it succeeded but if I go into my other terminal uh there looks like there are some errors Happening Here looks like something oh no no no that's something else where is my uh all right so we have an error 500 it seems so something is definitely wrong we should not be getting an error 500 so let's go ahead and read what this is oh it's because we did not finish our web hook yes so this is technically correct but let's go ahead and just uh wrap this up by finishing it correctly so we have to go back inside of this route right here and outside of this try and catch event you always have to return a new next response null with a status of 200 so stripe web hooks will cancel themselves and pause themselves if they receive receive too many invalid uh events so make sure that you always return 200 at the end and be scarce with your errors in the web hook let's try this out again so I'm going to go ahead and rerun this stripe trigger payment intent and then I'm going to look at this again there we go this time no errors and we have 200 inside of our post methods so here where I'm running stripe listened forward to the first instance you should have gotten 500 and now the second instance you should be getting 200 meaning this is correct perfect so ensure that you're getting the 200 event and now we are ready to actually write the logic for this so let's go outside of this try catch event and let's get our session using event. dat. object as stripe. checkout. session so we Define the type for this constant and we have stripe imported here from the uh main library now if event. type is equal to checkout session completed in that case we can create a new subscription so first let's define let's extract the user ID metadata so we know for which user is this web hook firing so const subscription is A8 striped subscription subscriptions. retrieve and we are going to pass in the session do subscription as string here if there is no session question mark metadata question mark user ID in that case we are going to return new next response user ID is required and a status of 400 and inside of this user ID uh me just confirm that ins of your uh what is it user subscription actions metadata user ID values need to match exactly so capitalization is important here that is what we are searching for here so make sure you don't have if you have user ID to here you need to search for that here as well so just ensure that this is correct and now in here we're going to create our user subscription so await DB from data base drizzle then we're going to do insert into user subscription from database schema so make sure you add that import values and they are going to be user ID which is session. metadata. user ID stripe subscription ID is going to be subscription. ID then we're going to have stripe customer ID to be subscription do customer as string stripe price ID is going to be subscription. items. data first in the array. price. ID so how do I know it's first in the array because in my user subscription when I generate the checkout session my line items has an array and only one element inside so that's how I know that is the one that I need and stripe current period end is going to be new date we're going to use the subscription do current period end times a th000 there we go so that will successfully create uh and that will successfully create the first uh first time subscription payment and now we're going to create a different if Clause so outside of this if event checkout session completed we're going to handle if a user is renewing their subscription so if event. type is invoice. payment succeeded that means user is renewing their type so this is if a user is creating the subscription for a first time but now we are renewing it so let's just copy and paste this subscription because it's the same thing for retrieving it there we go but we don't have to check for the metadata now instead we can use await database update user subscription again so we already have all of this do set this time stripe price ID is going to be subscription items data first in the array. price. ID and stripe current period end is going to get updated so that little value of ours which checks whether something is active using the stripe current period and and the day offset will now be valid so very simply we are using again the exact same thing subscription current period end times a th000 there we go and let's use where and let's import equals from drizzle orm so just ensure that you have that and pass in the user subscription do stripe subscription ID needs to match subscription. ID there we go so user subscription is imported from database schema and we are comparing a stripe subscription ID here because in our schema all the way to the bottom here stripe subscription ID is unique so we can use that without the metadata like user ID here to very simply uh update an existing subscription and give it a new expiration date so this is an expiration date right here great one thing I forgot to mention this multiplication by a thousand is simply a way to ensure that the timestamps are correctly represented in our JavaScript database by turning them into milliseconds so that's what the Thousand is doing just in case you were wondering and now this should completely work work so let's try it out so this is what I'm going to do I'm going to close everything I'm going to go inside of my terminals here Ure that you have uh 200 getting requests Ure that you have npm runev running and in my other terminal I'm going to run my database Studio here so my database studio in here should not be having uh any let's go ahead should not be having any user subscriptions there we go this is no rows it's completely empty so now if I go ahead and refresh my app here going to the shop let me just wait a second for my app to completely refresh and if I click upgrade right now let's go ahead and do the following let me just prepare my uh stripe web hook so go in your terminal where you have your listened forward to where you're seeing this 200 events and let's go ahead and do our dummy card something in the future any three numbers any letter and click pay And subscribe and if everything is working this should create our user subscription and you can see a bunch of 200 events meaning everything is correct and there we go we have unlimited Hearts there we go it's currently active and as you can see we have an infinite sign here in our hearts perfect and if I go inside of my drizzle studio and if I refresh this there we go I have a user subscription perfect so I just want to do one more thing I want to go inside of the shop here page. vsx go inside of the items and I don't want to disable this button right here uh if we have an active subscription right so instead of this thing update I want to say settings right because we already have unlimited Hearts so in here I want to get that error for my billing portal if you remember so right now if I click on settings here I should get an error there we go I got an error something went wrong and if you go into your terminal you're going to see why internal error you can't create a portal session in test mode until you save customer customer portal settings so you have to find this link and click on it and open it right here and that will lead you to the customer portal so if you want want to I'm pretty sure you can somehow search for this as well let me close this I'm pretty sure that there needs to be a way for you to search it this customer portal there we go settings billing there we go just search for customer portal if you can't find the link and then click activate test link that's it and now try and click on settings again without changing any code you should now be redirected to your stripe uh stripe current plan settings and there we go you can see when our plan renews and from here we can cancel the plan but cancelling it will not immediately remove my infinite Hearts it's only going to remove my infinite Hearts after my plan is supposed to renew so we still have to fulfill the fact that the user paid for this month that's a lot of uh confusion that people have in my YouTube videos they always tell me oh you didn't handle the unsubscribed there is nothing to handle with the unsubscribed it simply won't renew that's the way you handle unsubscribe uh I'm not sure if it's even legal to remove users account users Pro account during their one month of payment so I'm pretty sure that is illegal to do so be careful with that uh not legal advice of course uh great now we have implemented stripe remember whenever you're are testing stripe you need to have uh your stripe running where is it this you need to have stripe listen forward to running otherwise it's not going to work on Local Host uh in development in production is going to be a little bit different perfect so what we have to do now is we have to ensure to uh fill all the gaps for our to-do lists right so I I wrote a bunch of this to do in our app so let me remove everything uh there we go so in here I'm saying to handle subscription in here I'm saying to handle subscription so a bunch of places where we need to handle subscription uh and then once we finish that we're going to add quests and leaderboard uh and then we're going to be able so for example here's a little bug here we display five hearts and I'm pretty sure I know why that is happening so that's what we're going to do in the next chapter we're also going to add some new elements here in this tab uh and then we're going to create an admin dashboard great great job so now that we have our subscription it's time to fill all of our to-do list so first things first that I've noticed in the shop page I have unlimited Hearts but in the learn page I don't have unlimited Hearts so let's go ahead and resolve that I'm going to go inside of my app folder main learn page. DSX and in here I'm am not calling the active subscription so let's go ahead and go inside of the learn page. vsx here you should be here right right and then what we are going to do is we're going to fetch the user subscription data using get user subscription so make sure you've added an import here for that and then we're going to add that here last in the list and add user subscription as the promise result so now we have user subscription here and then let's go ahead and add this here so double exclamation points question mark is active and that should resolve uh my uh my my my inconsistency between the hearts there we go so now it's infinite here and it is infinite here great the other thing that I'm pretty sure is missing is the infinite hearts in here so in here they are still five and if I select an incorrect option I I am losing Hearts here right so we have to fix that on both the back end and the front end so we don't lose any hearts so first things first let's go inside of the uh lesson folder and let's go inside of page. TSX and in here there we go we have a to-do here uh which says that I need to add user subscription so let's go and add user subscription data get user subscription make sure you've added an import right here from database queries user subscription data and user subscription is here there we go and now we can very simply just pass in the user subscription and we can remove this Todo right here and there we go so now you can see that this is infinite and there seems to be a little issue here when I go into Mobile mode it's not really visible so let me go inside of the header component here where is it right here header component and let me try and give this Infinity icon a shrink of zero there we go so now it doesn't get smaller perfect so now we have the infinity tag but I believe believe this still doesn't mean on the back end that it's working properly let's go ahead and ensure that so inside of page lesson we now pass this to the quiz and let's see where do we use this oh we have it to do here as well so let's do that type off user subscription from database schema. infer select there we go make sure you have imported user subscription from database schema so so now my lesson seems to be having uh an error here and that is because the user subscription can be um oh yeah so this is the problem let's get inside of quiz so my get user subscription here returns a little modified version of that so it Returns the data but also is active so we have to account for that as well so let's open this up ad is active which is a Boolean and let's make this whole thing optional there we go now there is no errors in our lesson page when passing the user subscription here so let's go inside of here and see all the places that the user subscription is actually used so in the header uh and that seems to be it uh I just want to see if that is the entirety of it um looks like it is and now let's just go inside of my my actions here so in here we have the challenge progress and there we go to do uh not if user has a subscription so let's go inside of the challenge progress here let's go inside of the first one upsert challenge progress and just below the current user progress I want to add the uh user subscription await get user subscription and import this too so just make sure you've added the query you now have the user subscription here uh and then in here where we have our practice let's remove this to do and if this is not a practice and if we uh don't have an active user subscription so let me collapse these elements there we go so we are only going to return the error for not enough Hearts if the user has zero hearts and this is not a practice and the user doesn't have an active subscription so if this returns true in that case this error uh will not be thrown but if this is false and this is not practice and it's zero then an error will be thrown great so we now have that uh and let's go ahead and just confirm is this the only place where I need that I think that it is right I don't need uh anything else here now let's go ahead and let's go inside of my user progress here uh so inside of the user progress I believe uh oh so this is in my upsert user progress how about close that and go inside of the reduce Hearts instead so let's go do the reduce hearts in instead so in here I'm going to go ahead and do the same thing I'm going to remove the to-do I'm going to get the user subscription using await get user subscription query so again make sure you've added get user subscription and then in here uh I'm going to go ahead and first we get the challenge oh wait where am I reduce Hearts all right so in here we check for the user ID where is my is press practice there we go is practice so if there is a practice we return practice if there is user progress we return not found and then I have to handle that this before I check the hearts so if user subscription is active in that case I'm going to return an error subscription and that will prevent the reduction of Hearts from going on right so we won't do this part at all um perfect so we have that and while we are here it looks like uh we already have some to do here I'm talking about this one upsert user progress so to do enable once units and lesses are added so I think we can do that right course. units uh get course by ID so I think that I have to go inside of this get course by ID method and in here we have a to-do to populate with unit and lessons so let's add with so this is get course by ID inside of my queries right here it is get course by ID inside of database queries so let's add with units order by units ascending ascending units order with lessons or by lessons ascending and let's do ascending lessons. order there we go and then we can go inside of here and we can remove this to do so this is back inside of upsert user progress in the user progress action so if there are no course units length or no lessons in the unit we're going to return an error that course is empty so I think you can already try this out so if I refresh this now and if I mistake something for a robot there we go I didn't think my hearts are reduced so no matter how many times I try and do this I've have definitely done this enough times to be shown an error but since I am an infinite user I'm a pro member I can do this as many times and I can still answer correct answers because uh I have a Pro uh model perfect so now that we have this let me go ahead and see are there any places where I have left mine to do uh there we go inside of lesson ID o yes so in a repeating practice is the place I've missed it there we go in here it still says that I have four Hearts so let's just go ahead and resolve that so I'm going to add const user subscription data to be get user subscription from database queries import let's add the user subscription here and user subscription data is going to be a promise all and then I can pass that in here and I can remove this to do there we go and now there we go now my practice lesson shows my infinite Hearts as well so just make sure you have added the get user subscription import here let's see something else here so this to do user ID in the challenge progress seems to not have broken my app so I can remove that let's see uh what about this to do uh this seems to be working fine this seems to be working fine this queries right here uh I guess we could order them uh yes we should definitely order them so this is what I want you to do I want you to go to get units which is in the queries right here and in here we're going to go ahead and we're going to do our order by so we're going to use lessons and we are going to extract ascending and we are going to return ascending lessons. order right so that's what we're going to do and then we're going to do the same thing with challenges right challenges that order exists I believe there we go so I think that is fine and units need that as well right units units. order like that let's try let's quickly try this out if everything is still working uh it seems to be working just fine but we can easily confirm by going inside of the terminal uh let's close the database Studio One and let's do npm run database seed so that's going to add our new uh it's going to reset everything basically let's just confirm that the first lesson in the first unit is going to be working still if I click here there we go this is exactly what we had previously so the order is still working that's what I wanted to ensure that the order won't mess things up the reason it was working previously is because uh the it was ordering them by IDs right and inside of our seed script the IDS are 1 2 3 four five right they they are incremental so technically you can still rely on IDs but if you want to you can also rely on order and I think this is a much better way uh of doing this right uh perfect uh I think this should be fine right so units find many order lessons we're ordering the lessons challenges right that's how you do that I think uh so we can remove this to-do here and I just want to find one order bu so I can ensure that one that is not inside of get lesson so this one challenges challenges ascending yes this is how you do it great so to do what's left uh move alongside item components to into a constant file uh we can do that so in the root of our application let's create constants dots and let's go ahead and find this Tod do that I have written here let's add it here let's export const points to refill let's remove this from here let's go all the way down and let's import this from at/ constant there we go let me move that here and we have one more place where we have to do this not in the constant not in the user progress but in the main shop so we remove this and instead we import the ones from constants there we go perfect so we just filled that up now what I want to do is uh I want to ensure there are no more to-dos in our app great uh now let's go ahead and let's create the leaderboard which is currently a 404 page so all of these things are going to be quite static until we get to the admin dashboard so let's go inside of Main and in here I'm going to go ahead and copy the shop because it's quite similar and let's call it a leaderboard and that should there we go now inside of page here let's rename this shop page to leaderboard page there we go and let's go ahead uh and let's actually uh style this right so I'm going to go ahead and do the following so user progress can stay the same as usual and in the feed wrapper in here we are not going to use the shop instead we're going to use the leaderboard and leader board is going to be the alt and then the H1 element is going to be leaderboard and the parag graph is going to say see where you stand among other Learners in the community like that there we go perfect uh and now what I want to do is I want to remove the items here and just add to do add user list so there we go now it's empty let's remove the items let's delete the items from this leaderboard folder don't accidentally delete the ones in shop right so we need those uh user subscription data is needed here but there is one more thing that we need uh which is a method to get the top 10 users so let's go inside of the database let's go inside of queries here let's go all the way to the bottom and let's export con get top 10 users to be cach and let's make sure this is an asynchronous method which will uh return the data by using await database query user progress find manyu order by user progress the structure uh what is this uh I'm I forgot how to pronounce this sorry so let's go ahead and use that on user progress. points and let's go ahead and limit this by 10 columns that we so columns are the equivalent of Select in Prisma we need the user ID the username user image source and their points that's what we need and return data there we go perfect uh and let's also let me also do this so I want to F see if we are authenticated and if we are not I'm going to return an EMP array so I just want to ensure this query the same way I would ensure an API route there we go uh and now let's go ahead back inside of our app folder main folder leaderboard page. DSX and in here let's go ahead and let's get top 10 users data to be get top 10 users and then we will have top 10 users matching the top 10 users data promise like that uh or we can maybe call this leaderboard data and then call this leaderboard there we go and now I want us to go ahead in here in this to-do list and let's go over leaderboard do map let's get the individual user progress and index and let's go ahead and return a div or we can we can also return like an immediate return so instead of opening a function here you can open an immediate return and return a div here with a key user progress. user ID and inside you can render uh user progress do username there we go so this is my dummy name account um perfect now I want to add a couple of things from shatan UI so let's add npx shatan UI latest ad Avatar that's the one we need and we also need a separator so let's also go ahead and add a separator component there we go so let's add a separator just from here from components UI separator just above the leaderboard so make sure you've added this import here from components UI give this separator a class name of margin bottom four height of 0.5 and rounded full and now inside of here let's go ahead and style this a bit better so besides key this div is going to have a class name of flex item Center full withd heading of two PX of four rounded of extra large cover BG grade 200/50 like that and then inside of here I'm going to go ahead uh and write a paragraph for this which will say index + one so representing the position of the user and let's give this paragraph a class name font bold text line 700 andmr of 4 and then I'm going to use an avatar component from components UI Avatar so make sure you have added Avatar and Avatar image from components UI Avatar and inside of here I'm going to use an avatar image here I'm going to pass a source to be uh user progress. user image source let's go ahead and give our Avatar a class name of Border BG green 500 height of 12 width of 12 and mL of three and Mr of six and for the Avatar image let's give it a class name of object cover and and then I want to go ahead outside of the Avatar and I want to add a paragraph user progress. username to render our name and another paragraph saying user progress. points XP like that there we go let's give this class name a text of muted foreground let's give this want a class name of font bold and text neutral 800 and flex of one like that uh and I think that should be it there we go so now we have my user right here I have 30 points and this is my name and other users will appear below perfect so we now have a functioning leaderboard great and now let's go ahead and let's create um the quests page so quests page will be quite similar so we can copy the leaderboard and create the quests page so let's go inside of the quests page right here let's rename this to quests page and in here we are not going to need to load the leaderboard data so we can remove that from our promise all and we can remove the get top 10 users from here we don't need that we just need the user progress and the user subscription in here so make sure you have those this can stay the same the ispro can stay the same uh we are passing that to the user progress that is good uh and now in here I'm going to use the image of quests and Al is going to be quests and the text is going to be quests and the description in here will say complete quests by my earning points there we go and I'm going to remove the separator and I'm going to remove this entire thing here I'm going to Simply have to do add quests so this is our dummy quests page we don't need the separator or the Avatar import so let's go to the quests page now it should no longer be a 404 instead it should be this initial text which we have just written here uh let me just see something uh so we use a DOT here how about I add a dot in the quests page here as well uh and now let's go ahead and let's write some quests so I'm going to go ahead and write that here const quests is going to be an array of objects title is going to be earn 20 XP value for that uh is going to be 20 and then we can just go ahead and copy and paste this makes this earn 50 XP value is 50 then it gets a bit harder with 100 XP and then with 500 XP for example right something increasingly more difficult and after that we can do one last one of a thousand you can of course make this Dynamic later uh and now let's go ahead and let's just uh make these quests so inside so just below this paragraph here I'm going to open an unordered list with a class name of full width and I'm going to go ahead and write quests. map I'm going to get the individual Quest so quests in is the name of my constant here right so in here first things first I open the function on purpose because I want to calculate the progress by using the user progress points and divide them by the value of each Quest times a 100 so I get the percentage and then I can return a div I can give my div a key and it can simply be quest. title and let's give this a class name of flex items Center full width padding of four Gap X4 and Border top of two let's go ahead and let's use an image component which we already have imported otherwise it's from next slash image let's give it a source of SLP points. SVG and out of points a width of 60 and a height of 60 there we go so they we we are reusing our points image which we are already using throughout the app outside of this image let's create a div with a class name uh Flex flex-all Gap Y 2 and full with inside of it I'm going to create a paragraph quest. tile and a class name of text neutral 700 text extra large and font bold there we go and then let's add a progress component from component UI progress we already have that we use it in the header of our uh quiz component right so let's give this a value of progress which we Define in each iteration of quest. map and let's give it a class name of height of three and there we go perfect uh except the the first one which doesn't seem to be working for some reason uh not sure why earn 20 XP what if I write 10 uh uh what if I refresh does it work now oh seems to be just some weird cash book maybe I just returned it to 20 and it seems to be just fine right I don't know I guess it is fine uh if I made a mistake in the in the progress here let me go ahead and just console log the progress in each of these and let's also get the value of quest. value so I know exactly what's going on uh and this is a server component so it's going to be logged here in this one so progress is 150 uh is that a problem for our progress component I think it can accept like if I write 99 here that's almost finished if I write 100 that's finished if I go 150 that's still finished so yeah this should work just fine great so that's it for our quests component so that wraps up the shop we have quests leaderboard we have learn uh one thing that I want now is some more items here in the sidebar one of them will include a promotional component to upgrade to Pro so let's go ahead and let's do the following uh let's go back inside of our Scripts seed and let's also add a deletion of user subscriptions right for our seed script let me just see where is this button going so that's okay that those errors are for some deleted uh file so let me close my mpm run Dev here and let me do mpm run database seed and now let me go ahead and do npm run Dev uh npm run Dev again so I could have done that here but fine and now I should no longer be having any progress any points nor should I be having uh any subscription so I'm going to select Spanish here and there we go I am back at the beginning perfect uh and now I want to create some elements here on the side so first let's do that in this learn page right here so I want to go back inside of my app folder main learn page. DSX and below the sticky wrapper user progress we're going to add a promo component so we are going to get an error right now so let's go inside of components here and create a new file promo DSX so this is going to be reusable so this is where you put it and now let's mark this promo as used client and let's export con promo and let's return a div there we go now let's go ahead and import this from components promo so let me just move it right here there we go and now you should no longer be having an error for the promo component so in order to see what you're developing you have to zoom out until you can see that little uh fixed sticky sidebar that we have let's go ahead and style this so I'm going to go ahead and give this the a class name uh of Border two rounded extra large padding of four and space y4 and inside of here I'm going to create another div with a class name of space Y 2 then another div with a class name of flex items Center and GAP X2 and in here I'm going to import an image from next SL image so make sure you add that with a source of unlimited. SVG so I'm going to reuse our unlimited Hearts icon let's call this uh Pro and let's go ahead and give this a height of 26 and a width of 26 here like that and outside of this div encapsulating the image my apologies inside of this div alongside image add an H3 element upgrade to Pro and let's give this a class name of font bold and text large and then below that outside of this div what I started to do was a paragraph which will say get unlimited hearts and more let's give this a class name of text muted foreground and then outside of that div we're going to add a button from /ui button or components UI button in here we're going to say upgrade today let's give this a variant of super so we have that uh we do have a super variant right I just want to confirm that's not something I have uh or you can use any other variant that you like there we go we do have a super variant so use the variant super let's give it a class name of full width a size of large like that and we can make this entire thing be a link so let me just wrap that in a link which will just go to shop so import link from next link there we go uh oh but now we lost the Styles so let's put the link inside of the button instead like this and then let me just align this uh how do I do this oh okay and now we have to also add as child prop here there we go so now when I click here I'm redirected to shop perfect uh and we only want to show this uh if um if we are not subscribed right so I'm going to go ahead and do the following I'm going to use if so we can now it would be quite useful for us to do that little pro pro thing so let me write const is pro so I can do that here now is pro and then in here if it's not pro only then show the promo like that perfect uh and now I'm getting this weird typescript error this will go away on uh refresh here so if if I go ahead and copy this and I want to add it now to all my other pages so that is quests so let me go here in the quests here we already have is pro and I will just add it below here and import it from here from components promo and then I will do the same thing for the leaderboard so find my sticky wrapper and add it here uh if you want to you know you can explore ways to reuse this so you don't have to reuse the sticky wrapper and feed wrapper all the time but I found it that it's useful to do it this way when so you can pick and choose on what pages do you want to show the promotion and where you don't want to show it right so it just gives you some more control so we have leaderboard learn and we should also do it in quests we did that and also in the shop page that's also somewhere where we could display that because why not so just make sure in all of these Pages you have added the promo from components promo right and now they should be everywhere there we go so it's in the learn page it is in the leaderboard page right here it is in the quests and it is in the shop and one more thing that I want to do is I also want to create a little sidebar item uh for my reusable quests so for the quests I want to go ahead and close everything go inside of my components find the promo and paste it here and place this with quests. DSX export const quests from here and this the uh actually none of these have to be used client yeah uh no need for promo to be used client no need to break the boundary so for the quests here let's go inside the learn page and let's add them below the promo so quests from components quests don't accidentally import the page right we are importing uh this reusable component which we can use so let me just expand this there we go so now you should have two identical components here so let's go inside of quests here and we're going to change this a bit so this is not going to uh have anything like this here so let me just remove everything besides the main div here and let me open a div with an H3 which will say quests let's give the H3 element a class name of font bold and text large and besides them I want a link component which will take me to the quests page and it will simply render a button which will say view all the button will have a size of a small and a variant of primary outline so right now these two are below another so I'm going to use the outer div encapsulating those to turn into a flex item Center enter and it's going to be justify between for them let's also ensure they taking the full width and space Y 2 there we go perfect uh so now we have that and now what I want to do is I want to make sure this points can accept uh the points here so which can be a type of number so type props is going to be points which are a type of number props all right and now I want to go inside of here of the learn page where we render this and pass in the points from user progress. points like that uh so this is the learn page right learn folder page and now I want to go inside of quests and I want to revisit my old uh where is it my quests page because I want to copy the quests so perhaps we can move them to constants let's do that let's move the uh quests in the constants file so we can reuse them there we go so they are here now so I can remove them from here and then I can use them here by importing them from the constants there we go import quests from constants perfect so now I'm going back into my reusable components quests and I will add an import for that and now I can go ahead uh and iterate over them so let me go ahead and this is fine let me just go uh where do I want to go uh just below here and I want to add a UL with a class name of full width and space wi of four let's do quest. map let's get the individual Quest and let me just copy what I already have in the quests page so we can speed up the process so we don't need no console logging anymore but I could copy the entire return here there we go and I'm just working with points here because in here it's just a prop but I think by default uh oh yeah I'm missing the progress import so let's import this from /ui progress and let me switch it to components UI progress uh and I think well it actually looks fine if you like it like this you can leave it like this um but perhaps I will modify it just a tiny bit here uh let me see I will remove the Border top I don't think that is needed yeah or maybe maybe it looks better with it you decide for yourself you can style this separately if you want to so let's go ahead and play around with this now right uh oh yeah and let's just add the quests uh somewhere uh oh yeah and let me change this um yeah let me reduce the height and width of this to 40 and change the text extra large to be text small in here uh this can be fine Gap X let me change this to Gap X3 and let me remove the border so I do want it to have just a little bit of a different style and change the height of the progress to height two so now they look a bit smaller right uh I think that is fine and instead of padding four I I just want to use padding bottom four there we go now they look smooth perfect and now inside of the page uh let me see how I use quests so very simply I just add them below the promo now let's do the same thing for the quests actually in the quests we don't have to do it because we are there right so we have to do it in the shop so below this I added import quests from components quests make sure you've added that and besides shop we also need this inside of my leaderboard page So Below the promo we are adding the quests so let's import that from components quests and there we go so if I click on view all it redirects me to the quests where I don't have the same information repeated to me in the sticky wrapper so that's why I didn't create a reusable one so you can pick and choose when you want to add things and if you want to you know create some different promotions on different tabs right because that's what the original Dual lingo app does there we go so we can now see quests on all important websites here let's also do one more thing let's copy the loading page from the uh app folder main learn so we have loading here let's paste it inside we have it in the courses let's paste it in the leaderboard uh ignore these errors they're going to resolve this Andy uh put it in quests and put it in shop here and there we go now all of them should have nice little loading States and not just the learn page perfect so let's try and play our game now so if I go ahead and get some new points here uh I should be having some I should be having some uh some changes in the sidebar so I want to lose some points on purpose now so let me break this and there we go so you can see how much progress I've made by earning 20 pixels 50 pixels 100 500 1,000 right so I'm not close to finishing these ones and if I go into a shop for example if I refill my hearts now there we go you can see how everything gets revalidated and now let's go ahead and see if our Pro promo will um leave once I upgrade so for this you have to ensure that you have your web hook running otherwise it's not going to work so make sure you have this running the stripe listen forward to command let's go ahead and upgrade this I'm going to use the dummy information that I have my dummy name here let's pay And subscribe and this should create a completely new uh there we go 200 everything seems to be working just fine I'm redirected back and there we go I have infinite hearts and my promo is no longer displayed anywhere perfect so I'm pretty sure we've wrapped up everything important needed for here also if you try and choose any other language I think now you get an error that's right because we hover that because in the seed script there are no um lessons for that so we kind of cover that edge case what we're going to do now is we're going to build the admin dashboard and then uh I'm going to show you a seed script that you can copy and paste from my app to test out whether there are any outstanding bugs in your application but that is pretty much it you finish the entire app great great job so let's go ahead and let's create the admin page so I'm going to go ahead and go inside of my app folder and I'm going to create a new folder called admin inside of it let's create a page. DSX and let's name it admin page and let's just return admin page inside as simple as that and now let's go to logal close 3000 SL admin and you should see a text admin page now let's install the packages we need so go inside of your terminal here and I'm going to run npm install react admin and react admin data simple rest so just wait a second for this to this two packages to install uh I'm going to npm runev my project again let's go inside of package.json so there we go I have added react admin data simple rest with a version of 4 16.12 and react admin with the same version so make sure you have those two packages now let's go inside of the admin page here uh inside of the admin folder we're going to create a new file called app. DSX like this go ahead and Mark this app as use client and in here import admin from react admin like that and then export con app like let's actually do a default export so export default app in here and now let's use this admin and let's simply can I just write admin like that because we want to import that back inside of this page here so can I just uh very simply return the admin uh I think I forgot to add okay so how about we do uh sorry app from do/ app uh so it cannot be used as a react component because I forgot to return this can I do this and can I refresh local close 3000 admin let's see if this is going to work just fine so just make sure you refresh things after you uh shut down the app there we go we have react admin our app is properly configured so we can now add a resource as a child of admin perfect so now in here if you want you can read the documentation yourself uh or you can go ahead and see the examples so let's go ahead first uh and let's change the way we import this app so I want to make sure I'm following the rules which say that we need to dynamically import this from next Dynamic so this is what we are going to do I'm going to go ahead and write const app app to be dynamic Arrow function import at SL actually I can do just app like this and SSR false so make sure you don't do server side rendering on this one and save this again refresh this and there we go now we ensured that our react admin is completely on the client side perfect so now we can go back inside of this app and in here this is where we are going to do our work so let's go ahead and let's also add an import for simple rest Provider from react admin data simple rest and let's create our data provider so const data provider is going to be simple rest provider with a prefix of Slash API and then we can pass this data provider data provider right here perfect so let's go ahead and uh let's create our first resource here you can import the resource from react admin let's give this resource a name of courses like this and there we go now in here uh should we see courses or not yet uh I think I also need to add record representation that's going to be the title and for the list can I use list guesser let me see if I can do that yes I can use the list guesser here so here's the thing now we have these courses and we have some errors in here you can look at the network tab right here and you will see that we have an error here for the courses right so it's trying to fetch API courses right so we have to modify this now we have to create the API routes for all of our entities so that they can properly be fetched so let's go ahead and let's first do the uh courses so go inside of the app folder inside of API and in here go ahead and create uh the horses file like this and inside of here a route. DS and then in here import a very simple next response from next server import database from drizzle import courses from database schema and Export con get to be an asynchronous method which will simply get all the data here and await database quare hes find many and very simply return next response. Json data let's try it out now so let's see what happens now now we are no longer getting a 404 page but we are getting this the content rage header is missing in the HTTP response so now we have to fix that uh there are many ways of fixing that uh but the one that I found is that we can go inside of next. config.js right here and you can go ahead and add asynchronous method headers and you can write return open an object and then open an array write source and go ahead and write a regex API dot so basically a pattern matching everything after the/ API route and then let's add a headers array let's add an object of key access-control-allow-origin and I'm going to allow it for development purposes to everyone like that and then I'm going to go ahead and add Access Control allow methods and I'm going to allow get poost put delete and options so I'm going to copy this again this one is going to use headers so the headers are going to be content-type and authorization and copy this again and last one is going to be content range and for this one go ahead and add bytes like this 0 to9 slash an asteris like that so just go ahead uh and add this there we go so that's our next config here so if I save this and if I refresh I think I should be able to see a list of my courses which I've initially added using a seed script uh there we go Spanish Italian French and Croatian perfect and you can see how we have uh kind of a generated Title by itself the tit the the list has generated itself all alone right so this is why that happened so the reason that happened is because react admin has this really really cool thing called list guesser which will basically take the contents of the API response and generate a component that you need but it's not meant to be used like that because it also gives you the jsx of what it expects to uh for this to be right so it generated a course list for us with a DAT greed and everything else it got so that's what we are going to do uh but here's one thing that I want to do first so uh I want to protect this somehow I don't want anyone to be able to go to the slash admin page uh and I don't want anyone to be able to access my/ API SLC courses so this is what I'm going to do I'm going to go inside of lib and I'm going to create my out. CS uh or maybe admin. THS and let's great export con is admin here let's go ahead and let's extract the user ID from await out from clerk nextjs let's make this an asynchronous method and in here I'm going to go ahead uh and what do I want to do basically um I want to go to clerk.com uh just a second all right so here I am in clerk there are multiple ways we can do that we can use their new permissions API uh or we can simply go into our users and we can copy the ID of your user right so there we go this is you this is your user uh and in here you could be you can also explore the metadata for example if you want to explore that but you can also just use you know user IDs here so if you want to you can add uh return user ID equals that right or you can very simple simply create you know uh uh whitelisted or allowed IDs like that and then you can do uh if allowed ID's index of user ID is not equal to minus1 one for example if there is no user ID it's automatically false right something like that so you can try out that and then in here you can just throw all the admin IDs right so maybe this is admin IDs that's one way of doing it um if you want to you can explore uh they definitely have this new uh is it restrictions I'm not sure exactly where it is but uh you can explore their permissions basically so going inside of the documentation and explore permissions perhaps that's something uh more interesting for you but I'm just going to keep it simple for this one I'm going to go straight with the IDS here uh and now that I have this I'm going to go ahead and go inside of my app inside of admin page. DSX and I'm going to turn this into an asynchronous method and very simply I'm going to do if uh let's me let me guess to const uh is admin await and let's make this um uh get is admin get is admin there we go if we are not admin redirect uh to the root page so make sure you have all the Imports and there we go since I am an admin I am not redirected from the admin page so I can safely visit this page but what happens if I change this to 1 to 3 there we go I am redirected back and if I manually try to go to slash admin I cannot visit that page anymore perfect so I'm just going to bring my ID back here I'm going to go back to slash admin and now I can visit that and since this is also reusable does this have to be a synchronous so a wait will I think this will work even without this user ID yeah I think this will work even without that so if I try this again 1 two three yeah it works without it as well so you don't have to wait out uh so we don't have to wait this and I don't have to wait that and I can call this is admin and and I can import this admin here and then I can just do if is admin and execute the function like this immediately there we go so now if I go to SL admin I can visit it if I add an additional here 1 to three I can still uh I get redirected perfect so our logic is working so now let's go ahead uh and let's go inside of our API routes so course is here let's go ahead and do the same thing if is admin from Li admin return new next response unauthorized and a status of 403 not sure which one is unauthorized either 401 or 403 I think 403 is uh there is a difference between unauthenticated and unauthorized right but you can explore that for yourself perfect so now only the admins can also uh fetch the data great so now let's go ahead and let's not use the list guesser instead let's go ahead and let's create our course list so inside of this uh where is it our page here in here we use the admin app so let me close everything besides this so app admin inside we use the admin app let's create a new folder which we're going to call course as an individual entity and inside we're going to create a list. TSX let's import the data grid the list and the text field from our react admin and let's export con course list here to display the list like that and let's add a data grid here with a roll click on to go to edit let's give it a text field with a source ID title and image source and don't forget to return this there we go uh it seems I have an error here so it's not it's data grid but with a lowercase G so data grid like that and then we can go back inside of uh the app here and instead of using list garer we will use the list of the course list like that so we don't need to use those guessers anymore and it's not recommended or expected of you to use them great so now that we have that let's create an ability to actually create uh our resource so for that we have to create uh a course ID here but in first we have to do it inside of the API so API courses inside of here uh let's go ahead and let's create the post version there we go so post same thing uh and let's go ahead and D structure our body so const body is going to be await and we are getting a request here which is a type of request so await request. Json my apologies request in this way and then we are going to use courses we're going to use database. insert courses from database schema so just make sure you add this import database schema do values and we are very simply going to spread the body and make sure you add do returning because we are going to bring this back to the front end and without returning it will not give you what it just created right so make sure you do that and make sure you return the first in the array so we just need since this is an array we just need the one that was created if you don't add returning then this will not work data is just a neon HTTP query result you need to add do returning when using drizzle uh great now you can go back inside of uh the app here where are we uh admin inside of course uh go ahead and copy and paste this and rename this to create. PSX and call this course create instead of list you're going to be using Create so import create from uh react admin and instead of data grid you're going to be using a simple form import simple form is not going to have any props and in here you're going to not have an text field for ID no need for that so you're just going to have a text field for title validate if you want to you can add validate which is an array of rules and in here you can add required for example you can import required from react admin and give it this a label of title and then you can copy and paste this change this to image source and give this a label of image and then you can go back inside of the app and you can add create and you can use course create from course do create so now uh if you click on the create right here uh is this working uh let's see course create did I forget to do something let me just double check so we have create we have a simple form um I'm not sure but I I don't see any Fields here I also don't have any errors here that's interesting how about I shut down the app and try again maybe it's that so this should work just fine course create method we added this we added this we have an image source which have a title we added a create to be course create all right so it's still not working so I have to debug a little bit why so I'm going to pause the video and see what's going on all right I I figured what's going on uh in my create method we should not be using text Fields right we are using text input here so text fields are only for um for representation right for presenting the data but text input are the ones you would use to actually uh add something new so let's try this out now so I'm going to add uh for example uh a new country and I'm going to use an image source of hr. SVG because I know I have that so let me save this and we have an internal server error so let's see why that is happening all right we have something going on here here let's see what happened duplicate key values unique constraint courses uh key so let me try uh not exactly sure why that happens how about I try the following I'm going to create a new script similar to this one so I'm going to copy my seed script I'm going to call this reset and I'm going to call this resetting the database and in this one I'm just going to do the reset part literally nothing else so let me go all the way to the bottom here there we go just this resetting finished failed to reset the database that is all I want from here just to reset the entire thing just to see if it is that maybe so I have a new script now so I can go back inside of my package Json and I can add a new script here database reset uh TSX do script reset. DS or you can also use bun if you want to so let me try this out now so if I go ahead and run mpm run database reset uh all right we are having another problem here it cannot find okay my bad let's try it again database reset resetting the database and there we go database reset finished uh all right so now if I go into my courses here all right let's just wait for this to uh refresh it's kind of slow now let's see why is it slow do I have to reset my database it's just nextjs uh let's try this again so I just want to go on localhost 3000 admin but for some reason it's not letting me go there oh okay there we go courses perfect and now the course is empty I can now click create so let me add creation slr.svg let me click save and there we go now Croatia is here can I now add Spain SL es.svg can I do that now there we go so now there seems to be no problems here at all right so I can go ahead and add I don't know what else do I have French slr.svg so I can click save here there we go uh so if I go ahead and go inside of Local Host 3000 slash learn here I should be redirected to the courses page and there we go I only have Croatia Spain and French perfect so that is a exactly what we wanted to do so now let's go ahead and let's create uh our admin for updating a course so let's say I made a mistake in the Catia label and I want to rename it to something else so for that I'm going to copy the create and I'm going to call this edit right here uh and in here we're also going to be able to change the ID so let's give this an label of ID and it's going to be called course edit like that and then let's go back inside of my app here and let's give this an edit of course edit fromour edit the problem is we also have to create our API route so we can delete this course and also edit it so going to API courses and create a new folder inside Square braet course ID inside of here create a route. DS like that and in here I'm going to go ahead and Export con get first so this is an asynchronous method in here we're going to have a request but we're also going to have params and in this forams is the name of our course ID which we defined through a folder name here so that's how we get pams the same way we get them in the page props so pams is an object with a course ID inside of here which is a type of number we know that because uh our ID in schema is a Serial so let's get the data using await database from drizzle. query. courses. find first where equals from drizzle orm courses from database schema. ID equals course ID equals PR course ID like that and let's return next response. Json data and import next response from next server and I'm just going to do if we are not is admin return new next response unauthorized with a status of 403 if you want to you can use the middleware for this or just do it on a larger route group um but I've heard and seen uh from you know some certain blog posts uh that it's not good to use uh those large groups to to do authentication you should still do it in every single page and in every single uh route basically everywhere where you access your data you should also do a check so that's the only way you can be Serv uh at least that's what I understood from those blog posts so let's go ahead and separate this there we go and while we are here let's also create the put and let's create the delete so I'm going to copy this and call this put and I'm going to change this from course ID uh this is going to be oh this is still course ID my apologies but instead of doing uh finding it here we're going to to get the body from await request. Json and then in here we're going to do database. update courses. set and we're going to spread the body where equals courses. ID params course ID like that and then the last one in here I can just copy get again is going to be delete so again instead of uh find first this one is going to be database. delete courses. where equals courses. ID and params course ID so let me just collapse this there we go like that uh perfect effect and can I also add returning here yes let's add returning here and make sure you add returning in here because we returning that data back uh great so I think that now you should be able to do pretty much everything with this so let me go back to my course here if I click in here why am I seeing it like that let me just uh uh uh uh courses find first course ID oh because I didn't add returning in this one I don't have to uh let's see data image source or undefined let's see what's actually going on behind the screens here so what network tab is being called in here when I refresh in this oh it looks like no network tab is being called if I click here uh okay how about in here so we do have the courses but they don't seem to be uh triggered so let's go inside of the app back here and let's the box so we have edit uh which should oh I know why let's go inside of course edit it's because it's wrapped in create it should be wrapped in edit my apologies so edit there we go so now if I change this to cre 2 and click save and I go to look host 3000c courses and leave this site and Ryan refresh this uh maybe it's just cached oh no I didn't save it ratio two and save element updated so it's still doing that I think now so we do have an error it looks like but see see what was an error so yeah we have to wait um the response update must be like data but the receive data does not have an ID key the data provider is probably wrong for the update uh let's see what did they missed here so inside of my route here I suppose this we're using a put here uh what is data oh I think I have to give it an EMP uh the first in the array again the same way I have to do it here yes let's refresh now so I think this this did update it but it did get this that weird error uh so let me go to locost 3000c courses here there we go now we have Croatia 2 perfect uh if I try this again back to Croatia and click save there let's wait a couple of seconds and now there should be no errors and there are no errors perfect so if I refresh now it's back to Croatia and I can also go here and click delete and I can refresh here did that get an error no there we go so it's just a little bit of cash perfect so we officially have finished the admin dashboard for creating uh the courses what's left now is to literally copy and paste this for all other entities so that's exactly what we are going to do so the next entity we have to take care of is unit so let's just just copy and paste this and let's name this units and now you should have units in your sidebar here and right now it's going to do the exact same thing so now let's go ahead and let's copy the course folder paste it and rename it to units sorry to unit individual first let's do the list so this will be the unit list and let's go ahead and modify uh the data grid to what we expect so we have the ID we have the title besides the title we also have a description and we are also going to introduce something else so let's go ahead and use replace this last one with an order but let's also do this let's add a reference field from react admin and let's give it a source of course ID and a reference of horses like that so we have the unit list now let's go ahead to the app folder and let's use the unit list here from unit list I'm just going to separate this uh and there we go now you should have uh that ready let me just change the record representation is correct so let me just refresh this to see why there we go okay so this is units now it's not found because we have to create an API route for it so it's exactly the same let's go inside of uh API copy the courses and paste it here call this units go inside of route. DS and in here you're going to change the import we we are not we are only going to work with units here so first in the get change the query from courses to units and that's it in the post insert into units and that is it for this one so now if I refresh here I think there we go so we know don't have any units so that is fine so then change this from course ID to unit ID going into route. TS go ahead and remove all instances of course ID so in all three methods we don't need course ID anymore we only work with unit ID and then remove the don't use the database schema hes instead units so now we have to replace all instances of courses and database query units so in here as well units units units here and units here like that great so we have solved the API routs for everything so now we can go back inside of an admin here unit and let's go ahead and let's uh resolve the create here so this is going to be called Unit create it's going to have a title which is a a title instead of image source it's going to have a description and the label of description uh and then it's going to have a reference input so make sure you import reference input from react admin reference input is going to give it a source of course ID and reference of courses and let's go ahead and uh add a number input here because our source is going to be order here validate is going to be the same thing required and label is going to be order so order is a type of number if you don't remember and let's also add a number input here there we go so now if you go ahead and click create here uh you should oh yeah we forgot to go inside of app and we need to replace the course create with unit create import so make sure you add that there we go so now you can see how in here you can choose the course which can be Spain or French right and let's go ahead and do the same thing for edit so I'm going to copy create actually I'm going to copy everything inside of create I'm going to go inside of edit uh and I will just paste it here and I will replace the create methods with edit so I have export const unit edit and I'm using the edit methods here and the only thing that I can add here if you really want to is also the ID field if you really want to modify the ID though this would be a number input as well so let's say I create a first uh for example uh unit one learn the basics of French and I choose a French course and give it an order of one I click save uh all right and yeah I forgot that I have to go back inside of the app and replace course edit with unit edit from.un edit there we go and there we go uh so now you can see how I have my units here and you I have a relation with the French course inside and I can directly see uh which unit and course that is perfect so now if I go inside of here for example if I select French uh all right so it's still not working because I need to create a couple of lessons so now that's the next thing we have to do we have to create the lessons so let's go ahead and do the lessons so first thing I want to do is the API for that that's easier for me kind of so let's go inside of the app folder API let's copy the units here lessons and let's call this lesson ID so first thing we're going to do in the lessons itself and instead of using units anywhere in here we're going to be using lessons so I'm going to search for units and I want to make sure there are none so lessons and lessons that's what matters all right and in here I renamed this to lesson ID that's good let's go inside of here same thing we don't need units we need lessons and actually I'm going to search for individual units so so it catches this as well so we are no longer using unit ID anywhere I need lesson ID now great and let me again search for units so I keep track of things so I'm looking for lessons here equals lessons. ID same thing here lessons basically every single instance of unit needs to be replaced with lessons all right I think that is it perfect so now let's go ahead and let's create a list for our lessons so I'm going to go inside of admin here and I'm going to copy our last unit I'm going to paste it here and I'm going to rename it lesson let's go inside of the the lesson list right here let's rename this to lesson list we're going to have the ID we're going to have the title we're going to have a uh reference field to unit ID which references the units I'm not going to have the description but I will have and this is a number field actually I mean it doesn't really matter for the representation right because you won't modify it from here uh but there we go so this is working this is the lesson list now let's go inside the edit here actually let's first do the create so this will be lesson create uh and in here I very simply want to modify my title I don't have the description instead of course ID this is unit ID and it will modify the units and the order is still here and that is it and then I can copy this go into edit paste it here replace this with lesson edit and replace the create Imports with edit there we go now I can go back inside of my app here I can copy and paste this resource give it the name of lessons representation title can St stay the same because we are still working with title and then lesson list from lessons not list lesson create from lessons create and lesson edit from lesson edit and I will just separate them visually like this there we go so now we have the lessons as well so I can go into the lessons I can create a new one I can give this a title uh so this will be for example nouns right I can go and select the first unit and I can go ahead and give this an order of one element created there we go if I go in here it refers to the unit one which is right here perfect uh excellent so now let's go ahead and let's create the challenge is one so same thing as always let's go inside uh of my app folder API let's copy the last one which are lessons let's rename this to challenges make sure you don't misspell challenges I'm going to change this to challenge ID and let's first resolve this one so no lessons in this room only challenges so let's do database. query. challenges and database. insert done challenges crlf no lessons in this file perfect let's go inside of challenge ID same thing we remove the lessons and write challenges all instances of lesson ID are to be replaced with course whoops challenge ID I search for lesson and I make sure that none of them will exist after my changes so lessons replaced here and here here and here here and here no lesson in my code perfect so challenges challenges challenge ID all of those things replaced perfect uh and now that we have the challenges we have to create so let me copy lessons from here challenges let's start with the list so challenge list and this should be single challenge like this let's stay consistent so this is a challenge list inside of here and in the challenge list I want to go ahead and we are still going to render the ID instead of title this time is going to be a question and then let's go ahead and let's import a select field so let's add a select field right under the question select field here is going to have a source of type choices is open an array open an object open an array and open objects inside like this ID is Select and name is Select and then we're going to have one more so basically those two Anum options from our database assist I believe those are the ones so if I find the type there we go challenges enum are select and assist so that's what we are doing here we are allowing user to choose between the two then we have a reference field to the lesson ID and a reference to lessons and text field again uh number field is order here great so that is my list now let's go ahead and let's do the create one so we are not having the title we are having a question so let's give this a label of question and the order can stay the same name this is this will be a lesson ID lessons and let's go ahead and let's import uh this will be a select input this time so I'm going to add a select input I'm going to give it a source of type and I'm just going to copy from the list the choices in here like that and I'm going to copy validate required as well fil I'm going to copy this entire thing and paste that in the edit here so this is going to be challenge edit did I rename the previous one I didn't challenge create so make sure you have challenge list challenge edit and now challenge create so we are in challenge edit we have to change all the instances of create to use edit there we go so let's go back inside of our app here let's add a new resource here challenges challenge list challenge create and challenge edit there we go and record representation will be a question this time and let me just separate my challenge Imports here there we go so now we have the challenges finally and I can create a new challenge so for example which of these is the the man I'm going to give it a type of Select and the lesson will be announced and Order of this challenge will be one perfect so now I can see my lessons and I can see my challenges which have uh a lesson nouns which is right here which has a relation with the unit which has a relation with the French course so I think now I should be able to click on French and I should be able to see my first lesson there we go and I should even be able to see the question that I've added but I don't have any challenge options so that is the last one which we are going to create you can of course create this for every single entity in your app but I want to keep it only you know enough for us to to have a useful admin dashboard you can make it as useful as you want it so let's go inside of API let's copy the challenges and let's rename this to challenge options like this I believe is that how I want to call it yes challenge options and let's change this to be challenge option ID let's go ahead inside of this route let's use challenge options change the query from challenges to challenge options insert to challenge options as well so be careful with this one make sure you don't have challenges so nowhere should you have the word challenges these two are similar now let's go inside of challenge option ID first thing I want to do is replace all instances of challenge ID to challenge option ID so the exact name of our folder it needs to match the capitalization matters as well so if you keep getting that your records are undefined it's probably because of the incorrect param and now we have to do the same thing so it should all be challenge options so I'm going to search challenges and I want to bring this to zero so challenge options here challenge options challenge options challenge options and challenge options there we go so our API is done now so let's go ahead and let's create a list for the challenge option now inside of app admin I'm going to copy the challenge paste it here rename this to challenge option singular going inside of the list here rename this to challenge option list we're going to have an ID instead of question we're going to have text we are not going to have the select field anymore but we are going to have a Boolean field so let's just uh replace the select field with Boolean field import and the Boolean field uh will be have a source of correct and the reference field will go to challenge ID and reference will be challenges and it's also going to have a text field with a source of image source and audio source and you can remove the number field we don't have it here actually we do have it it's the ID except if you're working with uu IDs then it's a string let's the create one so challenge option create instead of question it's a text there is no select input here but we do have a Boolean input so let's copy the Boolean input add it here the Boolean input will have a source correct and a label of correct option and we are not going to make it required right you don't have to label this as correct the ref reference input is going to go to the challenge ID and reference to challenges there's not going to be an order there's going to be a text input here and it's going to Source an image source so we're going to say image URL here copy and paste this with audio source and let's call this audio URL and uh none of these are also required right we don't need those perfect so no number input here then I'm going to copy the challenge option create going to the edit and I'm going to paste it here and rename this to challenge option edit and replace the create with edit let's go inside of my app here right here let's go ahead and copy the challenges here and let me replace this with the uh challenge options like this and let's have a representation here of text uh I think you can also give it like a um like a label let me just check for a second um so you can see how this is called challenge options right in the sidebar it kind of looks uh bad so how about I uh oh yeah we can use name so you can change the name to be challenge options right you can just write this my apologies I thought it was something else so let's change this to challenge option list this is going to be challenge option create and challenge option edit so these three new Imports here there we go and recorder presentation here uh I think it can be text so let's go ahead and try the challenge options now so uh all right it seems to be having an error let's see why uh uh uh uh uh oh okay I know why so the name needs to match our API route which is challenge options right so this is the name which will call the challenge options API route but in here we can add options and then add a label challenge options there we go now that is better and if you click here here there we go let's go ahead and create one so I'm going to add L I'm going to select the Challenge which is which of this is a man I'm going to give it an image source of uh woman. SVG an audio of esom MP3 I'm going to click save there we go uh I'm going to go ahead and create a new one this one is going to be a text of uh el hombre which is the correct option the same challenge uh SL man.svg sloman sles man. MP3 click save and let's create a third one so I don't know why we're getting that error there at the bottom I'm going to explore the last one can be for example L robot select the correct option let's add slash robot. SVG /es robot. MP3 and click save and there we go you can now see oh yeah so robot should uh not be correct oh so we're having an error oh okay okay let's try this so I cannot load an individual challenge option there is a bug so let's go ahead and see challenge options challenge option ID challenge option ID oh I'm querying the challenges here I should be quering the challenge options there we go that is the bug let's try it out if I click l robot there we go now I can edit and save perfect so only L ombre is the answer for what this question right here we have audio sources so the same way we did in our drizzle but this time it's in uh in an admin dashboard perfect so now I can go ahead and try this out perfect so let's go ahead uh and try this out if I select robot I should get incorrect there we go if I select Lamer it's incorrect and if I select El omra I finished the entire exercise perfect excellent so we're done here uh and you if you want you can go ahead and you know explore this react admin dashboard it's very powerful you can create it as useful as you want uh for example this challenges for example lesson noun and this relation with the unit might get a little unuseful if you have a lot of languages and a lot of courses perhaps it would be better to have something like unit one French right and challenges nouns French right so something like that you can pretty sure I'm pretty sure you can do that you know just by changing the source and the label react admin is very very powerful and you can see there's a ton of features so for example I did not Implement pagination here if you want you can explore that as well their documentation is absolutely amazing great so you have the react admin dashboard finished great great job I'm going to see what there is to do next or are we ready to deploy so the last thing left to do is to deploy the application but just before we do that I want to show you one quick thing that I will add so we won't write this together this is a another seed script that I will do so I will call this production like this prod and I'm going to copy this directly from my uh Snippets here that I have and I'm just going to change this to go to uh where is this uh just a second let me see how I do it in here all right let me copy that there we go I'm going to go ahead and give this ATS ignore like that uh all right so in here I don't need get is correct option I'm not using that so basically this prod is going to be a huge script that I do uh and it's going to fill the Spanish course pretty much ready for production basically it's going to create two units uh and it's going to fill all of this lessons with a bunch of different challenges you see we will have even more characters like uh zombie the girl the boy so we're going to go ahead and do all that so let's go ahead and just populate our public folder with everything we need as well so go inside of my public folder here and let's go find and let's find boy. SVG so all of these are uh Kenny assets so I'm going to download boy. SVG here uh let's wait a second for this to be downloaded and I will drop it in my public folder then I'm going to go ahead and find boy. MP3 in Spanish and I'm going to download that as well so let me drag and drop that in the public folder as well and I have to do that for girl so let me download girl so all of these are from 11 Labs AI you saw the process of how I do that you have 10,000 free uh quotes so you can go ahead and generate as many of this as you want and I also need uh girl. SVG right here let me add that to my public folder and one more character which is the zombie so let me add the zombie character here and let's go ahead and find the zombie. SVG there we go so those are the three characters that I have added here which are being used in this seed script here so now what I'm going to do is I'm going to go ahead in my package Json I'm going to copy my seed script and I'm going to call this DB Pro like that and I'm going to use the prod script so let me go ahead and run that now so I will shut this down and I will run mpm run VB prod and let's see what mistakes did I create here no database connection oh yes my apologies I mean you're not going to get this error uh so inside of prod here I just have to import environment config so it loads the environment variable so this is quite a long database script mpm run database fraud but I just want to check with you so this is how you run it you do npm run prod and it's basically going to fill a bunch of this lessons two units but it's only going to be for one course right it's going to be for Spanish um because I have the assets for that there we go so sitting database database seed successfully and now let's go ahead and let's see how this works so I will refresh this entire thing there we go so we only have Spanish but we are filled with courses as you can see here I can click Start here there we go so we start with our familiar ones but there we go we have El Chico here for example we have uh let's see what you didn't see yet the boy right here right and then we have questions like this to switch it up so this is a script which you can for example show in production to test the app out there we go so I showed you which pictures you needed to add and which sounds you needed to add uh for this to play correctly perfect uh and all the questions will then repeat for the next one so if you go into the next one you're going to see the same questions right so that's the only thing but for a demo it's enough and if you want to you can go to localhost 3000 admin here and then in here there we go you have Spanish and you can see all the units which you have there we go so you have two units here then you have all the lessons and you can see how they repeat themselves because at some point they stop and then they become the lessons for uh for unit two right and same thing is for challenges right but this is basically to show you how this would technically look like in production and then you can play around and find a way to improve this maybe you would find a way to do imagination here or maybe you know instead of showing nouns here somehow indicate this reference to show nouns and also for that to be lesson with the unit one or something you know something to to make this clearer to navigate because it can be quite complicated you can see especially when we have a lot of similar questions uh great but that is a script I wanted to show you now it's time for us to do the deployment so the first thing we're going to do is the following let me go ahead and shut down the app and let's run mpm run build so MPN run build is simply going to tell us whether we have any typescript errors inside of our app if we do we will not be able to deploy uh this is fine as long as the final output has been uh uh received there we go compiled successfully now it's linting and checking the validity and this looks like it's completely fine there we go so if you get this result back that means that you have no type errors inside inside of your app which means we are ready to deploy this so the first step to doing this is to create a GitHub repository so let's go ahead and prepare our new repository here on GitHub I'm going to call this next4 du lingo clone and I'm going to give uh I'm going to make this private and I will simply create the repository so now we have to go inside of our terminal here and run G add get commit and I will write 28 deployment here for you this can just be initial uh commit there we go and then once you've added everything and run a git commit you're going to run this second option to push an existing repository from the command line so let me push that and then once that is done you can refresh here and Bam you have the entire project right here after that is done go into versell here and you should be seeing your new uh GitHub right here so go ahead and add that now you have to fill the environment variables so go inside of your environment file and you can just copy everything and then go ahead and open the environment variables and select the field and simply click paste so all of these are fine but we are going to have to modify the stripe web hook secret and the next public app URL but we can only do that after we deploy because we don't have the uh website URL yet so so I'm going to go ahead and pause and uh let the video play again once this is deployed there we go so we deployed our project so I'm going to go ahead and click continue to dashboard here so I can get my full uh domain name so there we go I have my app deployed so copy the URL make sure you don't click on this deployment URL so not this one because this one changes with every deployment you have a static URL it's this one x14 dualingo clone. verela so it's the shorter one that's the one we need so let's go ahead and do the following let's go inside of stripe and do you remember web hooks well we only have a local listener we never created an actual web hook for a host at endpoint because we never hosted our app but now we do so let's click add endpoint under the hosted endpoints here so you can access this by going into the developers Tab and clicking in the web hooks here so this is our local listener now we have to do the same thing but for a hosted so change the endpoint URL to use your new app SL apiweb hooks SL stripe make sure you don't misspell this and click on select events here the ones we need are checkout session completed remember we have two events in our app and the second one we need is invoice where is it payment succeeded if you are not sure you can go inside of the web hook and find the two events there we go checkout session completed and the other one invoice payment succeeded so those are the two we need go ahead and click on add events there we go confirm them here once more and simply click add endpoint and then click reveal signing secret and copy that then go back to versel go into settings in your project click on environment variables right here find the stripe web hook Secret click on edit and replace the contents inside and click save and then you also have to change the next public uh where is it next public app URL so find that click on edit it's no longer Local Host it is now https and your verell deployment but remove the backward slash sorry the forward slash remove the last one so just make it like this and click save after you have done that go back inside of your deployments tab find the one that is ready and working click on the three dots and click on redeploy and simply press redeployed without any additional changes here and then you can go ahead and open the building to watch that so I'm going to pause the video again and show you when it's complete there we go so the second deployment was successful and now we can finally visit our app and let's go ahead and try it out so I'm going to go ahead head and click I already have an account and I'm going to log in and there we go I am back inside so what I'm interested in now is does my upgrade stripe work so now this is where I should see my events so make sure that let me just ensure where I am uh oh here on stripe so I'm using the test card again because I'm still in test mode regardless if this is deployed or not let's click pay And subscribe and let's refresh here to see if there are any events happening there we go invoice payment succeeded right here we have our event great so where was I right here perfect you can now see that I have a lifetime deal um sorry I don't have a lifetime deal I have uh a pro deal perfect so all of that is working you successfully deployed you have a nice production script here great great job thank you so much for watching this tutorial and see you in the next one