00:07
So we'll create an action using Node.Ace make action, put this inside of our lessons directory, and we'll call this store lesson. Go ahead and hit enter to create that. We can clear out our terminal and make a validator
00:17
using Node.Ace make validator, and we can call that validator lesson. All right, clear that out once more, and let's jump into our text editor. We'll begin with that validator.
00:26
So we'll jump into our lesson validator file. And just like with our module, this validator is going to be able to be used for both creating and editing. So we'll call this just our lesson validator,
00:34
do vine, and again, we need our metadata for our organization metadata here. And then we can call compile from there,
00:41
passing in a vine object that contains a name of type vine string with a max length of 150 characters.
00:49
We'll also have a publish at field, and that's going to be a type vine date. Now, Vines validator accepts in a number of different formats. We're going to be posting this up as an ISO.
00:59
So we need to tell it that in order for it to be able to properly ingest that as a vanilla JavaScript date. We can do that by passing in a formats option.
01:06
And for that format, we just need to set UTC to true, and that's going to allow us to post up our ISO format. Now, our publish at here is going to be nullable. So the value could be null,
01:16
and it's also going to be optional as well. So it may also be undefined. In addition to that, we also want to transform this from that vanilla JavaScript date
01:24
that vine JS works with back into a Luxon date time. So we use the transformer to take in that vanilla JavaScript date object. We'll do a ternary here to check and see if we have a value.
01:34
If we do, then we can use date time from Luxon from JS dates and provide that value in. If we don't have a value, then we can just return back null.
01:42
Then we can move on to our module ID. This is going to be a type vine number, and we want to use our exists and exists in organization helper
01:52
to verify that this exists in our modules table. Next is our access level ID. Again, that's a vine number, and we need to verify that it exists in our organization
02:02
inside of the access levels table. Then we have our status ID, again, vine number, and the exact same thing there exists, exists in organization within the statuses table.
02:12
And then that should do it. And our squigglies at this point should just be formatting. So if we give it a save, all those should go away. That then takes us into our lessons action for our store lesson.
02:21
For this, we're going to want the organization, that's a type organization, and then the data which we can infer from the type of our lesson validator. Take those out of our handle method,
02:31
and we can return from our organization, reach for its related lessons, and then call the create method, passing in that data from our validation.
02:40
Our validator has that module ID on it, and then this relationship will bind it to the organization as well. So it should be good on our IDs,
02:48
leaving us with just needing to add in our order yet again. So just like we have in the past, we'll add in a new static async find next order method.
02:57
And for this one, let's just take in the module ID. That's going to be a type number. For this, we can return the results using our lesson model
03:05
to query where the module ID is the one that we're inserting this lesson into. We can then go ahead and order by the order field
03:15
in a descending fashion and grab the first. We can also add in a select specifically for that order to limit the data that we get back. Actually, we want to plop this inside of a variable.
03:25
So we'll do last module lesson equals await, and we're plopping that into a variable so that we can return and check whether or not we got a last module lesson back.
03:35
If we did, then we want to increment the last module lesson order by one, otherwise default that to zero. Then for the order on our newly created lesson,
03:43
we just want to await this find next order and pass the data module ID in. All right, we should be able to give that a save
03:51
and jump into our lessons controller to grab our HTTP context and add in an async store method that takes in a request response
04:00
and organization from our HTTP context. Let's do const data equals await request validate using
04:07
and pass it in our lesson validator with our organization metadata, passing that our organization's ID. Then we can await, call our store lesson action
04:17
and its handle method passing in that organization and our validated data. And then we can go ahead and just return response redirect back. Give it a save as that should all be formatting.
04:27
There we go. And let's jump into our web routes. We'll add in a new comment section here for our lessons. And for right now, let's just add in our post.
04:35
So the router post slash lessons, and this will use our lessons controller store method as lessons store.
04:44
All right, and at that point, we should be able to jump down to our front end and start working on that. So let's add in a brand new component, just like we did with our modules called sortable lessons.
04:53
So sortable lessons dot view. And for this file, let's go ahead and use our sortable modules as a lot of what we have here, we will be keeping.
05:02
So let's go ahead and give our whole sortable modules there a copy and paste inside of our sortable lessons. We'll go ahead and work our way up from the bottom. So rather than deleting a module, we're going to be deleting our lesson.
05:12
We won't have a prefix URL for our lessons as its URL structure is a lot more simple than our modules. So we can just switch that to slash lessons.
05:21
We have our module right there. We just switch that to lesson, lesson there as well, and get rid of that comma. Our lessons form not only has a status field,
05:30
but it also has an access level field. So we'll put the access level field before the status, switching this to access level form dot access level ID. We're going to get a red squiggly there
05:40
because we haven't actually added that to our form yet, but we'll do that here momentarily, switch status to level and then switch our key value
05:48
and display to using that level variable. We're still going to have a name field, but in between our name and our access levels, we're also going to want a publish at field
05:58
that allows us to select a date and a time. We'll work on that a little bit later. Let's just get things working with our base form here as a starting point.
06:06
For our form dialogue, we can switch this from series module to lesson for the resource. And then again, our post is going to be just going up
06:13
slash lessons and then slash lessons ID will eventually be our put. Then we have our add lesson button,
06:22
as well as our dropdown menu, which should be able to remain as is. We just need to swap this from passing in the module to passing in our lesson.
06:31
And we'll want to do the exact same thing for our edit here. Our lessons won't have a sub field, so we won't have a count to display for it, but we will want to display the lesson's name
06:41
and also where it resides inside of the module. So for that, let's add in a span prior to the lesson's name with a class text slate 400 flashed zero.
06:51
And let's add in a W with a three character width for that text. Small add in our handle and cursor move so that we can also click to move it via this element.
07:01
We'll have our module order as the prefix dot, and then we'll want to display the lesson order after the dot.
07:08
And that'll end up displaying something like 1.0, 1.1, 1.2, 2.0, so on and so forth as we add modules and lessons to those modules.
07:17
Then we have our sort handle, which can remain as is, and we'll switch our element here to lesson, which should get rid of a number of our red squigglies. The one that remains is our module,
07:27
and that's going to be coming through our props. So we'll get to that here in a second. We also don't need our index for this one. So we can go ahead and remove that. And before we swap out our view model here,
07:36
let's go ahead and add in our module state. So ultimately, that's going to be coming from our props. So we'll start with our props in our script. We still want all three of these prop keys,
07:46
but the model value here is going to be different. Rather than having a module DTO array, this is just going to be a single module DTO. So we're going to pass the whole module in as our prop view model.
07:56
And then where we previously had our computed modules, we can switch this to just a singular module. Now, as we work with our singular module state,
08:04
we'll get that same view model circle that we've discussed prior, and that will also cascade through to our module.lessons as we change that on our sortable item.
08:14
We also want to change the group of the sortable for modules to lessons. And again, this group isolates the sortable for the lessons from the sortable for the modules,
08:24
meaning that we're going to be able to drag lessons between lesson groupings and modules between module groupings. We can't swap a module into a lesson group and vice versa. That's going to keep that isolated for us.
08:34
Now, as for our lessons form state, which we're going to need for our user resource actions, default form value that we pass in here to the curried function, our onCreate and onEdit,
08:44
and we're going to go about this a different approach from how we took our modules. Instead, we're going to create a separate DTO specific to our lessons form so that we can work with a little bit more cleanly.
08:54
The publish at is going to add a little bit of additional work so that we can verify whether or not we have a publish at to deal with, and we also need to get it to the correct formats for our daytime fields.
09:03
So we'll go up to our DTOs, create a new file, and we'll call this lesson underscore form.ts. We can go ahead and export default class, and we'll call this our lesson form DTO.
09:13
We'll declare a module ID so that that is optional of type number. We'll declare an access level ID, optional of type number,
09:23
a status ID, optional of type number. We will declare a name of type string
09:29
and declare a publish at date of type string or null and declare a publish at time type string or null.
09:38
So we're going to be splitting our publish at singular column into two separate properties here in our lesson form DTO, our publish at date and our publish at time, and we'll do that inside of the constructor.
09:48
So we'll take in our lesson of type lesson DTO. If we don't have a lesson, then we can just go ahead and return, allowing for an empty constructor to be used.
09:57
Then we can go ahead and just fill in our primary properties. So we'll have our module ID, which we can populate from our lesson module ID. Let's get our autocomplete going by importing our lesson DTO
10:06
here as well. There we go. I think we're missing our module ID off of our lesson DTO, so we'll get that out of here too. This access level ID equals lesson access level ID,
10:16
and we go ahead and convert that to a string here as we will need it as a string for ShotCNView, and we'll want to do the same there for our status ID as well. So let's go ahead and switch that to a string for our declare type
10:25
for both of those, and then we can do this. Status ID equals lesson status ID to string. Then we have our name, which we can populate from our lesson name,
10:35
and then our publish at date and our publish at time. Now in our lesson DTO, the publish at is still going to be a string. So to make that easier to work with,
10:44
let's first check and see whether or not we have a value, and if we do, then we'll go ahead and plop it into a single publish at variable that is a lux and date time.
10:53
So we'll first check and see if we have a value for it using a ternary. If we do, then we can take date time from, and it's going to be an ISO and pass that lesson publish at in.
11:02
Otherwise, we'll just set that to null. For our publish at date time, we can check and see whether or not we have a publish at. If we do, then we can call up to format, and we're going to use the native browser's inputs
11:12
for both the date and time for this. So the format for that is going to be YYYY-MM-DD. That's going to be a four-digit year, a two-digit month,
11:22
and a two-digit day. Then if we don't have a publish at, we just want to set that to null. We want to do the exact same thing for our time. The only difference is the format is going to be different. Instead of doing a date,
11:32
we want to do an hour-hour-minute-minute format, and I believe the capital HH there is the 24-hour time format. So we don't need to worry about designating an AMP yet.
11:41
Okay, so that's our lesson form DTO. Let's go ahead and command-click into our lesson DTO and get that module ID added in. So let's declare module ID. That's going to be a type number.
11:51
Scroll down to our constructor and add it in there as well. Module ID equals lesson.moduleID. And the relationship for our module is also missing, but I don't believe we need it,
12:01
so we can go ahead and omit that and close that file out. We can close out our lesson form DTO and go back into our lessons, sortable lessons file. Okay, so let's go ahead and now update our resource
12:10
to use our lesson DTO, both on our edit and inside of our use resource actions. So that's lesson DTO for both of those. However, we also have our lesson form DTO
12:19
that's specific to our inertia form type, and that is what we're providing into the curried function on our use resource actions. So if we hover over our use resource actions,
12:29
we're going to see two different types. We're going to see our lesson DTO, and then we also have that form, which is a part of that curried function. So we can specify another type argument
12:38
in between the primary function for our use resource actions and that curried function that we pass the default form state into, designating that form's type as our lesson form DTO.
12:47
And it doesn't look like that type wants to come through, so I'm going to scroll up to the top of my file and add in an import for lesson form DTO
12:55
from hash DTO slash lesson underscore form. Scroll back down, and now we can begin to see red squigglies on our actual form state because it doesn't match the string type
13:04
that we have on our lesson form DTO for the status ID. So for that, we want to add in a to string, and now it's going to complain that the overall type
13:13
doesn't match the required designation of our lesson form DTO. So we need to add those additional properties in. So in addition to our status ID, we have our access level ID.
13:22
That's going to come from our props organization, access levels at that first index, and we can reach for the ID, again, converting that to a string. Then we need to also provide in the module ID,
13:32
and that's going to come from our module computed property. So we can reach for the value and the ID there. And lastly, we have our publish at date, which we can default to null and our publish at time.
13:42
Again, we can default that to null. So now our default form state is happy, but our on edit form state is not. For our on edit, we can do things slightly different
13:51
and make use of our actual lesson form DTO to update the default form state here for us. So we can do new lesson form DTO and directly pass that resource in.
14:00
Our lesson form DTO constructor will now take in our lesson DTO properties and update it and map it to our lesson form DTO properties,
14:08
which will get returned into our open and update our new default form state. We're manually specifying these properties on our create because we have the status and access level IDs
14:16
as well as the module ID that we want to change up slightly because we don't have a default lesson there that we're working with like we do on our edit. All right, so with all of that set,
14:25
we should now be able to jump back down into our components and our sortable modules component. And let's go ahead and scroll on down to the bottom of our LI where we are listing our modules.
14:35
Inside of that LI, we'll add in our sortable lessons component. This will be self-closed. And inside of here, we want to provide in the props.
14:43
So we have our view model, which is going to be a single module. So we want to take our modules array and reach for the specific index that we're at. Remember, view doesn't like us
14:53
passing the direct looped object into the view model. So we need to reach for the original object, which is our modules and pass the indexed item in instead.
15:01
Then we need to also provide in our organization, which is just from our organization value and the same for our course. And that should do it. So we should be able to give that a save.
15:10
Jump back into our browser. And if we did everything right, there we go. Our button is a little askew, but nonetheless, if we click on it, it should open up our add lesson form.
15:19
Type in something like our first lesson. Our selects here are empty, but we still get the values in here. So I'm going to go ahead and select one and add in a new lesson.
15:28
And okay, immediately I can see that should be lessons, not lesson. Let's jump back into Visual Studio Code here, jump back into our sortable lessons,
15:36
and that would be down inside of our form dialogue component. Just switch that from lesson to lessons. For both of those, give that a save, jump back into our browser, close out that dialogue.
15:45
And let's try that one more time. Our first lesson, select something here for our access level, doesn't really matter what, and then add in our lesson. And there we go, we see our first lesson.
15:54
Now, our numbering should have been 1.0 here. We'll take a look at that here in a second. First, let's go ahead and fix our add lesson button. So we'll jump back into Visual Studio Code.
16:02
What we want to do here is wrap our button in a ULLI to match the structure of our actual lessons list. And then for our LI, we'll add in a class PX2,
16:12
margin left three characters to offset it from that 1.0, 1.2 numbering that we have before our lessons name.
16:20
So let's give that a save, jump back into our browser. And there we go. Now our button looks more appropriate. Let's go ahead and make sure that our lessons ordering is working okay.
16:28
Our second lesson, and then we'll take a look at our numbering and why our default form arguments are populating. All right, so we have our 0.0, 0.1 showing up.
16:38
That seems to be working okay. We don't need a border in between our lessons. So we can go ahead and scroll up to our sortable. Let's just change the LI's class altogether.
16:46
So this is going to be flex, flex wrap, items, center, justify, tween, gap of two, rounded MD, PX2,
16:54
PY1.5, other, BG, slate 50, a transition duration of 300, group and relative. While we're in here, let's go ahead and scroll down
17:03
and see if we can spot why our selects aren't getting our default value applied. And yeah, we're casting these two strings whenever we set the value,
17:12
but we have them being passed into the select item as a number. And maybe the select item works with numbers. Okay, we had that set on our module. So let's go ahead and scroll back up
17:20
to where we are setting our default form state here. Where is that? Right here. And let's get rid of our two strings. Now that's going to make our DTO unhappy. So we'll jump into our DTO and let's do the same there.
17:30
So we'll jump into our DTO, get rid of these two strings and then switch those back to numbers. Let's test and see whether or not that works. Okay, so let's jump back into our browser. Let's go ahead and try adding in a new lesson,
17:40
this time to our test module one. And sure enough, these default select values are now working a-okay for that. So awesome. Okay. I suppose select item works okay with numbers.
17:48
My apologies on that mix up. Close that out. And let's also scroll. Let's see, we have our prefix URL. We can clean that up a little bit. Scroll back down because I think we left my cool module.
17:58
We can switch that to lesson for the placeholder on the name. And let's scroll back up to our list a little bit to where we have that first div.
18:07
Let's see, we want to get rid of this div right here, leaving the div that separates the left-hand side and our right-hand side.
18:14
And we'll let our LI stylize those for us. And then on this first div, we want to add a flex one so that it consumes as much of the width as possible
18:24
and max width full. All right, give that a save, jump back into our browser. And that should tighten up our listing here a little bit, keeping our three dots here on the right-hand side. That now leaves us with needing to check out
18:34
our zero ordering on our first module. So I'm going to see if that was just a hiccup here. So we'll do test for a lesson in our second module. Okay, that went to one.
18:44
Let's jump back to our courses, jump over to our test course two, which we have not worked with yet, and try adding in a first module here. Also add in a first lesson. And okay, cool. So this one did start at zero.
18:54
I believe the ordering on our course test one here was because we created this in a REPL session, if I'm remembering correctly, and I don't believe I specified an ordering for that.
19:04
So it started at zero on this one. Okay, I'm going to go ahead and delete that course so that I don't stumble across that again and think we have an issue.
19:12
And we'll work with test course two from here on out. And oh, sorry, one more style difference between our modules and lessons. We need to get rid of the bold title.
19:20
So just on our lesson name, let's go ahead and get rid of font bold, and let's instead switch that text small. So we can give that a save and back to our browser one more time. And there we go. That looks better.