In this chapter we're going to develop the meetings form. So this will include creating the meeting list header, which will use the text MyMeetings and the new meeting button, and also the actual form where we will be able to select an existing agent. Let's start by creating the meetings create procedure. As always ensure that you're on your default branch and that you have no unstaged changes. Now let's go ahead and copy agents server create procedure simply so we speed things up.
So I'm going to copy the entire procedure here and then I'm going to go inside of my meetings server procedures and then at the top here right above the get one I'm going to paste the create procedure. So now let's go ahead and let's actually create the meetings insert schema. So we can also help ourselves by copying the agent schemas and pasting it inside of meetings. Let's rename the agents insert schema to be meetings insert schema here. And it will be a name and agent ID.
And this will be agent is required. And for the meeting update schema, we are going to extend the meetings insert schema with an ID like this. Now we can go back inside of the meeting server procedures and instead of agents insert schema we now have it meetings insert schema from dot dot slash schemas instead of having a created agent we are going to have created meeting in this procedure here and instead of inserting agents we will be inserting into meetings The input can be spread like this and we assign the user ID manually. And simply return the created meeting here. And that's it.
That's all we need for our create procedure for now because there will be just one more thing we're gonna have to do here do create stream call absurd stream users So later when we add video call SDK we're going to have to do that here in the create procedure that's why we are adding a to-do here. Perfect so we can mark this as completed. Now let's build the list header component And we can also help ourselves here by copying the agents UI components agents list header. Let's go ahead and paste that inside of meetings UI create a new folder called components and paste it inside. Instead of agents list header, this will be meetings list header, like this.
And now let's go ahead and do the following. Let's remove the new agent dialog from here. Let's remove agents search filter, and let's remove use agent filters. So we can remove this, and we can remove this, and this. Basically, we're just building the UI now.
So this onClick for now can just be an empty arrow function, and then we're going to easily add it later. So you can also remove these things here as well. To do filters. Remove this, this, and this. Double check that you are doing all of these changes in the meeting list header and not accidentally in the actual agent list header.
And now rename this to meetings list header and this will be my meetings and the button will be new meeting here. Now let's go ahead inside of our app folder dashboard meetings page and let's wrap this hydration boundary inside of a fragment. And let's go ahead and render the meetings list header component like this from the modules. And while we are here, let's just mark this as an asynchronous function. And let's also copy our protection here.
So let's grab the session and let's do a redirect here. So simply add that here in the meeting page. So meetings page, import out from live out and headers from next headers and redirect from next navigation. So you should have all of these added. There we go.
So now our route is protected and when I refresh the meetings it should now render the meeting list header above my JSON data here. So we have this bug here because it's extending our view. So what we can do for now is go inside of the meetings view component here, and just try to do data table here. And you can leave this error to stay like that. There we go.
So now you have a much normal look here. Now I want us to create the new meeting dialog. Let's go ahead and copy from the agents here in the UI components. We have the new agent dialog. Let's copy that and let's put it inside of meetings UI components.
Let's go ahead and rename this from new agent dialogue to new meeting dialogue. Let's go ahead and rename the instances of agent to meeting. So a new meeting dialogue. You can remove the form import from now and in here you can add to do Meeting Form. Change the title to be New Meeting and this will be create a new meeting.
There we go. Now let's go back inside of our meetings list header here and let's actually render the new meeting dialog component from forward slash new meeting dialog because they are in the same components folder in the meetings module and now we have to add back our useState here. IsDialogOpen, set IsDialogOpen from useState from React with a default value of false. And then in here give it an open of IsDialogOpen and an open change of set IsDialogOpen. And in here simply set IsDialogOpen to true.
So now when you click on a new meeting, you will see new meeting pop up and on a mobile, this will be a drawer. There we go. Now we have to implement the meeting form. In order to do that, I also want to add the update procedure already, simply because it's simple to do, and we will able to complete the form both for update and for create status. So let's go ahead inside of modules agents server procedures, find the update procedure and let's go ahead and copy it because it's going to be quite similar.
So now let's go inside of our meetings server procedures and above our create, let's now add our copied update protected procedure. So we can now import the meetings update schema, which we created a moment ago by extending the meetings insert schema here. Instead of updated agent, it will be updated meeting. And we are updating the meetings, which means that in the where clause, we are looking for a matching meeting ID and a matching meetings user ID. If we cannot found, if we were unable to find the updated meeting, that means that meeting does not exist for this specific query.
Maybe the user doesn't have access to it. We're just gonna throw all of that under not found and let's return back updated meeting as simple as that make sure you're using update and set here perfect so now we have the update procedure as well which means that we can now go inside of the agents module UI components and we can now copy the new the agent form. So let's copy the agent form, go inside of meetings UI components, paste it here and let's rename this to meeting form. And now let's go step by step, and let's simply fix these things. So in here, the agent form will be MeetingForm.
So rename this to MeetingForm, and use the props here. Now let's fix the schema and the type. So let's go ahead and copy from agents their types here. Let's space them inside of the meetings module. Make sure you're inside of the ModulusMeetings types here, and in here change this to be MeetingGet1, and in here use MeetingsGet1.
So make sure that you have implemented, inside of our MeetingsRouter, getOneProtected procedure, which is something we did in the Meetings setup chapter alongside getMany. If you haven't, it's a very short procedure and you can implement it now. Great. And now when you hover over Meeting getOne, you will see the exact meeting fields inside. Perfect.
So let's replace this with MeetingGet1 from types and I seem to be having some error here or maybe I just need to try again. There we go. Now it's working. And in here we are using the Meetings Insert Schema. Now let's go ahead and actually append that schema in our form here.
So type of meetings insert schema. So the resolver is using the meetings insert schema. And we now also have to properly add the form here. So the name will be initial values name, and the agent ID will be initial values agent ID. And also use the meetings insert schema right here.
Obviously this will now cause errors because we have not changed the mutations here. So let's start with create agent. It's actually going to be create meeting instead of trpc agents create it's going to be trpc meetings dot create like this and for invalidation, we are going to invalidate meetings.getMany. Like this. And also to do invalidateFreeUsage.
And onError here, toast the message and redirect to upgrade so exactly the same now let's change this to update meeting use the see meetings update and re-invalidate meetings get many. And re-invalidate meetings get one when we update it, like this. And all other to-dos stay exactly the same. Perfect. So now what we have to do is we have to modify the instances of create agent to be create meeting and update agent to be update meeting.
And that also includes right here, update agent and create, update meeting and create meeting. And there we go, no more type errors because everything is perfectly type safe now let's go down here and now we have to fix this form field because inside of here it says to use instructions but that's not true we will not be using instructions So we can remove this form field for now. And we can remove the text area import. So now you have a completely valid form, which you can go ahead and add to the dialog. So let's go back inside of our new meeting dialog here.
And let's go ahead and import meeting form from forward slash meeting form. And let's also add a router from use router from next navigation here like this. And inside of here, let's add the meeting form. And let's go ahead and add onSuccess here to accept an ID, call onOpenChange to false, and do a router push to forward slash meetings and then that meeting that we just created. So we immediately open it up in case the user immediately wants to start it.
And add on open change here. Now we're going to have to do a slight modification instead of the meeting form here. So go inside of meeting form and simply allow onSuccess to accept an optional ID of string, like that. There we go. And then instead of create meeting here, in the onSuccess, you can pass that.
So for example, pass in the data and data.id. And then the onSuccess will now respond with the ID and we can redirect to that meeting ID. You could have also done that here immediately, but since we have a callback, let's use the callback. So now when you refresh here, you will see that you have a new meeting here with the name here, but we also rendered the avatar. So let's quickly fix that.
Let's go inside of new meeting form. Let's find the generated avatar and let's remove it entirely and let's remove the import for it. So this is how it's supposed to look like. Now let's go ahead and let's give this a proper placeholder. So this first input instead of being a math tutor, let's for example call this math consultations.
Like this. Now we need to add one more field to new meeting and that is the option to select an agent. So the way I'm going to do that is by implementing a new component called Command Select. This way, it's going to be a type of select which is responsive on mobile and a model on desktop devices. So let's go ahead and do the following.
Let's import the React node and use state from React. Let's import Chevron's up-down icon from Lucid React. Let's import CN from libutilus and button from components UI button. And let's import the following from components UI command. Command empty, input item list, and responsive dialogue.
Now let's go ahead and create an interface props here. First of all, let's create an array of options. So we are going to accept options, which are going to be an array of objects which accept an ID, value, and children using the React node import from above. Besides the options, we're going to have two functions. OnSelect, which accepts the value, and onSearch, which is an optional function which accepts the value.
We are then going to have an actual value and three optional props here. Placeholder is searchable which is a boolean and class name which is a string. Then let's export const command select here. And inside of the props here, let's assign them. And let's go ahead and destructure all of them here.
Options on Select, on Search, Value, Placeholder, and Class Name. And let's give the placeholder a default value of Select an Option. Now in here, let's create a state, Open and Set Open. Use state with a default value of false. Then let's get the selected option here to be options.find, get the option, and compare if option value is identical to the current value that the user selected.
That's how we will find the selected option. Inside of here, go ahead and open a fragment, return a button. Inside of a button, add a div which is going to check for selected option, question mark, children, or simply render a placeholder if selected option was not found. This div right here, I mean below it, add Chevron's app down icon. For the button itself give it a type of button so it can safely be used inside of a form, variant of outline, class name of CN, default classes will be height nine, justify between, font normal and px of two.
Then we're going to have class name, whatever the user passes. And actually, above that, let's add, if there is no selected option, so make sure you put the question mark before it, in that case, it's going to be text muted foreground, like this. So we are transforming a button to behave like a select. And then what we're going to do outside of the button, we're going to render the command responsive dialogue component. Inside of here, let's add open to be open.
Let's add onOpenChange to be set open. Inside of here, let's add a command input with a placeholder of search and onValue change onSearch. Below that, add a command list. Add a command empty which will render a span no options found and give the span a class name of text muted foreground and text small. Below the command empty let's iterate over all of our options here.
Render the command item here here and inside render option dot children give the command a key of option dot ID and on select call on select with option dot value and set open to false onSelect, call onSelect with option.value, and set open to false. That's it. We will have to do one more thing, but first I want to demonstrate how this currently looks like so we have an easier time understanding. So I want to go back to the meeting form where we added the math consultations input here. And let's go ahead and do the following.
Above all of this, let's add our agents and let's load them. So this time we are using use query because we don't know where we are going to use this model, so we can't guarantee with use suspense. So we're just going to use the good old use query here inside call TRPC agents get many query options set the page size here to be a hundred and set the search here to be const agent search set agent search and const agentSearch setAgentSearch and useState empty string and import useState from React like this. Then also add const open setOpen useState false. And in here add the agent search.
We are basically increasing the page size to 100, and we are allowing the user to search through the agents. So this will be a better user experience than a dropdown select, because this way they can actually filter through all of the agents they might have, and they can also be more specific with the search query ensuring that we load them in this large data set. Perfect. So now that we have this, let's go ahead and let's import the command select component which we have just created and let's also import generated avatar component and now let's go ahead and let's copy our form field for the name and let's paste it below here. Let's change it to be agent ID and change the form label to be agent.
And inside of the form control, you're going to do the following. You will render the command select option. You will pass in the options here. Open parentheses, agents, data, question mark, items, or fallback to an empty array, dot map. Get the individual agent here and immediately return an object.
Add an ID to be agent ID, value to be agent ID, and then children to be a React node, which will be a div with a generated avatar inside, a class name of flex, item as a center, and gap x of 2. Give the generated avatar a seed of agent name, variant of bots neutral, class name of border and size 6 and below that a span rendering the agent name like this And then let's add some more options to command select, which will be onSelect to be field onChange. OnSearch will be setAgentSearch. So basically it's gonna be controlling this state here, which will then re-trigger this call every time it changes. And value field dot value and the placeholder will be select an agent.
So the only thing we have left unused is the open and set open. We're gonna use that in a moment. But now, when you click on this, it should open the command, but looks like we did something incorrect. So let's go back instead of the command select here, and let's see what we did incorrectly. In the button, let's add on click here, set open, set to true.
So this is referring to the internal set open of the command select, not to this is open. So now when you click here, you will see a list of your agents and you will be able to select them. So if you go instead of your agents here, for example, and create a new agent, click Create, go back to your meetings, click New Meeting, you will find it here. And if you search for new, you will be able... Oh, yeah, so right now it's not working because we didn't properly implement the filtering inside of the command, but it's quite easy to do that.
What we actually have to do is we have to turn off the way Command Component filters by itself. So in order to do that, we're going to pass the prop should filter to be the opposite of our onSearch value. So if we have onSearch, in that case, it's going to be false. But if we don't have it, it's going to be true. But we are getting an error for it, which means we have to go inside of the Command Responsive Dialog, inside of Source Components UI command, find function Command Responsive Dialog, and simply add a new prop here, shouldFilter, and set it by default to be true.
Go ahead and map it here. So should filter is an optional Boolean like this. And simply go ahead and in isMobile, add it to this command. So should filter, you can see the prop exists and pass it here, and make sure to also do it in here. There we go.
So now our command select will not use the internal filtering, instead it will use react query search. So when I try this again, and write new, you can see how it loads that new agent, right. So it's actually doing an API call in the background here, or updated that. There we go. When I click here, I can select the agent.
Amazing, amazing job. So now, when I click test here and click create, we should be able to create a new meeting. And I'm redirected to that meeting ID page immediately. So this is my URL right now. There we go.
Perfect. So if you want to, you can enable inside of your meetings view The data JSON, JSON Stringify Data. Let's just give it a class name, overflow-x-scroll, so it doesn't move our content too much. Well, it still moves it. But you can see our items total says two.
If I go ahead and create a new one right here, go back to the meetings, now it says total three. So we are successfully creating new ones. Just make sure to scroll here in case your button is disappearing. And you can now comment it out so you can see it here normally. One more thing we have to add here is a text if the user never created an agent, so they have a quick way to create it And we already have the dialog for that.
So inside of the new meeting form, let's change this to be a bit more precise. So open agent dialog, open new agent dialog, and set open new agent dialog. So we are specific about what this is. And then we're going to go ahead and find our agent ID form field. And after form control, let's add form description here.
And make sure to import this from componentsui4. Inside of here we're going to ask not found what apos your my apologies not found what like this you are looking for You can add a space like this go to new line and render a normal button here create new agent give this normal button a type of button, this is very important, a class name of text primary, hover, underline, and onClick will very simply call setOpenNewAgentDialog to true. Currently, this doesn't control anything, so what we have to do now is we have to actually use it. So let's go ahead inside of here. Let's wrap our entire form in a fragment like this.
Oops, in a fragment. And then here, simply render the new agent dialog and pass it open to be open on open change to be set set open new agent dialog and this will be open new agent dialog. Make sure you import new agent dialog component from modules agents UI components new agent dialog. So now in here you have a quick button to create a new agent here. And for example right now it doesn't exist here so if I go ahead and create new one, click create, it automatically invalidates the get many query so it immediately appears here.
And we can create it. Amazing, amazing job. So I think I've noticed one little bug here, which we're going to do in the next chapter. And that's if I search for something and close it and open it up again, you can see it stays in that weird state. So I'm going to look into that and fix it in the next chapter.
But you just wrapped up the new meeting form. So we can now create new meetings. What we're going to do in The next chapter is actually display them and filter them. Now let's go ahead and let's create, review, and merge the pull request. So this chapter is called 17 meetings form.
So I'm going to create a new branch here. 17 meetings form. Once you're in the new branch, you go inside of your source control, mark all of these changes as staged changes, and add a commit message. 17 meetings form. Let's click commit and let's publish the branch.
Once the branch has been published, let's go ahead and open a pull request here and Let's see what CodeRabbit has to say about our code. And here we have the summary. So we introduced a couple of new features, including a customizable dropdown select component with search capabilities. So that's our new command select component. We added a header section with a new meeting button and a dialogue for creating messages.
My apologies, meetings. We implemented a form for creating and editing meetings with validation and agent selection using the command select component. And of course, a model dialogue for creating a new meetings with navigate on success here. So let's go ahead and go through this sequence diagram. We're not going to go through entire sequence diagram, we're just going to go through this.
What happens when we click on a new meeting? So the user clicks on a new meeting in the MeetingsList header component. After that we open the dialog which renders the form, MeetingForm. And once we submit that form, we create it through our server procedures here and we validate the data. The server procedures return the created meeting and then we fire the onSuccess where we close the dialogue and we redirect the user to the new page which is the meeting ID page.
Perfect. And down here we have some actionable comments here to complete these to-dos which is completely valid but since this is a tutorial I'm not going to do them right away. I will do it when it makes sense so you see why we are doing it. So I will close this. In here it suggests adding the is loading for our command select.
That's something we can consider later. And in here it found a bug which we have to fix. So right now our new meeting dialog does not cancel properly. So I think you can actually try that. If you click new meeting and click cancel, nothing happens.
So that is because inside our new meeting dialog here, We didn't call onOpenChange properly. We need to pass false to it. Like this. So what I'm going to do now is I'm going to do that in the second chapter simply because in case you already merged it, right, so I don't want to cause you any problems, we're just gonna do it in the next chapter in the new branch. But this is a bug found by CodeRabbit.
I have completely missed this. Great job. So let's go ahead and merge this pull request as it is right now and once it's merged let's go ahead back inside of our main branch here and let's click synchronize changes let's click ok and then everything should be fixed Let's click on the source and go inside of the graph and we just merged 17 meetings form. Amazing, amazing job. That marks the end of this chapter and see you in the next one.