In this chapter we're going to implement the individual agent page. This will include modifying the existing agents get one procedure, creating the agent ID server component page, adding a data table redirect on click, creating the agent ID client view. What we are not going to do is implement the actual edit and delete functionality. We are just going to prepare the UI for that. So let's start by modifying agents get one procedure.
As always ensure that you are on your default branch and feel free to synchronize changes before beginning, just so you are sure that everything is up to date. After that, let's go inside of our modules, agents, server, procedures. And let's go inside of get one protected procedure here. We can leave the input as it is. The only thing that we are going to modify is the following.
In the where we are going to add and we already have and imported from drizzle ORM. Let me just go back to my get one here. And let me collapse this like this, so it's easier to keep track of. Inside of this end query, the first one will be to make sure that we are loading the agent for the ID that we pass in the input. But the second one will be to ensure that the agent's user ID is the same one as context.
Whoops, we need to extract context from here because this is now a protected procedure. So make sure you change this to protected procedure if you haven't. Context.auth.userId. So besides loading by the agent ID, we are also going to confirm that this user has access to see that user ID. And the way we do that is by checking if that user is the author, the creator of that agent.
And then what we're going to do is if there is no existing agent, let's throw new trpc error, which you can import from at trpc forward slash server and in here return a code not found and a message agent not found like that there we go and you can leave the meeting count to be a dummy number still we are later going to change it now that we have that let's go ahead and let's implement the agent id page so we're gonna go inside of app folder dashboard agents and create a new folder instead of square brackets agent id Be mindful of the capitalization here because the way you write the variable inside will be the way you will access it. So if you put a lowercase I you will need to use a lowercase I in the code. If you put an uppercase I you will have to use the uppercase I so just be mindful let's create a page.tsx inside of here and let's go ahead and create an interface props and the params will be a type of promise and inside agent id which is a string So this is what I was talking about.
It needs to be agent ID because we named this folder agent ID instead of square brackets, right? So then this will look like localhost 3000 forward slash agents, and then one, two, three. And then one, two, three will be the agent ID. So it's basically kind of an unhard coded part of the URL. So dynamic part of the URL.
That's the word I was looking for. Let's go ahead and do a default export here for the page. Let's add the props here. Let's get the params. Let's turn this into an asynchronous method.
And let's go ahead and destructure the agent ID await params. There we go. Now let's go ahead and do the prefetching. So query client is get query client from TRPC server, add TRPC from here as well, and then just do void the query client, prefetch query like that, and inside add TRPC agents, get one, query options, and inside id pass the agent id there we go we have now prefetched this agent now inside of here let's render our hydration boundary which you can import from 10 stack react query and also add dehydrate from here. Passing the state here to be dehydrate query client.
Just like that. Now inside of here, go ahead and add suspense from react and error boundary from react error boundary. So make sure to add these two. And let me just properly align these. For both of this, give them a fallback.
And for now, you can just write a paragraph loading error. And then in here, render the agent ID view. And pass in the agent ID prop to be agent ID. Like this. Now let's go ahead inside of our modules, agents, UI, views, and create the new agent-id-view.tsx.
In here, create a props, accepting already destructured agent ID. So in here, it's a promise in the params, but in here we are passing it directly. So we have it like this. Let's export const agent ID view here. Let's use the props here.
And let's get the agent ID. And inside of here, let's prepare the DRPC from useDRPC. And let's prepare, I think that's it for now. So let's just leave it like this. And let's get our data using useSuspenseQuery from 10-stack-react-query here.
And pass in DRPC agents get1 query options using use suspense query from 10 stack react query here and passing PRPC agents get one query options and passing the agent ID as the ID like this. There we go. So now our useSuspenseQuery matches our hydration because this is the only prop we are sending. Perfect. Now that we have this, we can go ahead and render out some information here.
So inside of here, return a div with a class name of flex1, py of 4, px of 4, md px of 8, flex, flex column, and gap y of 4. And you can just JSON stringify data, null, and 2. So now, make sure to mark this as use client as well. Go inside of the page and render the agent ID view from modules. There we go.
So now you still don't really have a way of accessing this because clicking on this does nothing. So we created the agent ID page and now we have to add a data table redirect on the click here. So Let's go ahead inside of our modules, agents, agents view. Go ahead and add the router from use router from next navigation here. And then in the data table, besides having data and besides having the columns, also add on row click here.
You will get the access to row information and do router.push here, open backticks, slash agents, and then row ID. So now when you click on an individual agent, let's just do a hard refresh and click here, you will get redirected to the following URL. Agents and then the agent ID. So I can double check that. Let me just refresh my neon tables here.
If I go inside of my agents here, let's just wait for them to load. This is the agent that I clicked on, F9VIZ. And I can see that's exactly what's inside of my URL. So your URL should look exactly like this because that is the exact structure we have defined in here. Agents, agent ID, right?
So agents, agent ID. And in here we have the JSON information about our agent now let's go ahead and let's build the proper loading and error states So let's go back inside of our agents module, UI, agent ID view, and we can actually borrow the agents view states here. So copy these two exports, the loading and the error, go instead of agent ID view here, paste them and let's rename this. So this is agent ID view loading and this is agent ID view error. Import the loading state from components loading state and import the error state from components error state.
This will be loading agent and this will be error loading individual agent like this. Make sure you've imported the loading and the error state. Now that you have these two, you can go inside of your page here and we've added these placeholders so now let's add the actual agent ID view loading and in here let's add agent ID view error components so all of them from the same import. The agent ID view, agent ID view error, and the loading state. So now when you do a refresh here, you will see how it looks like.
And if you try and change the ID to something gibberish, you will get the proper error state. You might run into that infinite loading error, but it's going to resolve itself in a few seconds and it will show error loading agent. Perfect. Oops, let's go to the agents here And just select some proper agent now so you can see the data. Now let's go back instead of the AgentIdView component, so instead of our Modules, Agents, UIView, AgentIdView, the client component here, where we stringify the data.
And instead, let's render the Agent ID view header component. Let's go ahead and pass in the Agent ID here to be Agent ID. Let's pass in the agent name to be data.name. And let's pass in onEdit here to be an empty arrow function and onRemove here to be an empty arrow function as well. Now let's go ahead inside of the components and let's create AgentIdViewHeader.dsx.
There we go. And inside of here, let's prepare our props. AgentId, AgentName, OnEdit, and OnRemove. Now let's go ahead and export const agent ID view header. Let's go ahead and destructure these props here.
So that's agent ID, agent name on edit and on remove. Inside of here we are going to return a div with a class name flex items center and justify between. And now inside of here we're going to add the breadcrumbs. So this is a component that we already have. So you can go ahead here and import all of these from components UI breadcrumb.
The breadcrumb itself, item, link, list, and the separator. So this is a chat-cn component and it exists inside of source components UI breadcrumb. Now let's go ahead and use it to render out our header. And we can actually import agent ID view header here simply so you can see what you're doing. Right.
So right now nothing is visible. And now we're slowly going to add some more information here. So open up the breadcrumb inside, open up the breadcrumb list, inside of it a breadcrumb item, and finally the breadcrumb link. Give the link as child prop and a class name font medium and text extra-large Inside use an actual link from next link Like this Give this an href of forward slash agents and in here write my agents like this. So now you have a text which says my agents and when you click on it, you are redirected back to the agents page.
So you can see when you click on something, you will have this left. Perfect. Now let's go ahead outside of this breadcrumb item and let's add a breadcrumb separator here and let's add a Chevron right icon. Let's go ahead and give this a class name. I mean the breadcrumb separator.
Let's give it a class name of text foreground, text extra large font medium and target the SVG using this method here. SVG size 4 like this. Make sure you have imported the Chevron right icon from lucid react. Then after the separator here, you can copy the breadcrumb item like this and the link inside and change this to have a text of foreground, like that. And the href will be open curly brackets, replace these with backpicks, and you will go to agent's agent ID.
And inside you will render the agent name like this so when you click on it you are already in here but it's I think it's semantically correct to do it this way right So you can now go back to your agents and you can see the name of the current agent you are on. There we go, perfect. So now let's go ahead and import our dropdown menu. From components UI dropdown menu, import dropdown menu, trigger, drop down menu item, and drop down menu content. And also import button from components UI button.
Also import trash icon and pencil icon and more vertical icon, all from Lucid React. Now let's go ahead. After our breadcrumb ends let's open a drop-down menu and this is important give it a model false. Without model false, the dialog that this drop-down opens causes the website to get stuck, or maybe precisely unclickable. So that's why we need model false because our drop-down menu will open models.
The edit model and the delete module. So that's why we have to not do double model otherwise the website will be in this weird hanging state. So let's add drop down menu trigger here. Button. Mark this as child.
Give this a variant of ghost. Inside more vertical icon. And outside of the trigger, add a content, give it an align of end, inside drop down menu item, on click, on edit and a pencil icon with a class time of size or and text black and the text of edit duplicate this change the on click to be on remove this will be delete and trash icon So the reason I'm typing text delete but using remove keyword is simply because delete is a reserved syntax in JavaScript. So that's why I'm using remove internally, but delete on the user side. So now in here, as you can see, you have a nice drop down for editing and deleting.
Currently they do nothing, we're going to implement that later. Perfect. Now we can go back inside of the Agent ID view here and below the header open up a new div with a class name background-white, rounded-large and border. Inside of here open a new div with a class name px4 py5 gap y of y flex flex column and call span of 5 Inside of here a div with a class name, flex-items-center and gap-axe of 3. Let's render our generated avatar component, which is a self-closing tag.
Make sure you have imported it from components generated avatar. Go ahead and give it a variant of bots neutral and a seed of data name and a class name of size 10. Below it an h2 element rendering the data name. Give the h2 element a class name text to Excel and font medium like this and outside of this div render a badge from components UI badge so make sure you import it from here and not from Lucid React because sometimes IntelliSense imports it from there. Give this batch here a variant of outline and the class name of FlexItemCenterGapX2.
Target the SVG and give it the size of 4 inside. Render the video icon. Make sure you have it from Lucid React. And go ahead and render data meeting count. So inside of our procedures, get one, we added meeting count in the select with a mock number five.
So later we're going to change it to the actual meeting count, but I'm just showing you this in case you don't have it. You need to add it so that you can access it here in the Agent ID view. So now that you have the meeting count here, go ahead and check if data meeting count is equal to one, render a meeting, otherwise meetings, like this. There we go. Outside of this, render a div with a class name, flex, flex column, get y of 4, and two paragraphs.
First one with the label instructions, and the bottom one with the actual data instructions. Give this one a class name of TextLarge and FontMedium. And this one a class name of TextNeutral800. And there we go. Now let's just give this video icon here a class name of TextBlue700 Perfect So this is our agent view You can see that it matches exactly what we see here, but in a different layout.
And now in the next chapter, we will be able to edit it and to delete it. Great, amazing. I think that's all we wanted to do here. Let's just see this error that I'm having here. It looks like I'm not using more vertical.
That's right, because I'm using the more vertical icon. Okay, so basically, I added an invalid import here. Perfect. So I think that's it for this chapter. Everything seems to be working just fine.
And obviously this is not in sync now. This shows 6 and this shows 5. So I'm going to change that later when we actually do the proper calculation. So we've added this and we've added this and now let's merge this. So I'm going to go ahead and create a new branch with the name 14-agent-page.
I'm going to stage all changes once I am on the new page here. I will add a commit message and I will publish the branch. And now let's go ahead and open a pull request. And let's see what our code reviewer has to say. And now let's read our code summary.
We introduced a detailed agent view page with loading and error handling states. We added a header component for the agent date detail view, featuring breadcrumb navigation and an action menu with edit and delete options. We enabled navigation to an agent's detail page by clicking on a row in the agents list. We also improve data security by ensuring only agents owned by the current user can be accessed, so this is referring to us modifying the get one procedure. In here we have a more in-depth walkthrough.
We have a short sequence diagram explaining how when the user clicks on an agent row we access the agent view. We use router push agents agent ID to get to the agent detail page. In here we load the agent detail page using a React server component and then we basically fetch the agent data later on but we have it in the cache so it's much faster and then we return agent data for error and depending on that we show either the agent details loading or the error UI and it also noticed a related PR here for our agents setup so it's pretty cool how it keeps context of all of our previous work. So it knows what we are actually developing. And in here, we have a request from them to verify that we know what we are doing because we set the model to false.
So you can see that when you add a comment it doesn't tell you that you made a mistake it just asks you to verify. So pretty nice noticing of the comment here. And of course it tells us to add a proper comment here to implement this and not just leave it like that and that's exactly what we will do in the next chapter. So let's go ahead and merge this pull request and once you've done that go ahead inside of your main and synchronize the changes. After that go inside of your source control, open the graph, and confirm that you have just merged 14 agent page.
Perfect! Amazing, amazing job, and see you in the next chapter.