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 Labs. 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 person saying the voice, you can go ahead and play around with the voice settings in here and then inside go ahead and create a couple of phrases. For example, I'm gonna go ahead and create El Hombre, I'm gonna go ahead and click generate right here and there we go. I have El Hombre right here so I'm gonna 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 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 ahead 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 them, I believe. So yeah, 29 languages. Great.
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 man, 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 slash es underscore man, es underscore woman and robot. So go ahead and use the 11 labs AI to create one for El Hombre, one for La Mujer. 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 La Mujer, right?
And you would do the same thing. And then you would do El 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, 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. We're also gonna 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 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 man, robot and a woman. And these sounds match exactly what I have as audio source files inside of my database es underscore man es underscore robot and es underscore 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 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 a collection of packages for us here. So let's go ahead and go back inside of our card component in a side of our lesson folder. So this is the last place where we worked, where we added the little shortcut and all the different selected and status variants. So now we're gonna go ahead and create this onClick method, right?
So let me show you this while demonstrating that. I'm gonna create my handleClick here. So handleClick is gonna be useCallback from React. So make sure you've added use callback. The reason we need use callback is because this is gonna 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. Sorry, the dependency array. And then let's do onClick here.
So very simple. And let's add the onClick here. And here's what I want to do now. I also want to add 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 useAudio and also useKey. So we are going to use both of those. Let's prepare the useAudio one just above the handle click. Go ahead and the structure an array from useAudio. And inside of here, simply add source to be the audio source property.
Right, there we go. Audio source right here. 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. It looks like I can do that easily. And then let's go ahead and destructure audio. And then let me show you how this looks like. Can I see?
I'm not really satisfied with this documentation, but basically we don't need the second argument. So I'm gonna use an 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, literally anywhere. So if you want to, you can wrap the entire thing in a fragment and add it at the beginning. But I'm gonna, for example, just add it here. So it's just gonna be an audio file, an audio element, that's it. It's not gonna 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.play. 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 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 useKey from React use. So when we click on number 1 on our keyboard I want to call handleClick. Like that. And in the last one I'm gonna go ahead, in the next one I'm gonna give it an empty options and then in the last one I'm gonna use this handleClick here as the dependency array.
And now if you try it out, if you have this set up correctly, your audio source files should play. So I'm gonna try it out now. And I just forgot one thing. So I'm using handle click, but I never passed it to the on click here. So let's add handle click here.
There we go. And let's click here. There we go. 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 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, let me go ahead and actually use a visual indicator that I had clicked on something. So right now we play the controls and we call the onClick prop which comes from the challenge, which calls the onSelect right here, but if we go into our quiz onSelect 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 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 is gonna be undefined so let's go ahead and use the selected option and set selected option now. So we can do that very simply inside of our onClick function. Sorry, const onSelect method.
So onSelect is going to accept an ID, which is a number. If you're using UUIDs, it's going to be a string. So if the status is anything other than none, we are going to break the method. And I believe we also need to create a state for our status here. So status setStatus.useState 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 a none.
So if it is anything other than the 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.
You're going to see how this works later. And then let's simply do set selected option and passing 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 hard-coded 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 El Hombre, there we go. A sound has played and now El Hombre is selected. La mujer, el robot.
Let's try without using my mouse. There we go. All of these are working. Perfect. So now that we can select both visually and 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, on select and all of those things here and find the end 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 disable 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.tsx and now inside of here let's go ahead and add some imports so I'm going to import useKey and useMedia from react-use I'm going to import checkCircle from lucidreact and xCircle from lucidreact as well. Let's prepare our CN library and let's prepare our Button component from Components UI button. Let's define the props 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 footer completed. So completed is if we finish 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 const 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 .slash footer the same way we did with the header right here. So we are not going to use the lesson ID at the moment. Instead this is what we are going to do.
So we're gonna go ahead and style this. So let's go ahead and give this a class name of CN. The default classes are gonna be LG height of 140 pixels, then a height of 100 pixels on mobile devices. And border top is going to be 2. 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 of 1040 pixels, full height mx auto, flex items center, justify between, px of six, lg px of 10. So we are just going to push it a bit so it suits the same spacing as our header right here.
So we are using that max-width 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 gonna be disabled. Last name is gonna be ML-Auto. So it's always in our right corner. On click 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 wanna use the, I wanna use my prop size and I can't do that by using LG or MD. So what I can do is make use of our ReactU since we already have it here. So const isMobile is gonna be useMedia. And I'm gonna go ahead and write in parentheses this is in strings so in parentheses max width of 1024 pixels so if it goes above that is mobile is mobile is going to be triggered So let me just go ahead and see, did I break something here?
What is our error here? Unexpected token footer. Oh, so I just have to pass in is mobile. In that case, it's gonna be small, otherwise LG. I think that should, there we go.
So that now works, perfect. And variant, if status is wrong, is going to be danger, otherwise, is 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 ternary. 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, 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. 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 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 array. There we go. But we are not done yet. So now what we have to do is we have to play a bit 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 if status is correct we're gonna go ahead and render a div here 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 6, width 6, on LG height 10 and width 10 on LG and MR of 4 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 it also says next so let's go ahead and bring this 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 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 rose 500 and instead of nicely done, oops, I changed the incorrect one. Make sure you're changing the one which says wrong. So text rose 500 X circle and instead of nicely done is 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. And now we are missing one more case. So let's copy the last one here and this is gonna 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. Pipe5 status.
So I remember to remove this later, right? So right now I'm going to hard code the status to be completed. Alright, let's try it out. So 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, isMobile is gonna be small, otherwise large. And on click, what I'm going to do is very simply use window.location.href. And I'm going to change it to backticks slash lesson. And I'm gonna 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 of being finished. But later we are going to change this screen as well. So if you're wondering how will this footer ever receive the status 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 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.