00:10
that is active. And so that our edit and delete options for the active organization are separated from the add organization option, let's plop a separator in here as well.
00:20
So drop-down, menu, separator. And just like we're doing with our edit, whenever we click this, we want to reach into our use
00:26
resource actions state, we'll have a specific one for destroying to open up a confirmation dialogue.
00:32
So let's scroll down to our inertia components, right-click new file, and we're going to call this our confirm destroy dialogue.
00:39
We'll have our setup script with a language of TS, and let's start with our props. So we'll do const props equals define props.
00:47
For the type of our props, we're going to have an open boolean, an optional title of
00:52
type string, cancel text that is optional and of type string, action text optional and
00:58
type string, an action href of type string, this is not optional, as well as action data that is optional and of type record string any.
01:08
The open prop is straightforward. That's going to determine whether or not the dialogue is open. Then we'll have a title for the dialogue. We'll have a cancel button, which is for when the user decides that they don't want to delete the item.
01:18
We'll have an action text, which is for when the user confirms the deletion. We'll have an action href that we can bind into the confirmation button to send out a
01:25
request to an API endpoint, and then we can optionally attach data to that href as well so that it goes out with our inertia request.
01:32
Now most cancel confirmations are going to have a baseline set of text, something like cancel and delete. So we can add those in as the default props for our cancel text and action text by wrapping
01:41
our defined props with a with defaults function. This is from Vue.js, and it provides us a way to define in default state values for
01:49
our props while still using the TypeScript definition to discern what our props are. And we just need to provide in what those default values are using the key as the prop
01:57
name and the value as that default value. So for our cancel text, we'll default that to cancel. And for our action text, we'll default that to delete.
02:05
We'll have some emits that go out of here so we can define emits, and this will be updating whether or not the dialogue is open, as well as a confirm event for when the user confirms
02:15
that they want to delete whatever the resource item is. Lastly, we need internal state to be able to easily update the open boolean.
02:22
So we'll do const internal open equals, and we'll use computed for this from Vue, provided an object so that we can have a getter that returns back our prop open flag.
02:32
Oops, sorry, that should be props. There we go. And a set that takes in an updated value and just emits the update open event with that
02:41
new value to update open at the parent level of this component. Again, doing that same circle that we mentioned with our form dialogue.
02:48
We've been using the notion of emit there instead of emit, so let's keep that consistent. Okay, that is it for our script. That leads us now to our template.
02:56
Very first thing that we need to do is add in our alert dialogue component with a Vue
03:00
model bound to the open key using our internal open state, and we'll have an alert dialogue
03:07
content with an alert dialogue header that contains an alert dialogue title. And we want to do V if a title was passed in since that's optional, and then provide
03:16
that title in if one is provided. Then we can also pass in an alert dialogue description there and pass in a slot as its content. Scroll down a little bit here.
03:26
That should do it for our header. From there, we're going to go straight into our footer. So we'll have an alert dialogue footer next, and this contains our alert dialogue cancel
03:35
button with our cancel text as its display value, and ShotCM Vue is automatically going to take care of switching the Vue model for our alert dialogue to false whenever we click
03:45
that button, so we don't need to worry about any event emission there. And then we'll have our alert dialogue action, and we want to handle this event ourselves,
03:53
so we'll do as child to propagate that downward, and here we want to add in two different options. First, we may have a link, and we'll use the link when an action href is passed in to the
04:03
props. When that is passed in, we want to set the href to that action href. Since this is a confirm destroy dialogue, the method of this action is going to be delete,
04:11
and then we can optionally pass in any of the action data if it's provided. We want this link to be a button, so we'll do as button there, and then we'll also preserve
04:20
the scroll position when that's clicked. We can go ahead and end our link and plop our action text as its content, and when an
04:27
action href is not provided, we want to go ahead and just use a button, so that'll be our V else, and we'll set the type of that to a button, set the variant as destructive
04:36
so that it is red, and then handle the at click ourselves, just sending up an emit for that confirmation action, and again, setting our action text as the content there.
04:46
So that's our confirm destroy dialogue. The next thing we want to do is jump into our resource actions composable, and update this to handle state for that dialogue.
04:55
We already have a dialogue that's purposed for creating and updating a resource, so we'll call this dialogue just destroy, and again, this one's going to be a href, and for the
05:04
most part, its state is going to be very similar to our base dialogue. It's just going to have a little bit of a variance to it.
05:11
So we're going to have our is open flag that defaults to false, our resource that defaults to undefined, but we're also going to allow additional data, and this data is specifically
05:19
purposed to house any of that optional information that we may want to post up with the confirmation action of our link, which we're providing in right here.
05:27
So we're just providing a way to specify that whenever we open up the dialogue here. Then we'll have our open method that takes in our resource, and we're always going to
05:36
have a resource to take in here, as we need a resource in order to actually delete it. So that one's not going to be optional, like our update and create dialogue is, but we do
05:44
want to still unwrap href, the type of our resource. And we're also going to accept in data, but this time it's going to be for the stateful
05:51
data that we have housed directly on our destroy state, and this is going to be a type record bring any, and we can set that to an empty object if it is not passed in.
06:00
First, for our destroy value, we want to update the resource to the resource that's being passed in. Then we want to set the data.
06:07
So do destroy value data equals, and we don't want to mutate the actual object that's being passed in here. So we're going to spread it to create a shallow clone of it so that those mutations don't
06:17
affect the data outside of this destroy dialogue. And then lastly, we would just want to set this to open. So destroy value is open and set that a true. Just like we went through with our dialogue, we're going to get it right squiggly here
06:27
because resource is initially undefined and it's not going to accept in or unwrapped ref resource as it's not set up to accept that type.
06:35
So we need to specify a type for our ref, and we can't use dialogue because we have a little bit of a variance between these two types. So let's scroll up and create a new interface called destroy.
06:45
Now both of these have an is open and an optional resource. So we can go ahead and cut that out of our dialogue and move that into a shared interface
06:53
that we can just call something like actionable and paste that in. Then we can have our dialogue and destroy extend that actionable interface to get that
07:01
is optional and resource added in. So extends for both of these and set that to actionable.
07:07
For our destroy, we also need to add in that data, which is a record where the key is a type string and the value could be anything. And then our open methods are slightly different for both of these.
07:17
So we'll leave those outside of our actionable. So for our destroy, we'll accept in that resource of type unwrap ref and set that to a resource there. And then for our destroy, that's not going to be optional.
07:27
And then we also want to accept in data that is optional and set the type there to record string any. And then this won't return anything, so we can set the return type there to void.
07:36
All right, let's scroll back down now to our destroy and set our ref type to that destroy interface that we've created. Then let's scroll down a little bit more and let's return destroy from this composable.
07:46
Now we're ready to jump back into our organization select. We can scroll up to where we are creating this composable instance and let's extract out our destroy ref.
07:55
We want to call this whenever we click on our dropdown menu item or deleting the active organization.
08:02
So we'll do at click, call our destroy, open and pass the active organization in as the resource. For our organization, we don't need to pass in any additional data that will come in handy
08:12
for our difficulties, access levels, and statuses, which now leaves us with just needing to add in our confirm destroy dialogue, which we can do underneath our form dialogue.
08:20
So we'll do confirm destroy dialogue. Whenever we click on our destroy dropdown menu button, that's going to set the resource
08:27
of our destroy state to the organization that we passed in. So for our text, we have full access to it whenever our confirm destroy dialogue is open.
08:36
However, when our confirm destroy dialogue is closed, that resource state will be set to undefined. So we do need to use optional chaining here whenever we reference that resource inside
08:46
of this dialogue. So for our text, we could say something like, are you sure you'd like to delete your, and
08:52
then let's put the actual text of this inside of a strong and use our destroy.resource. Use optional chaining there to reference the name of the resource being deleted, and then
09:02
add in a question mark for organization. We can also warn all data associated with this organization, including courses and lessons
09:12
will be deleted forever to warn them that there's no going back from this destroy. For the actual props passed into the confirm destroy dialogue, we're going to want our
09:21
view model for the open flag set to our destroy is open flag. We can set the title to delete organization with a question mark.
09:29
And then when confirmed, we can have the action href go out to a new endpoint that we'll create
09:34
at slash organizations slash, and then use that destroy resource and then optional chaining there to reach for the resources ID. That should do it on our client side.
09:44
Now we need to actually go out and create this endpoint to delete our organization. So we can start by jumping into our routes web and we'll add in a new one for router.delete
09:54
slash organizations, and then taking the ID as the params. We use our organization controller once again, and use the destroy action from that.
10:03
So the name of this to as organizations destroy. Now, while we're here on our routes, it's important to note that for all of our organization
10:10
based routes, we're not making use of our organization middleware. And that's for a specific purpose as these are in charge of either creating a new organization,
10:19
editing an organization via its specific ID, or even deleting an organization by its ID. So we want to work specifically with either that ID or the newly created organization for those applicable routes.
10:29
So we don't want to be taking in an organization from our HTTP context for these routes in particular, which is why we're omitting that for these routes.
10:36
Okay, let's go ahead and now jump into our organizations controller and scroll down to that destroy method. In addition to our params, we're also going to want to take in the response as well as off.
10:46
And the very first thing that we're going to want to do is jump into our terminal and make a new action. So node ace make action, put this inside of our organizations folder, and this is going
10:54
to be for destroying our organization. So we'll call this destroy organization, clear a terminal out, jump back into our text editor and make use of this action.
11:03
So we'll await destroy organization, call the handle method, and we'll want to pass in our user from off user and assert that as this route is off protected, and then pass
11:12
in the ID from our params ID. Actually, we can also go ahead and just take in our session so that we can do a session
11:20
lash and say something like your and then we can get back our organization. So const organization, this will be the organization that was deleted, we can say your organization
11:29
dot name has been deleted with a success message there. Presently, our destroy organization isn't returning anything.
11:37
So organization here doesn't actually have a type at the moment, it's just void. So we're going to get a red squiggly, but that will be fixed once we fill out our destroy organization action.
11:44
Lastly, we're going to want to go ahead and return response redirect. And again, this will eventually be our courses index page.
11:51
But for now, we'll go ahead and just use our home path for this purpose. With the way that we've set up our organization middleware, which is going to be used on our
11:59
welcome page, course page and every other page except for these organization ones, that middleware will be in charge of picking up that the active organization for the user no longer exists
12:08
as it won't be returned back from the query, and it will find a new active organization for the user automatically. So we don't need to worry about setting that in our destroy method here, the organization
12:17
middleware will take care of all of that for us. So we're ready to go ahead and jump into our destroy organization action, where we need
12:23
to specify that this takes in our user of type user from our user model, and then an ID of type number, extract that out of our handle. So user and ID.
12:33
And our first task is to get the organization for the user via the ID that's passed in.
12:38
So we'll await user.related organizations.query where organizations.id is the ID passed in and then find the first or fail.
12:48
As if we can't find an organization, then we don't have anything to delete. And that is unintended for this action. Once we have that organization, we just need to await organization and call the delete
12:58
method as everything with a foreign key bound to our organization here, we'll have that deletion cascaded to them.
13:04
Then we can go ahead and just return the organization in case it's needed wherever this action is being called, which in our case, it is via our flash message.
13:13
We should now be good to jump back into our browser. And that does look a little off, doesn't it? I'll tell you what, I bet you that needs a brand new component that we don't have installed.
13:23
Let's go take a look at our confirm destroy dialogue here. And sure enough, this one is an alert dialogue, which is separate from what we're using for our form dialogue, which is just a dialogue.
13:32
So we do need to install this alert dialogue component, which comes with those action based footer buttons along with it.
13:40
So command shift P, let's search for shodcmview add new component and ensure that we have that installed. Okay. Now we should be good.
13:48
Since that's installed, we can close out these terminal windows. Let's try jumping back into our browser, give it a refresh, and it still looks like it's the same.
13:56
Let's try jumping into our browser, make a very small change, give it a save so that everything just kind of recompiles there. Let's try refreshing one more time. Ah, there we go. Okay. So what happened there is we need to make a very small change inside of our file so
14:06
that the automatic component imports could pick up that brand new component that was installed as it didn't automatically happen whenever we installed that component.
14:14
So now that I picked that up, we should be able to jump into our dropdown here, delete my first organization and voila. There's our confirmation dialogue, delete organization.
14:22
Are you sure you'd like to delete your, my first organization organization, and then our warning that all data is going to be deleted with it. If we hit cancel, it won't be deleted.
14:30
And let's go ahead and switch to one of these that are very weirdly named. So like testing use resource for actions there, go into delete for that one. And you'll see that that got picked up here accordingly.
14:39
If we go ahead and hit delete, it redirected us to our create page, which should not have happened because we have plenty of organizations here ready to go. So let's try refreshing again.
14:49
Yeah, same thing happened. So let's jump into our text editor once more. And this is going to be inside of our middleware for our organization middleware, uh, specifically
14:58
it should be this get active organization. Let's jump into the handle there. Essentially what's happening is we have this first or fail, and we also have this if for
15:06
an active ID right now, we do have an active ID so that if for the active ID is being applied.
15:13
So our query is being limited to that one specific organization, which has now been deleted. So our first or fail runs, it runs into a 404 there, which emits out to our organization
15:23
middleware, which gets caught and redirects us to our organization's create page, which we're seeing in our browser. We have two options to remedy this.
15:31
First is we could update our get active organization to have a fallback. If the active ID is not found to go ahead and just check for any organization that the
15:38
user has, or alternatively inside of our organization's controller, inside of our destroy method, we could clear out that active organization cookie.
15:47
Now we're going to be adding team support later onto this, which means that we could have another user delete somebody else's active organization, which means updating our destroy
15:56
method inside of our organization controller is not an applicable approach for our use case. Instead, we want our get active organization method to automatically be able to find a
16:04
new active organization for the user if they have any other organizations bound to them. So we'll leave our destroy method here as is, jump back into our get active organization
16:13
method and mutate this here slightly. Let's go ahead and extract this query out into a separate method.
16:19
So we'll just grab this CTX auth use web, user related organizations dot query, and move that into a private method called query, and we'll just return the result of that.
16:29
We don't want to make this query method async, as that would mean that we would need to await it wherever we call it, and we don't want to await it there, instead we want to build off of it.
16:37
So by returning back the query builder, we can add any methods that we need to it just by calling this in our private query method.
16:44
For example, if I hit dot here to see the autocomplete, scroll down, you'll start to see that we have these query builder methods available to us as query simply returns back
16:54
our many-to-many query builder contract for our organization. We no longer want to call first or fail here, but rather just first so that we can provide
17:02
in a fallback query if the active ID here cannot be found for the user. And then we'll switch this const for our organization to a let so that we can override it if this
17:11
query cannot find the active ID. So we'll add in an if not organization that overrides the organization using that same
17:19
query, except this time omitting the if for our active ID and just calling first or fail. Plenty of different ways that you could write this, you can write this however you want to do it.
17:29
The gist of it is if we have an active ID, we want to try and get that first for the user. And if we don't have an active ID, we want to try and get any other organization for the user to serve in its stead.
17:39
And then this line down here will update the active organization if it's been changed at all from this active ID. All right. I think this red squiggly is just from formatting, so I'm going to go ahead and give it a save.
17:49
All right, cool. With that small change, we should be able to jump back into our browser and just try to jump into our homepage here. Voila. There we go.
17:56
Now, my first organization is our active organization, and we can freely change it to any other organization from there. Let's jump over to our testing, testing 1, 2, 3 now, try deleting this one.
18:04
So we'll hit delete there, and you'll see that flow now worked seamlessly for us as our middleware was able to find and update our active organization.
18:13
And now if any other user from our team removes an organization that has been our active organization, we'll automatically have our active organization updated rather than being directed to our
18:22
organization create page as we saw earlier.