So let's go ahead and let's create the admin page. So I'm gonna 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.tsx 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 localhost 3000 slash 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 two packages to install. I'm going to npm run dev 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. Inside of the admin folder, we're going to create a new file called app.tsx, like this. Go ahead and mark this app as used client and in here import admin from react admin like that and then export const app 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 very simply return the admin? I think I forgot to add. Okay. So how about we did, sorry, app from dot slash app.
So it cannot be used as a react component because I forgot to return this. Can I do this? And can I refresh localhost 3000 slash admin? Let's see if this is going to work just fine. So just make sure you refresh things after you 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 or you can go ahead and see the examples.
So let's go ahead first 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, add slash, 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 SimpleRESTProvider from React admin data SimpleREST 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 right here. Perfect.
So let's go ahead and 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 should we see courses or not yet? I think I also need to add record representation. That's going to be the title. And for the list, can I use ListGuesser?
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.
So it's trying to fetch API courses. 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 courses. So go inside of the app folder inside of API and in here go ahead and create the courses file like this and inside of here a route.ts and then in here import a very simple next response from next server, import database from Drizzle, import courses from database schema, and export const get to be an asynchronous method, which will simply get all the data here and await database query courses find many, and very simply return next response dot 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 range header is missing in the HTTP response. So now we have to fix that. There are many ways of fixing that but the one that I found is that we can go inside of next.config.mjs 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. So basically a pattern matching everything after the slash 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, post, 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 to 9 slash an asterisk. Like that, So just go ahead 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. There we go, Spanish, Italian, French and Croatian. Perfect! And you can see how we have kind of a generated title by itself.
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 a 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 for this to be. It generated a course list for us with the data grid and everything else it got.
That's what we are going to do. But here's one thing that I want to do first. I want to protect this somehow. I don't want anyone to be able to go to the slash admin page and I don't want anyone to be able to access my slash API slash courses. So this is what I'm going to do.
I'm going to go inside of lib and I'm going to create myauth.ts or maybe admin.ts and let's get export const is admin here. Let's go ahead and let's extract the user ID from await auth from clerk next JS. Let's make this an asynchronous method. And in here, I'm gonna go ahead. And what do I want to do?
Basically, I wanna go to clerk.com. 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, 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. And in here, 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 a return user ID equals that, right? Or you can very simple, simply create, you know, a white listed or allowed IDs like that. And then you can do if allowed IDs index off user ID is not equal to minus 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. If you want to, you can explore, they definitely have this new, is it restrictions? I'm not sure exactly where it is, but you can explore their permissions basically. So going inside of the documentation and explore permissions, perhaps that's something more interesting for you. But I'm just gonna keep it simple for this one.
I'm gonna go straight with the IDs here. And now that I have this, I'm going to go ahead and go inside of my app, inside of admin page.tsx. And I'm going to turn this into an asynchronous method. And very simply, I'm going to do if, let's make, let me get stuff. Const is isAdmin, await, and let's make this getIsAdmin.
GetIsAdmin, There we go. If we are not admin, redirect 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 one to three? 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 gonna bring my ID back here. I'm gonna go back to slash admin, and now I can visit that. And since this is also reusable, does this have to be asynchronous? So await 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, one, two, three. Yeah, it works without it as well. So you don't have to wait out. So we don't have to wait this and I don't have to wait that.
And I can call this is admin and I can import this admin here. And then I can just do if isAdmin and execute the function like this immediately. There we go. So now if I go to slash admin, I can visit it. If I add an additional here, one, two, three, I can still, I get redirected.
Perfect. So our logic is working. So now let's go ahead and let's go inside of our API routes. So courses here, let's go ahead and do the same thing. If is admin from libadmin, return new next response, unauthorized and a status of 403.
Not sure which one is unauthorized, either 401 or 403. I think 403 is, there is a difference between unauthenticated and unauthorized, right? But you can explore that for yourself. Perfect, so now only the admins can also 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, where is it? 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 are 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 const course list here to display the list like that. And let's add a data grid here with a row click on the 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. 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 the app here. And instead of using the list guesser, we would 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 our resource.
So for that we have to create a course ID here but first we have to do it inside of the API. So API courses inside of here. Let's go ahead and let's create the post version. There we go. So post, same thing.
And let's go ahead and destructure our body. So constant 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, the 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 that values, and we are very simply going to spread the body and make sure you add dot 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 the returning, then this will not work. Data is just a neon HTTP query result. You need to add dot returning when using Drizzle.
Great, now you can go back inside of the app here. Where are we? Admin. Inside of course, 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 react admin and instead of data grid you're gonna be using a simple form import simple form is not gonna have any props and in here you're going to not have a text field for ID, no need for that. So you're just gonna 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 dot create. So now if you click on the create right here, is this working?
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. I'm not sure but 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.
CourseCreate method. We added this, we added this, we have an image source, we have a title. We added a create to be course create. Alright, so it's still not working So I have to debug a little bit. Why?
So I'm gonna pause the video and see what's going on. All right, I figured what's going on. In my create method, we should not be using text fields, right? We are using text input here. So text fields are only for representation, right?
For presenting the data, but text inputs are the ones you would use to actually add something new. So let's try this out now. So I'm gonna add, for example, a new country and I'm gonna 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 Let's see what happened. Duplicate key values, unique constraint courses key.
So let me try... 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, tsx.script.reset.ts.
Or you can also use bun if you want to. So let me try this out now. So if I go ahead and run npm run database reset, 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. All right, so now, if I go into my courses here, all right let's just wait for this to refresh. It's kind of slow now. Let's see, why is it slow?
Do I have to reset my database? It's just Next.js. Let's try this again. So I just want to go and log on to this 3000 slash 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 Croatia. Slash hr.svg. Let me click save. And there we go.
Now Croatia is here. Can I now add Spain slash 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 slash fr.svg. So I can click save here. There we go. So if I go ahead and go inside of localhost 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 exactly what we wanted to do. So now let's go ahead and let's create our admin for updating a course.
So let's say I made a mistake in the Croatia 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. And in here we're also going to be able to change the ID. So let's give this a 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. From .slash course edit. The problem is we also have to create our API route so we can delete this course and also edit it.
So go into API courses and create a new folder inside square brackets course ID. Inside of here create a route.ts like that and in here I'm going to go ahead and export const get first. So this is an asynchronous method. In here we're gonna have a request but we're also gonna have params And in this params is the name of our course ID, which we define through a folder name here. So that's how we get params, the same way we get them in the page props.
So params is an object with a course ID inside of here, which is a type of number. We know that because, our ID in schema is a serial. So let's get the data using await database from drizzle.query.courses.findfirst where equals from drizzle ORM courses from database schema.id equals course ID equals equals course ID equals brands course ID. Like that and let's return next response.json data and import next response from next server. And I'm just gonna 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. But I've heard and seen from, you know, some certain blog posts that it's not good to use those large groups to do authentication. You should still do it in every single page and in every single route. Basically everywhere where you access your data, you should also do a check. So that's the only way you can be certain.
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 gonna change this from course ID.
This is going to be... Oh this is still course ID my apologies but instead of doing finding in here we're going to do database.updateCourses.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 and get again is going to be delete so again instead of find first, this one is gonna be database.delete courses.where equals courses.id and params course ID. So let me just collapse this. There we go. Like that.
Perfect. And can I also add returning here? Yes, let's add returning here and make sure you add returning in here because we're returning that data back. 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... Courses, find first course ID. Oh, because I didn't add dot returning in this one. I don't have to.
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. Okay.
How about in here? So we do have the courses, but they don't seem to be triggered. So let's go inside of the app back here and let's debug. So we have edit, 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 Croatia too and click save and I go to localhost 3000 slash courses and leave this site and Ryan refresh this.
Maybe it's just cached. Oh no, I didn't save it. Ratio 2 and save. Element updated. So it's still doing that, I think.
So we do have an error, it looks like. Let's see what was an error. So yeah, we have to wait. 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 let's see what did I miss here so inside of my route here I suppose this is we are using a put here what is data oh I think I have to give it the first in the array again, the same way I have to do it here, yes. Let's refresh now.
So I think this did update it, but it did get that weird error. So let me go to log plus 3000 slash courses here. There we go. Now we have Croatia too. Perfect.
If I try this again, back to Croatia and click save. 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 cache.
Perfect. So we officially have finished the admin dashboard for creating 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 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 gonna 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 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 the replace those 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 courses 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. Prompt.slash unit list.
I'm just going to separate this. And there we go. Now you should have that ready. Let me just change. So the regular presentation 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 API, copy the courses and paste it here, call this units, go inside of route.ts, and in here, you're going to change the import.
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 don't have any units so that is fine.
So then change this from course ID to unit ID. Go 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 courses 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 routes for everything. So now we can go back inside of an admin here, unit, and let's go ahead and let's resolve the create here.
So this is going to be called unit create. It's going to have a title, which is a title, instead of image source is going to have a description and the label of description. 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 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, 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. 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, for example, unit one, learn the basics of French. And I choose a French course and give it an order of one. I click save.
All right. And yeah, I forgot that I have to go back inside of the app and replace course edit with unit edit from dot slash unit edit. There we go. And there we go. So now, you can see how I have my units here and I have a relation with the French course inside and I can directly see which unit and course that is.
Perfect, so now if I go inside of here, for example, if I select French, 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 gonna 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 wanna 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 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 units 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 gonna go inside of admin here and I'm going to copy our last unit.
I'm going to paste it here. I'm gonna rename it lesson. Let's go inside of the lesson list right here. Let's rename this to lesson list. We're going to have the ID, we're gonna have the title.
We're going to have a reference field to unit ID which references the units. I'm not gonna 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. But there we go, so this is working. This is the lesson list.
Now let's go inside of the edit here. Actually, let's first do the create. So this will be lesson create. 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 a name of lessons. Representation title can stay the same because we are still working with title and then lesson list from lessons.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. So this will be for example nouns, right? I can go in 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 1, which is right here. Perfect! Excellent! So now let's go ahead and let's create the challenges one.
So same thing as always. Let's go inside 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 rune, only challenges. So let's do database.query.challenges. And database.insert.challenges.
Ctrl F. 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.
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, 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 gonna have one more. So basically those two enum options from our database, assist. I believe those are the ones. So if I find the type, there we go, challenges enum, rselect 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, 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. This will be a lesson ID.
Lessons. And let's go ahead and let's import. This will be a select input this time. So I'm gonna add a select input. I'm gonna give it a source of type and I'm just gonna copy from the list the choices in here like that.
And I'm going to copy the validate required as well, Phil. 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. 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 man? I'm gonna give it a type of select and the lesson will be nouns and order of this challenge will be one. Perfect, so now I can see my lessons and I can see my challenges which have 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 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 gonna copy the challenge, paste it here, rename this to challenge option, singular. Go 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 gonna have a Boolean field. So let's just replace the select field with boolean field import and the boolean field 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 UUIDs, then it's a string. Let's do 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 gonna make it required, right? You don't have to label this as correct. The reference input is gonna go to the challenge ID and reference the 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 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, go into 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 challenge options like this and let's have a record representation here of text. I think you can also give it like a label. Let me just check for a second.
So you can see how this is called challenge options, right? In the sidebar, it kind of looks bad. So how about I, 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 record representation here, I think it can be text. So let's go ahead and try the challenge options now. Alright, it seems to be having an error. Let's see why. 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 we'll 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, there we go. Let's go ahead and create one. So I'm going to add La Mujer. 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 woman.svg and audio of eswoman.mp3.
I'm going to click save. There we go. I'm gonna go ahead and create a new one. This one is going to be a text of El Hombre, which is the correct option. The same challenge, slash man.svg slash woman slash esman.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 slash esrobot.mp3 and click save and there we go you can now see oh yeah so robot should 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 querying 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 this question. Right here we have audio sources. So the same way we did in our Drizzle, but this time it's in an admin dashboard.
Perfect. So now I can go ahead and try this out. Perfect. So let's go ahead and try this out. If I select robot I should get incorrect.
There we go. If I select the woman, it's incorrect. And if I select the man, I finished the entire exercise. Perfect. Excellent.
So we're done here. And if you want, you can go ahead and explore this React admin dashboard. It's very powerful. You can create it as useful as you want. For example, this challenges, for example, lesson nouns 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?