In this chapter, we're going to implement update and delete functionality for an individual agent. This will include adding agents.remove procedure, agents.update procedure, and the actual edit and delete UI. So let's start with adding these procedures. As always ensure that you're on your default branch and feel free to synchronize the changes to ensure that you're up to date. After that let's head inside of our modules, agents, server, and inside of the procedures.
And let's implement the remove procedure. So this will be a protected procedure, and It's going to have a input of z.object and simply accept id, which is a type of string. Add a mutation, destructure the context and the input. Let's go ahead and get the removed agent here by using await database dot delete agents where dot where and go ahead and add and the first one will match the agent's ID for the input ID and the second one will confirm that the agent's user ID is the currently logged in user so that only we can remove this user and no one else. Go ahead and add returning here.
If there is no removed agent here, throw new trpc error code not found with a message agent not found. Otherwise, return the removed agent back to the client. Perfect. Now let's go ahead inside of our modules agent schemas and in here let's export const agents update schema and let's reuse the agents insert schema and simply extend it by also requiring an ID give it a minimum length and a message ID is required. Like this.
Now let's go back inside of our procedures here, and let's implement the update protected procedure. So the update protected procedure will have an input of agents update schema. Make sure to add this import. And in here add a mutation, asynchronous, the structure, the context, and the input here. Let me just move this comma to this place.
And you can obtain the updated agent here by using await, database, update agents set everything in the input where and and you can just copy the same logic as here so the matching agent ID with the matching author and make sure to put returning here if there is no updated agent you can throw the same error as here agent not found and then return updated agent and just like that we have implemented our procedures let's go ahead and mark them as complete now let's go ahead and implement the delete ui as it is a bit simpler So what I want to do is I want to go inside of an individual agent here and I want to go inside of the agent id view. So inside of modules agents ui views agent id view. And in here let's go ahead and prepare some things let's set the router to be used router from next navigation and let's set query client to be used query client from 10 stack react query so I'm just going to move this here there we go make sure you have added use query client and use router from next navigation and then what we are going to do is we're going to add a remove agent method by using use mutation So make sure you have imported useMutation from 10-stack React query.
Inside of the useMutation, go ahead and pass trpc.agents.remove, and open up mutation options. Inside of the mutation options here, oh, and let's just see, looks like I have accidentally been doing this. Let me just fix this. Use mutation, use query client, use suspense query, okay. I think that's all I need, yes.
Perfect, so let's go back instead of the remove agent here, and let's add on success here to do the following. Let's use QueryClient.InvalidateQueries and the first thing we want to invalidate is our GetMany and add QueryOptions here and pass in an empty object inside. Then I want to invalidate something that we don't yet have so I'm just gonna add a comment to do invalidate pre-keyer usage. And after we've done that, let's do router.push forward slash agents like this. And you can mark this as async and then you can mark this as a wait so it will push after it invalidates the query and on error here obtain the error and do a toast which you can import from sonar so move it here toast dot error error dot message and just like that you have implemented the remove agent method so if you want to technically you could already call on remove here So you could do remove agent mutate and pass in the IG to be agent ID.
So for example, if you were to do this, delete, and try one more time because I have two of them with the same name. There we go. So there it's definitely working, right? The problem is there is no confirmation, right? The moment you hit delete, it immediately deletes.
So if you are fine with that, no problem. But I want to go a step further and I want to add the confirmation message. So let's go ahead inside of source hooks and create use-confirm.tsx use-confirm.tsx It's important to be tsx. Import jsx and useState from React. Import button from components.ui.button and import responsiveDialog.
Let's export const use-confirm. And let's define the props here. We will accept a title, which is a type of string, and a description, which is a type of string. And we are going to return the following. A JSX.Element and a promise unknown.
Let's go ahead and return that now. First of all, let's set the promise and setPromise to be useState here. And let's set it to null by default. Let's give the useState a following value. Open an object and write resolve, a function which accepts the value, which is a boolean, and returns a void.
So it can either be that instead of an object or it can be null. There we go. So that's our promise state. Now let's add the confirm method. In here, very simply return new promise.
Grab the resolve from here and set promise to resolve, like this. Then do const handle close. In here, set promise to be null. Create a const handle confirm. In here, check if the promise exists and resolve it with true and do a handle close.
And finally, let's do const handle cancel, which is an explicit no. So let's resolve this, whoops, with false, and handleClose. So we are basically creating a system where the user can answer yes or no for a specific action. And then a developer will be able to await the result from this hook and get either true or false. And depending on that, we can call the delete method or we can close the model for example.
So it's going to be a highly reusable hook for asking the user for confirmation. And let's now do a const confirmation dialog here and let's actually render this. So responsive dialogue like this. Let's pass in the open to be promise, not null on open change to be handle close. Title will be title, description will be description.
Inside of it, open a div with a class name, padding top 4, full width flex, flex column, flex column reverse, gap y of 2, lg flex row. My My apologies, this is not flex-col. Remove this. So just flex-column-reverse, but on large devices it's going to be flex-row, gap-x2, items, center, and justify-end. Inside of here, add a button, cancel, and below it, confirm.
For the cancel button give it an on click of handle cancel, variant of outline, class name of full width on lg width auto. In here let's copy these attributes and paste them here, change the onClick to be handle confirm, remove the variant, and just leave the class name. And finally, return the confirm dialog and the confirm method in an array. And you should have no errors here because we correctly matched all the types here. So let's finally see how this will be used.
Agent ID view. Let's go back here. And let's go ahead now and do the following. So after remove agent, let's do const use confirm. From hooks use confirm.
And let's go ahead and give the first prop, are you sure? And let's go ahead and do description. The following action will remove, and let's do data.meetingCount associated meetings. So when you remove an agent, we're also going to remove all meetings associated with that agent. So we want to let the user know that.
In here, grab the remove confirmation and confirm remove method. And then create const handle remove agent method grab it make it an asynchronous method and then you will get the okay from await confirm remove and this confirm remove will open the remove confirmation and only if the user pressed ok are we going to continue forward so if not ok break the method otherwise call a wait remove agent mutate async and passing ID agent ID and then go ahead and put this here and wrap the entire thing inside of square the entire thing inside of a fragment and render the remove confirmation here and you should have no errors now and now let's go ahead and click on an agent and let's try and remove it only if you click confirm and you will see it says five associated meetings because that's what we currently mock. So if I click cancel nothing happens but if I click confirm it deletes the agent exactly what we wanted. Now let's implement the update agent dialog. So let's go inside of our agent UI components and let's copy the new agent dialog and let's paste it here and let's rename it update-agent-dialog.
Before you edit it just double check that you are inside of it, that you're not accidentally inside of new agent dialog. Close that and go inside of update-agent-dialog. The good news is we made our agent form editable. So it will also edit as much as it will create for the first time. The problem is we don't have the update agent mutate added here.
So that's the only thing we're going to have to modify. So now go back to the UpdateAgent dialog and let's rename this from NewAgentDialog to UpdateAgentDialog. Change the title to EditAgent and this will be Edit the Agent Details, like this. And the only difference here is that we're gonna have initial values here and that will be a type of agent get one from the types and it will be required right You cannot edit these if you can't load the initial values here. So go ahead and pass the initial values which are optional for the agent form because it can be used both with or without them.
So let's pass them here. Now let's go inside of the agent form. And let's copy the create agent here. And let's paste it here. Let's rename it update agent.
And let's use the RPC agents dot update and let's see what we have to do on success here so we have to invalidate queries get many that's true and then we have to invalidate the get one because we just updated that individual one and in here this is also true so if the error code is forbidden we redirect to upgrade I'm going to leave this purposely on to do because if we implement it now, sure, it's going to work. But I want us to wait until we actually implement upgrades so it all makes more sense when it happens, right? Great, so that's our update agent. And now that I look at it, Our Create Agent actually does not need to invalidate GetOne because at this point, it will never exist in the cache. So our Create Agent doesn't need to do that.
But let's add a to do, InvalidateFreeTierUsage in the future when we have it. We don't have it yet, but we're gonna have to do this for the free tier TRPC route. We now have the update agent, which means that we can now go inside of isPending and add it here. So update agent is pending. And in here instead of a console log, we can now do update agent dot mutate here, spread the values and also add ID from the initial values dot ID.
There we go. And I think that's all we need, right? Because I think this will already show update. So now we have to go back to the agent ID view here, and we have to add a state. So let's import use state from react great and let's just add this field here update agent dialog open and set update agent dialog open from use state with the default of false And what we're going to do now is simply below the remove confirmation let's add the update agent dialog.
So make sure to import it. And now let's go ahead and just pass some props to it here. So that's going to be open, update agent dialog open. On open change, set update agent dialog open. And the initial values will be the data.
And there we go. Now on edit, modify this in the header to be set update agent dialog open to be true. And let's try it out. So make sure you have at least one agent. This one is called the new agent.
I will click edit here and there we go. You can see how it out of filled all information and it says edit and it says update. So a updated agent one, two, three. Let's try this. Let's click update and the moment of truth.
There we go. Seems to be working just fine and this is invalidated as well amazing we have officially wrapped up our agents entity the only thing left is to properly count the meetings we're going to do that after we add the meetings schema amazing so let's go ahead and see what we have to do we added edit ui and delete ui and now let's merge this pull request so I'm going to go inside of my graph as usual. I'm going to click on my branch. I will create a new branch. 15 agent update and delete.
So 15 agent update delete. Once I'm on the new branch, I will click plus to stage all the changes and I will write a commit message. And let's click commit and publish branch. There we go. And then let's go to GitHub.
Let's open a pull request here and let's see what our reviewer has to say. So let's see what we've done in this pull request. We added the ability to edit and remove agents, including the edit agent dialog and confirmation prompts before removal. We introduced a reusable confirmation dialog for asynchronous user confirmations. This was exactly the goal with the use confirm hook.
We had some improvements like enhancing the agent form to handle both creation and updating of the agents with improved success and error handling. Perfect, in here we have a more in-depth walkthrough. And in here we have a sequence diagram explaining our editing dialogues and our confirmation removal dialogue right here, in case you are interested. And in here, we also have a related PR agent page from our chapter 14. And we do have some comments here.
So this one recommends checking if the promise exists and resolving it. But we don't have to do that because we do that using a different method. We do it using onCancel. So Close by itself should not resolve the promise. That's why it's okay to do it this way.
So I'm just going to resolve this. And in here, it simply tells us to track and resolve those to-dos in a future pull request and we definitely will. Perfect! So that's it for the comments and let's merge this pull request. And after you've merged the pull request you can go back to your main branch, whatever is your default branch, and synchronize the changes.
And as always, go inside of your source control, graph, and confirm that you just merged that. And here we have our useConfirm hook. Perfect! That wraps up this chapter. Amazing job and see you in the next one.