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 content below the header. So let's open up a div and give it a class name flex1.
Let's go ahead and create another div with full height flex again, item center and justify center. Inside of here we're gonna go ahead and open one more div before we add our header. So on large we're gonna define the minimum height to be 350 pixels. On large, we're also gonna 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 0, because we already have defined the maximum width. Let's also define flex here. Plex column, and let's do gap y12. Now, finally, inside of here, let's render our h1 element and inside, let's just go ahead and render my question or for example this will be which of these is 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 3xl 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 challenge component. So let's go ahead and do the following.
So first I want to change this title. So let 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 const title here to check for the challenge type. So first thing that we have to do is we somehow 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 useState and let's pass in 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 const uncompleted index and let's go ahead and use challenges from above dot find index, find the individual challenge which is not completed. So challenge dot completed. Not challenge dot completed. So make sure you've added the exclamation point here.
And then in here let's return if we have an uncompleted index. 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 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 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 move it by, or we can reduce it by one and then it's gonna 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 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 gonna go inside of my terminal here and I'm gonna npn 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 hombre, la mujer and el robot. So that's what we are going to load. So let's go ahead and 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 mypage.tsx right here we are using the get lesson method. So confirm inside of your get lesson which you can get from database queries here that you are actually able to load the find first lesson and their challenges and once you can do 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 this logic right here 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 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 a form of a little question bubble which our mascot is going to ask the user and instead we're going to hard code the title to say select the correct meaning. 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 seed 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 gonna work for type assist but we are gonna go ahead and showcase that. So let's go ahead and write if challenge.type is a syst, in that case, and only in that case, render a question bubble component and pass in the question to be challenge question. Now let's go ahead inside of the lesson folder and create question-bubble.tsx. 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 destructure 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 ./.questionbubble. 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-item-center-gap-x4 and margin-bottom-of-6. Now inside of here we are going to add our image component, which we have added an import for above.
And let's import slash 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, hiddenLGBlock.
And then copy and paste this image. And now we're gonna create a mobile version. So this is gonna be with an height of 40 and it's gonna 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 mascot.
So it's used inside of our navbar folder and our sidebar folder. So you can find the files. You can find it in the public folder. 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 render 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 challenge. Let's give this div a class name of Relative, py2, ex4, border2, rounded, extra-large, text-small, and large-text-base. 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 gonna create an absolute minus left minus 3 top one and a half width 0 height 0 border dash X dash 8 border dash X dash transparent 8, border-x-transparent, border-top 8, transform and minus translate minus y 1.5. Let me zoom out a bit so you can see and one more, rotate dash 90. So we created a little chevron 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, 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 gonna have to derive them 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 from challenge to current challenge but just make sure you change all the necessary types as well but I'm gonna keep this as challenge.
So below this let's go ahead and get the options to be the challenge question mark dot challenge options or an empty array. So let's go ahead and pass in the option. So that is now okay. And now let's go ahead and pass the onSelect. For now that's going to be an empty arrow function.
Status for now is going to be hardcoded to be correct. So let's make this correct. And selected option for now is going to be hard-coded 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.esx. 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, infer 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, infer select and let's simply gather the type itself. Let's export const 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 .slash 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 are going to create a div and let's write class name CN. We can import CN from libutils. So class name is going to be dynamic depending on the challenge type. So let's write the default classes first. This is going to be grid and gap2.
And then let's add a comma. If type is assist, In that case, grid calls 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 two on mobile, but on the large, we're going to use grid dash calls dash repeat but open square brackets so we're gonna write a custom pattern repeat auto dash fit comma min max 0 comma 1 fr 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 this 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 parentheses, 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 el hombre.
And we have la mujer here. So basically we do have these challenge cards, these challenge options rendered. So now instead of rendering divs I want to render card elements. So let's pass in 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.imageSource. Shortcut is going to be openVectix index plus 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 a type props here.
And let's go ahead and accept everything we need. So we did an ID, which is a type of number. Or if you're using UUID, 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, infer 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 .slash 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 gonna have to use our CN import, so let's add that. CN from libutils here. And for this main div, I'm gonna give it an onclick 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 2. We're going to have rounded extra large.
Border bottom is going to be 4 pixels. And hover, BG black slash 5. Padding is going to be 4. On large, padding is going to be 6. Cursor is going to be pointer.
And when the element is active, border-bottom is going to be reduced to 2. So we are going to replicate our button type of element. Right, there we go. Now let's go ahead and give this dynamic types. So if this is selected, we're going to go ahead and change this to use Border Sky 300, BG Sky is gonna be 100.
Then we're gonna have Hover BG Sky 100 so it stays that way. Besides this we're gonna have selected and status being correct so let's go ahead and prepare that and then we're gonna have to handle the other states. So we have status is wrong and we have is status, oh, we only have correct and wrong. And the last one instead is not gonna be selected. It's going to be if this is disabled.
So let's now prepare this. So for this one 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 gonna 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 rows 100 and hover BG rows 100. Let me just see if this is correct.
BG green 100. Okay, I think this is correct. So let's try this out. If I go back inside of my quiz component if I change this from status none to correct 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 hard code this to be true and add pipe pipe here. So I'm gonna add a comment to do remove hard coded true. There we go.
So this is how our correct card instance looks like. So I can zoom out a bit to 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 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 if I go back and change this to wrong it has no effect because we are not gonna show all elements as wrong we are only gonna 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 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 render an image component from next slash image so make sure you've added this import here and give this image component a source of image source a fill property and an alt of text.
And give this div a class name of relative aspect square margin-bottom of 4, max-height 80 pixels, LG max-height 150 pixels and full width. So you can see how now I have these el hombre, la mujer and el 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 slash men.svg slash woman.svg and slash robot.svg. So I'm gonna head into my public folder and I'm gonna add these 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 download this one.
So these are from Kene 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'm 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 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.
If yours are still not loading, 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? 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 slash 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 gonna 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, El hombre, la mujer and el 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 TextSky 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 Vrong. 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 gonna write text green 500. For Vrong I'm gonna write text rose 500. Great and now that we have this let's go ahead and let me just see did I miss something here So we have flex item center justified between. 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 15 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 these states from the paragraph above. So I 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. 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 shortcut if the current item is selected.
So we're going to change the border to Sky 300 and text to sky 500. If we are correct, we are 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 pipe pipe. There we go. This is how it looks when selected, looks pretty good.
And now let's go inside of quiz and change 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, 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 11Labs AI to generate voices in different languages.
And then we're gonna have to create a logic which will show, which will actually select our option. We're gonna have to create these shortcuts and then we're gonna have to create a footer 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.