So I found out why our lessons are appearing as completed. So it is because this first lesson has some challenges. So if we go inside of seed here, let me close the courses, we have units and then we have lessons here. But we have only created the challenges for the first lesson. We never did it for all the other lessons.
Thus, these queries don't really work as expected. So this is what you can do. Well, technically in production this should never happen. You would never publish a lesson which doesn't have a challenge. But nevertheless, find your getUnits 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 an early return. So if unit.lessons.challenges.length is equal to 0, 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 locked fields. So this is now looking like something we would expect.
Great, so you can do this early return if you want to. 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 inside of them, right? But since we're using this hacky script, 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 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.tsx 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 gonna be it for now. Let's go ahead and use await, promise that 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 slash 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 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 uncompleted 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 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 initial lesson ID to be lesson.id.
Let's pass in the initial lesson challenges to be lesson.challenges. Let's pass in the initial hearts to be user progress.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... 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.tsx. 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 a type of challenges let's not misspell challenges from database schema dot infer select and let's open this and let's add our normalization fields which are completed which is a boolean 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, infer, 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 subscription DB type. Let's export const quiz component. And let's destructure 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 apologies, lesson page and we can import quiz from .slash 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 what's causing the progress here? Let's see. The expected types comes from property initial lesson challenges which is declared here on type, 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 wrap these entire thing inside of parentheses like this. There we go. So once I've wrapped this in parentheses this then triggered a proper array like that.
So does it work now? Oh yeah, yeah that makes sense because this array means that just this additional part of inferselect is an array, right? So when I hover on 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 we no longer have any type errors. Great. So now inside of this quiz component, I want to go ahead 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 hasActiveSubscription for now is going to be double exclamation points, user subscription, question mark dot 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 gonna add hearts, set hearts and I'm gonna import use state from React because I can do that because I have useClient at the top so ensure that you have useClient at the top for this component and pass in 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 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 boolean. Let's export cost header. Let's return a div saying header.
And let's assign these 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 .slash 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.tsx 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 are going to give this outer div a class name of flex flex-col and h-full and the inner div which will render the children is going to have a class name of flex flex-col height-full and width-full. Like that. Great. So that is our lesson layout. Now let's go back inside the header component here and I want to mark this, actually we don't have to mark it as use 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 responsiveness here. So on large, we're gonna 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 gonna exist in a moment. Max width for this header is going to be 1140 pixels. MX auto 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 use this trick with Max with an NX auto 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 gonna go ahead and give it an on click for now to be an empty arrow function. Let's add to do add on exit.
And let's give it a class name of TextSlate500. Hover Opacity is gonna 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 ShadCN library. So let me go ahead and I will shut down my Drizzle Studio. I don't even know if you have it running. If you do, just shut it down and let's use npx ShadCN UI act latest add progress.
That's the component we need next. So there we go. We now have that component. So let's go ahead and add it so progress here from components UI progress so not from radix from components UI progress like that. And the value here is gonna be the percentage.
There we go. And then let's go ahead and create a div here with a class name of text rows 500. Let's use that flex items center and font bold. Let's add an image component from next slash image and I'm gonna go ahead and give it a source of slash heart.svg. I'm going to give it a height of 28 and I'm also gonna do a width of 28.
And I'm gonna give it an alt of heart and a class name of margin-right of 2. 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 keyboard in JavaScript.
So I'd rather you use infinity icon here. So infinity icon and let's give it a class name height 6 width 6 and stroke 3 inside of square brackets and otherwise simply render the hearts. There we go. We have a nice game-ish 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 gonna 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 use the proper color for this one, right? So let's go ahead and do that. I'm gonna go inside of my progress component, which we've just added using the Chatsie and CLI tool. So I'm gonna 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 about we do that? And there we go. Now this is looking much, much better.
Perfect. So now that we have this, I want to go ahead and I want to create an exit model. So something that I have added a to-do 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 onClick, which when we click on. Is going to open a pop-up 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.