In this chapter, we're going to develop the messages UI. This will include creating the project view, the messages container, message card, and the message form components. And for the API changes, we're going to have to slightly modify the get many procedures of our messages. So before we do that, let's go ahead and ensure that we are on the main branch, and you can click Synchronize Changes just to make sure everything is up to date. And in your source control, your last merge should be 09 projects.
So I'm gonna go ahead and go inside of source, inside of modules, messages, procedures. And in the get many let's add the ability to add a project ID. So I'm just going to copy the input from the below create procedure. And I'm going to add it here and I'm going to delete the value because it's not required here. Only project ID is required.
And once we have the project ID, we can extend this to add a where. And let's go ahead and add project ID to be input project ID. Now let's go ahead and actually the structure the input from here so we can use it properly just like this. Perfect. So now we can load messages for an individual project.
Let's go ahead and let's do that. So now I'm going to go instead of source, app, projects, project ID, page.tsx. And since this is a server component, what we are going to do is we are going to leverage prefetching. So I'm going to go ahead and do const queryClient. And I will do await getQueryClient from trpcServer.
And this is not a promise, so we don't need a wait here. You can usually see that if you type an await on something that does not need a wait, you will see little three dots here, which will tell you that it has no effect on this. But you can also see that when hovering on something, you will see that there is no promise wrapping this. For example, when I hover over params, you can see that there's a promise of wrapping this. So await makes sense, right?
In here, nothing would happen if I used await, but we don't have to use await. And let's now add a void trpc, which you can import from the trpc server, same as getQueryClient. And let's go ahead and actually do void query client dot prefetch query drpc dot messages get many query options and pass in the project ID which we destructure from right here. Perfect. So now what I want to do is I also want to add inside of my modules, projects, server procedures, I want to add a get one like this.
And I want to add an input here. And I want to call this ID z dot string with a minimum value of one and a message ID is required. So should you call this ID or should you call this project ID? Well, since this is regarding fetching a single project, in my opinion, it is kind of redundant to call the property project ID here. And let me just see what I did incorrectly here.
So this is not how you open this, you should add z.object and then wrap this in parentheses, like this. This is the input. And then in here, you can go ahead and import extract this input, and you would find existing project here to be await Prisma project, find unique like this. And you would just do where id is equal to input id. And then go ahead and return the existing project.
And you can add if there is no existing project, throw new trpc error, which you can import from trpc server and the cool thing about this is that you have strictly typed codes so in this case this would be not found and then we can specify our message which can be project not found great so now we have a procedure to fetch an individual project by its unique ID. So we can leverage the find unique, which uses the index ID. Now let's go back inside of the page here and let's also prefetch for that. So trpc projects get1 and instead of project ID we are using the ID field. Because just think of it, when we are fetching messages, it makes sense that the prop is projectId, because it's referring to an entirely new entity.
But when we are fetching projects, we already know that id is referring to the projectId. That's why in my case it makes no sense to call this project ID. We already know it's a project. At least that's kind of my idea of naming a convention here. In here, basically, we are doing this, just in case you were confused.
But yeah, you can do a shorthand operator if the key and the value are named the same. So now we are prefetching these two, which means that we can now create our ProjectView component. So I'm going to do that by going inside of modules, Projects, and I will create a new folder called UI. And inside of here, I will create views and then inside of here I will create project-view.tsx and I will mark this as use client and I will export const project view. And in here I will create an interface props project id and I will call this a string.
In here you could also technically use id since we know what it's referring to, but I originally built the project using this, so I just don't want to alter the source code. And now in here, we are going to rely on getting the data from useSuspenseQuery and from using const trpc, useTRPC, like this. From use suspense query and from using const trpc use trpc like this trpc dot projects and here I have it get one query options and passing the ID to be project ID and let's go ahead and remap this to project then let's copy this and let's change this to messages get many and this will use the project ID key and we are going to remap this to messages and now in here we can return a div project JSON stringify project and then below JSON stringify messages null to just like that Just make sure you've marked this as use client. And now instead of the page here, what you can do is you can change this to be hydration boundary, which you can import from tanstack react query. You can pass the state here to be dehydrate again from tanstack react query and simply pass in the query client.
Then inside of here, render the project view component and pass in the project ID to be project ID. Just like that. And there actually is another reason why I don't want to use ID here simply because ID is reserved for HTML elements, right? You often see things like form ID and then something. So because of that, I want to explicitly use project ID here.
And let's go ahead and wrap this inside of suspense, which you can import from React. And let's give it a fallback of loading. Like this. There we go. Perfect.
So now, if you have your app running, And if you go to localhost 3000, and if you create a new project here, build a yellow landing page and click submit. The project ID was just loading for a second and as you can see the first thing we have is for me it's freezing tent. That's the random name that we generated. And then immediately below that, I mean, after that, we can see an array of messages. The first one is build a yellow landing page by the user.
And in a couple of seconds, we will get another message, which will basically be the response. And here we have it, the task summary. It created a landing page, blah, blah, blah. Perfect. So this works just fine.
And it leverages prefetching in the server components just be careful that your query options are exactly the same in the prefetch as they are in the useSuspense query, so they need to be identical. So make sure you didn't accidentally mess them up. Now I'm going to add some resizable panels inside of this project view. You already have this installed when we added all ShatCN components. So you can import all of these from components UI resizable.
So you can ctrl click to confirm that you have it. It is inside of source components UI resizable. And now let's go ahead and actually build our resizable panels. So I'm going to give this div here a class name of height screen. And then going to add a resizable not handle panel group.
And I'm going to wrap these two elements inside. I will give this a direction of horizontal. And I will then add a resizable panel. And let's go ahead and wrap the project in one resizable panel, and then another one for the messages like this. Let's go ahead and give this one a default.
Let's actually collapse this default size will be 35 minimum size will be 20. And let's give it a class name of Flex, flex-column, and a minimum height of 0. And now, let's go ahead and let's do the following in between these two resizable panels add a resizable handle and add width handle like this and for this resizable panel give it a default size of 65 and the minimum size of 50, like this. So now you should have this type of resizable panel and you can already see how this is going to look. In here, we're going to have our messages and in here we will have the project preview.
Right now, it is the opposite, but I just wanted to use it as an example. So that's how we're going to do that. And by default, you can see it has what I think is a kind of fair ratio, this size for messages, this size for the preview. You can, of course, change the default size to whatever you like. But you know, just make the total number of these two panels needs to add up to 100.
So you know, just make sure you are using the proper calculations. Great! So now that we have this, let's go ahead and let's develop this side of the resizable panel. So I'm just going to change this to be to do preview and this here will be our messages container. So right now we have an error because messages container does not exist yet.
So now let's go ahead and do the following. I'm gonna still say inside of projects, inside of UI, and I will create components. Now you're probably wondering why am I creating a message container, messages container, .tsx, inside of the projects module when I clearly have the messages module right here? Well, it's not the name that decides where you put something in module-based architecture. It is its purpose.
And this specific messages-container purpose will only be used inside of the project ID page, right? So this project's project ID page is obviously the project module. So just because we are rendering a component called messages here, messages container doesn't mean that it belongs in the message module, right? But something that's reusable, like the message API, that belongs in the message module. But messages container is just a container to render messages in the project.
So that's why this is the place I'm putting it in. The name doesn't matter. I can call this project message container. Maybe that would be a bit more visually attractive. But just to explain why I'm putting that here.
Now let's go ahead and let's build the messages container. So we are actually going to do the following messages container and then I'm going to copy a couple of things specifically this because this is where I will load the messages. If you can, it will always be better to load the messages, to use UseSuspenseQuery in a deeper component. Because the deeper component you use it in, the faster the page will load. And I'm going to show you why in a second.
So let's call this useTRPC from TRPC client, like this. And in here we need an interface, which I can just copy from the project view here. And Let's go ahead and destructure the props and get the project ID like this. And now we have the messages here so let's just return a div with json.stringify messages. There we go.
And now we can import the messages container here from components messages container and we can remove the user spans query for messages like this and pass in the project ID here to be project ID. And now what's important is that you wrap this instead of suspense as well. And give this a fallback of loading messages. Like this. So now the cool thing that's happening, I don't know if you will now see this, and yes, so let me try and demonstrate.
Yeah, it's kind of hard to do right now, perhaps, because I don't need this. What if I comment this out? Yes, you can see that when you comment this out in the project view, the page loads much quicker. That is because If we are using a useSuspense query inside of the project view, then it means that this suspense will fire, and that blocks the entire page. You can see that while that big loading is active, let me just write a loading project.
So while this loading project text is visible, the entire page is blocked. But if you move the suspense in a deeper component, like the message container, like we just did with loading the messages here, and wrap that inside of suspense. So let me now simulate by commenting this out. You can see that we are not blocking the entire view, only the messages view. So that's why I told you that it will be faster.
It's not really faster, it is just visually faster. So we are going to do the same thing for loading the project. So yes for now we can actually remove this because we will not be loading the project inside of the project view. Let me just move the Suspense right here. Perfect.
So now let's go inside of the message container and let's develop it. I'm going to start by giving the most outer div a class name of flex flex-column flex-1 and a minimum height of 0. I'm then going to add another div with a class name, flex1, minimum height of zero, and overflow y auto. And then inside of here, another div with a class name, padding top, padding top two, and padding right of one. And then finally, inside of here, I will go over my messages.
I will get the individual message here and I will render a new component so in here we're going to render message card component and you can remove the JSON stringify here. Now let's give this a key of Message.id. Let's give it content of Message.content. Role of Message.role. Fragment of Message.fragment.
And now we have a problem. Fragment is not loaded here. So let's go ahead and fix that by going inside of messages get many procedure it's inside of modules messages server procedures and simply do what we did in the previous chapter. Add include, make sure you're doing this instead of get many. Add include fragment through, like this.
And now let's go back instead of the messages container here. And as you can see now, we no longer have that problem, right? So now message.fragment exists. Let's add createdAt here to be message.createdAt. And let's just do isActiveFragment for now to be hardcoded to false.
OnFragment.click will be an empty arrow function and type will be message dot type. Now let's go ahead inside of the components and create the message card. Again we're doing this inside of the projects module because even though these components are called message, they relate more to the project entity than they do to the message entity. And instead of the message card component we are now going to do the following. First let's create the props.
Content which is a string. Role which is a type of message role you can import from at generated prisma. So this is the generated folder of Prisma, which you can find in your source folder. And you can see that you don't really touch this folder. You don't modify this folder because it is automatically generated every time you do npx-prisma-generate or npxprisma-migrate-dev, which in background runs npxprisma-generate.
You can always do npxprisma-generate yourself. This will simply update the entire Prisma. So in case yours didn't exist, now it will exist. So message role was directly generated from our schema message role. So if yours is called something else, you're gonna have to import something else.
Same thing for fragment from message here and same thing for message type. So basically content, role, fragment which can be null, created at, is active fragment which is a boolean, on fragment click which accepts the fragment as the value and type which is a message type. Now let's go ahead and let's export the message card here and let's assign all of those prompts from above and let's destructure them all here. Perfect. Now inside of this, Let's go ahead and do the following.
If role is equal to assistant, we're going to return a paragraph assistant. Otherwise, we're going to return a paragraph user. And let me just fix my typo here. So we have this. I don't think I need this.
There we go. Like this. And it's OK that all of these things are unused. Now let's go back to the messages container and import messageCard from .slash messageCard. Let me just separate my imports here.
No need for useClient in this component simply because the project view where it's rendered is already useClient, so its children will be as well. And as you can see, I have two messages. The first one is from the assistant and the other one is from the user. And I think that in this case, we would actually need the opposite to happen. So let's go inside of the messages container, go inside of messages.getMany and change the order by to be ascending.
So the first one should be from the user and the second one should be from the assistant. Now let's go inside of the message card and let's actually develop this. So let's do the user one first because I believe it is a little bit easier. So we're going to do user message here. And it will have one prop, which is content.
So let's pass in content here. And we're going to develop this just above this. Const user message. And let's create an interface user message props like this. And then just extract the props here.
It's just content. And in here return a div with a class name flex-justify-end padding-bottom-of-4 pr-of-2 pl-of-10. And in here, add a card from components UI card. You already have this as well. It comes with chat CN UI.
You can find it in source components UI card. Now instead of the card, render the content and give the card a class name of rounded, large, background, muted, padding, three, shadow, none, border, none, maximum width of 80%, and break words. Like this. So The user message will be rendered every time the user sends a message. And we should be able to see that now.
Build a yellow landing page. That was my first message, and you can see how my message is moved into this corner. We are now going to render the assistance output. So in order to do that, we will render the assistant message like this. Assistant message.
And the assistant will have some different props. So we're going to pass the content to be content, fragment to be fragment. It will have created at, it will have is active fragment, and it will have on fragment click and it will have a type. Basically, all the other props are related to the assistant message. So now let's go below the user message, let's create an interface assistant message and in here we can just add all of those props content fragment which can be a type of fragment or null created at which is date is active fragment which is boolean on fragment click and type.
I'm not sure but maybe these are identical to message card props. It doesn't have a role so yeah one less prop. I'm not sure if this is the best way to do this but you know I think it's fine. Now let's go ahead and actually do const assistant message like this. Let's destructure assistant message props.
Oops, yeah, I should call this props. Yes, like this. And then Inside let's just add all of those things. Content fragment created at is active on fragment click and type. And inside of here we are going to do the following.
Let's add a div with a dynamic class name, which means open curly brackets and import CN from libutils. If you don't remember this, but we got this when we installed a ShatCNUI, and I told you we are going to use this when we need some dynamic classes, and this is the first time we need that. So the way you use this library is very simple. You open it up as a function. It can accept an infinite number of parameters.
So the first parameter, the second parameter, the third, infinite number. What I like to do is I like to reserve the first one for my static class names. So flex, flexColumn, group, keyX, 2, and paddingBottom of 4. And then in the second argument, I like to do dynamic ones. If type is equal to error, I'm going to render it differently.
I'm going to render text red 700 and on dark mode text red 500 like this. And then inside of here, I'm going to add a div with a class name of flex item center, gap to PL, two, and margin bottom of two. Now I'm going to add to do add logo, because we don't have it yet. And I'm going to add an image component here. Actually, we can do that only when we have the logo.
So let's add a span for now. And our app name, in my case, this will be vibe, text small and font medium. Like this, then copy this span. And in here, you're going to need to install and install date FNS. This will be used to parse dates and let me show you my package JSON date FNS 4.1.0.
And I'm going fns 4.1.0 and I'm going to import something from date fns so import format from date fns like this and inside of here I'm going to format created at like this And I will format in this format. Like this. And then I'm going to slightly modify this to be text extra small and text muted foreground. And then I'm going to give it an opacity of zero. And I'm going to give it a transition opacity.
And since I have given this outer parent div a group class name, I can leverage that by doing the following. I can do group colon, my apologies, group dash hover. So when the group is hovered, change the opacity to 100. Like this. And that's how I'm going to make this appear when we hover on the parent element.
Perfect. And then outside of this div, let's go ahead and let's actually render the content. So div class name PL 8.5 flex, flex column and gap y of 4. And inside of here, a span with content inside. And let's go ahead and make sure we are using the assistant message.
We are, perfect. And there we go. You can see build a yellow landing page and then the vibe answers at this time, which only appears when I hover with a task summary like this. Perfect. So now let's go ahead and continue developing this.
And let me just see. So in here we have flex item center gap to PL to margin bottom of two. Okay, I think I think this is okay. I'm just this, this spacing seems a little bit odd. I'm not sure this is how it's supposed to be.
But yeah, go ahead and try and collapse your page a bit. It should work fine. It should normally break words. It shouldn't add any scroll bars except the the one from up down, right? That one should appear.
But no one on the x-axis should not happen. Great. So now let's go ahead and let's obtain our app logo. So head to the assets page you can see the link on the screen or you can use the link in the description and in here you can find logo.svg. I found this logo from LogoIpsum.
So these are amazing placeholder logos you can use for your projects and I use them in pretty much every project. They are amazing. So I slightly modify them to match the color scheme of the project. You can download them or you can copy the SVG since the code is in SVG. And you can then go inside of your project.
And what I like to do is go inside of public, create a new logo.svg here. And then I click this, open file using VS Code standard text binary and I paste it inside and save it and that creates the logo or you can just download it as a file normally without all that trouble. So now let's go ahead and let's add our logo to our message card, specifically in the assistant message. I added a to-do here. Now let's add an image here from next image.
So make sure you have added this import here. And then we're going to add the following. Source will be forward slash logo.svg alt will be vibe. Width will be 18. Height will be 18.
And class name will be shrink Zero. And let's go ahead and try again. And there we go. So now this space makes more sense because the logo perfectly pushes the text to be aligned with the content right here. Amazing.
And don't worry about this task summary tag, we will get rid of that later using something else. But this is basically how our chat will look like. And if you're wondering, the colors don't look exactly as your demo, don't worry, we're going to change the entire theme of the project later. But this is what I wanted to achieve. So now what I want to do is I also want to add a little message on the bottom here.
I mean a little form on the bottom. But just before I do that, I also want to create a fragment component. So after we render the span content, let's check if we have the fragment and if type is equal to result. Only then are we going to render the fragment card. The fragment card will accept three props.
The fragment itself is active fragment and on fragment click. And we can create the fragment just above here. So first, the props, fragment card props, fragment is active fragment, and on fragment click. And then the fragment card component. So let's just use the props and extract them here.
And then inside of here, we're going to return a button, but a normal HTML button like this, we're going to give it a dynamic class name using the CN library. In the first argument, I will add flex items start, text start, gap to border, rounded, large, background muted, width, bit, padding, three, power, bg secondary, background muted, width bit padding 3, hover BG secondary and transition colors. And then I'm going to check if is active fragment I will do the following background primary text primary foreground border primary and hover BG primary like this and on click here I will call on fragment click and pass the fragment as the prop. Inside of the button itself, I will add code to icon. So from Lucid React, Let me just fix this invalid fragment end here.
I don't need this. There we go. The code to icon will have a class name of size 4 and margin top of 0.5. I will then open a div with a class name flex, flex column, and flex one. And inside of here, I will have a span which will render the fragment title and the class name, text small, font medium and line clamp one.
Below this another span with a class name of text small and the text preview. I think we should already start to see this because this message from the AI assistant has the fragment and it is not an error. So we can see it right here. Make sure that you are doing this on a successful response. So you have the fragment generated in your database.
If you are unsure, if you still can't see it, npx prisma studio to show you what I'm talking about. So your message, whatever one you're doing, should have a fragment. You can see how some of my messages don't have fragments because they are by user or they are errors. But the ones that are successful have a fragment, right? So that's what you need to do.
You basically need to create a background job with a successful generation, something that has a fragment. So now after the preview here, outside of this div, I'm going to add another div with a Chevron right icon from Lucid React with a class name of size 4. So the same import place as code to icon. And let's go ahead and give this a class name. FlexItemsCenterJustifyCenter and margin top of 0.5.
And I think that marks the end of the message card component. I think we have everything we need now. The only thing I don't like is that this doesn't have the pointer cursor. It doesn't look clickable, but you don't have to fix that by adding the pointer to, this because this is already a button so what we are going to do is we are going to change the global CSS so that it shows the pointer when this is hovered like this I mean not this one but you get the idea right perfect So now what we can do is we can create the form here at the bottom and that will complete the message container So let's go ahead and go inside of the components and let's create the message dash form Dot TSX. So this will be rendered at the bottom of the message container.
Let's go ahead and just copy the props from the previous components. And let's export message form. Inside of here, go ahead and assign the props and the structure the project ID and return a div message form. And now let's go inside of the messages container. And now we have to render this.
So I'm going to render it. After the last div here, I'm going to open a new one with a class name, relative padding three, pt one, and then I'm going to add message form and I'm going to pass in the project ID project ID like this so make sure you have added this import and now at the bottom you will see message form In order to complete the message form component, we're going to have to install a new package. React-textarea-autosize. So go ahead and install this and I'm going to show you the version. So package.json 8.5.9.
That is my version. And now let's go inside of the message form and we're going to need a couple of things from react-hook-form So use form and then we're going to need Zod resolver from hookform resolvers zod. And if you're worried, where do these packages come from? We already have them. Hookform and form, react-hookform.
So all of this already exists and they came with Shadcny when we added all components. And the new one is this one. TextArea.AutoSize from React.TextArea.AutoSize. And besides this, Let's just see what else do we need. Let's also add useState from React.
Like this. Let's also add Zod. And let's add post from Sonar. And let's also add some icons. So that's going to be arrow up icon and loader to icon from Lucid React and from 10 stack query we need use mutation use query and use query client from 10 stack react query then let's add CN from libutils use trpc from at trpc client the button component, and form and form field from components UI form.
This is another chat CN UI component and when you installed that which you did using the dash dash all command you also got use form and you got the Zod resolver and also Zod and that is it for now so now let's define form schema here to be Z dot object And what you should actually do is you should visit one of your procedures in messages, specifically find the create procedure And you should copy the value from here. So you have the limit. So like this. Now, how you're going to call this value string, I really don't know. So you can do value, you can do content, whatever you want.
And let's go ahead and do the following now that we have this form schema. Const form useForm() passing z.infer type of form schema like this. And add resolver here to be ZodResolver and pass in the form schema object. And the default values will set the value to be an empty string by default. Great, now that we have the form, let's build the UI.
So, the outer div will be the form element from here, from components UI form. And we have to pass the entire object that we created here using use form. And then inside we need a native HTML form element like this. And in here we need the following. We need onSubmit to be form handleSubmit and then we have to create a custom submit form.
So const onSubmit here will accept the values which are basically this, so you can copy this from above. And for now just console log the values. The reason we are doing this infer is because when you hover over you can see that it is exactly what you define here. So now use that on submit and pass it here. So now this on submit will only trigger this which will actually initialize the network call when the validation passes.
So that's why we are wrapping it inside of here. Perfect. And now let's go ahead and do a class name here. CN, relative, border, padding four, padding top one, Rounded Extra Large, Background Sidebar, Dark, BG, Sidebar, and Transition All, like this. And then if isFocus, which doesn't exist yet, we were going to do shadow extra small.
And for showUsage, we're going to do a rounded top none. So now let's go ahead and just quickly fix these things. So for isFocused, it is an easy fix. All we are going to do is add a new useState here with isFocused and setIsFocused with the default value of false from useState react. And for the showUsage I'm going to manually set it to false for now.
So now you should have no errors here. And periodically you can check on this just to see how it looks. Great, now that we have this let's go ahead and add the form field component which is a self-closing tag. Just make sure you have imported it. Give this a control of form.control, give it a name of content, and give it a render of field.
Like this. And inside, use the text area auto-size self-closing component. In here, you can immediately spread everything you have from the field above and then go ahead and give it the following and give it an on focus and on blur to modify the set is focused state like this and the name should be value my apologies so already when you hover over this I'm not sure if you can notice but that's there's an ever so slight shadow change to the entire object. Now we have to fix this so it doesn't look so weird. So let's go ahead and give this a minimum rows of 2 and the maximum rows of 8 and then a class name, padding top 4, resize none, border none, width full, outline none, background transparent and a placeholder of what would you like to build.
And then let's go ahead and do onKeyDown, get the event and check if event.key is equal to enter and open parenthesis. We are also holding Control key or Meta key. So this will basically be Control, Enter. We prevent the default And we do form handle submit, on submit, whoops, on submit, and pass the event as well. So the on submit is this, just like that.
So now, Outside of this, which is form field, I believe, yes, outside of form field, but still inside of the form. Let's go ahead and do the following. Let's add a div with a class name flex-gap-x2-items-end, justify-between-padding-top-of-2, Then another div with a class name text 10 pixels, text muted foreground and font mono. And just write test here simply so you see where that is so it's right here at the bottom so this will now be the following it will be a keyboard sign I think this is for keyboard the the short name for keyboard Render a span inside and render the following sign like this and then enter. And that will turn like this.
The command sign and enter now let's style it the class name will be ML auto pointer events none inline flex height 5, select, none, items, center, gap 1, rounded, just rounded, border, background color, muted, px 1.5, font, mono, text 10 pixels, font medium and text muted foreground. And then let's go ahead outside of the KBD and let's do nbsp to submit. So basically command enter to submit. We are telling the user how to submit. And now outside of this div add a button element.
And this button element will do the following. It will render arrow up icon, which we already have imported from Lucid React. There we go and now we are going to style it. Give it a class name of CN size 8 and rounded pool like this. There we go.
This is how it's going to look like. And now we need to add some dynamic things here. So let's start by adding our create message mutation. So we need to add TRPC here, use TRPC. And then we need to add create message from use mutation, TRPC messages, create mutation options like this.
And then you can extract the following. You can then extract const is pending to be create message dot is pending. Const is disabled to be is pending, const isPending to be createMessage.isPending, const isDisabled to be isPending, or if not form, formState is valid. So if formState is not valid, like this. And let me actually move these two to the bottom here simply so I have all of these things in one place.
And now that we have the create message mutation, let's go inside of the on submit and let's make it an asynchronous method and let's do await create message dot mutate async and passing the value to be data actually this is values so values dot value and the project id Like that. Perfect. And now let's use the isBending and let's use the isDisabled. So first things first to the text area auto size disabled if is pending like that and then let's go ahead down to this button And the button will be a little bit different. So this one will be disabled if isDisabled.
So be careful. For the text area auto-size, we only disabled if it's pending. So only if the network request is pending. But disabled will be for this. So you can do isButtonDisabled just so you don't make a mistake.
There we go. And let's also do if isButtonDisabled background, muted foreground and border like this. And then inside of here a ternary if is pending. In that case, we are rendering the loader to icon, which we already have imported with the class name of size for an animate spin. Otherwise, we render the arrow up icon like this.
There we go. So now, make sure that you restart your server here. Actually, I will restart the entire project as well. So npm run dev, npx ingest dev. I will refresh this page here.
And I'm going to add build a blue landing page. And I will press Command Enter. And there we go, you can see that that has submitted this for a second, it was loading, we still have to do the cleanup function. But if I look in my InGIS developer server, you can see that this is successfully running. Amazing.
And if I refresh here, I should actually see my new message here. Build a blue landing page. Perfect. So now let's go ahead and just add some on success things to happen in the mutation options of the create message, right? So what should happen after we submit?
So the first thing that should happen is on success here. Once we get the data of this new message, Let's go ahead and let's first do form.reset, like this. So make sure that form is initialized above. And then let's do query client, which I'm not sure do we have it, we don't. So Let me just add const query client to be user query client.
So you have this imported from 10 stack React query. So in here, what you're going to do is query client.invalidateQueries and then pass in trpc messages, getMany, queryOptions, projectId, data, projectId. Or you can use the projectId from here. Yeah, maybe that's even easier to do. And then you can use the shorthand operator.
That's the first thing we are going to invalidate. Then the second thing we don't have yet. So I will add it to do re invalidate or invalidate usage status. We don't have this yet, but we will have it later. And now add on error here, get the error and do toast.error, error.message.
And I will add a to-do, redirect to pricing page, if specific error. There we go. And the only thing I don't have left here is the use query. And I will remove it for now because we don't really have the entity we need to call. And I think that for now, this is it.
I think for now, this is everything we can do here. And there we go, we have a response now. Created a fully responsive production quality blue themed landing page. Perfect. So now what I want to do is just to end this chapter, one more thing here.
I don't like how the first thing is when I load the page you can see I have to scroll all the way down and the second thing is when I scroll the text visibly clips here you can see how it's cut so let's fix those two things and let's end the chapter Both of these things will be in the messages container. So make sure that you have some messages and you can zoom in a little so you have the scroll bar like I do. The first thing will be a very simple self-closing div just above the place where we rendered the message form inside of this relative div. And give it a class name of Absolute minus top minus 6. Left 0, right 0, height of 6, 0, height of 6, background gradient to bottom, from transparent to 2-background, forward slash 70, pointer events none.
What this will do is it will create an ever so slightly white shadow. I'm not sure if you can see it, but it kind of melts the overflow. So it doesn't look as obvious that the text is clipping here. If you want to, you can improve this and change this to two background, and then you can see the clipping at all. It's like it fades into some kind of fog, right?
So just a slight effect to make this look better. So it doesn't clip. Now let's do the thing that when we load, we scroll to the bottom here. So in order to do that, we first have to add a bottom ref. So let's do that here.
Const bottomRef will be useRef from React with a default value of null and the type of HTML div element like this. Let me just move this to the top like that And then what we are going to do is we are going to change this to be use effect which you can import from react and let's first do the following. Const lastAssistantMessage and do data.findLast my apologies, messages.findLast search through the messages and find the message whose role is assistant. And that's how we are going to find the last message that the Assistant sends. So make sure you're using the find last API here.
And if we are able to find this last Assistant message, what we are going to do first is we are going to set the fragment to that assistant message. Now, we don't have this yet. So actually, I'm not sure if we can do that. So let me just do to do. And let's do set active fragment.
So we're going to do this. Well, maybe in this chapter, maybe in the future, I will see. But let's add messages for now, like this. And then let's go ahead and add another useEffect. And in here, we will do messages.length.
And we're going to check if bottomRef.current? Scroll into view, like this. And let me just check. I think that for now, this is okay. If I do a refresh here, it looks like it's not working.
So it should be scrolling me to the bottom but it is not probably because I never added that so let's go ahead outside of here add a self-closing div and give it a ref of bottom ref. So now when you refresh, there we go. You can see how you scroll down immediately. Perfect. So now, Yes, I think I'm going to end the chapter here simply because it's already been an hour.
So we're going to end here and in the next chapter we're going to wrap this up by adding isActiveFragment functionality. We're going to add some loading states while we wait for the response and we're also going to add the header here so that we can click the back button to go back to the landing page and so we can access some settings here and see the project name. Great! So we've already made some great progress here and you can add something like this if you want to see the error state. And now you can see how the error state looks like when you send it something that it cannot generate.
It will simply tell you something went wrong and it highlights the red color. Perfect. So I'm very, very satisfied with this. So we've done this, we've done this, this, this, and even more than this. Now let's go ahead and open a new branch and merge this.
So 10 messages UI. I'm going to open this. I'm going to create a new branch. 10 messages UI. I'm going to stage all of my changes and I will do 10 messages QI and I will commit and I will publish the branch.
A quick reminder that there is a free CodeRabbit extension which you can use to improve your code quality. And now I'm going to go and open this pull request here. And we're going to review the summary of this chapter and everything we did. And here we have the CodeRabbit summary. We introduced a chat interface for project pages, including a message list, message input form, and support for assistant and user messages with styled cards.
Added support for displaying message fragments and interactive fragment cards. We implemented a horizontally resizable panel layout with a dedicated area for future preview features. That is exactly what was the point and goal of this chapter. And may I say we did a pretty good job Because no comments, only some nitpicking comments like we can save some time by doing project ID instead of project ID equals project ID. So overall, amazing, amazing job.
In here of course we have an in-depth diagram explaining exactly how everything in this page happens including pre-fetching, including invalidation, including refetching, everything. Amazing, amazing job. I'm going to merge this pull request. Once the pull request is merged, I'm going to go back inside of my IDE and I will go back to the main branch. After that, I'm going to synchronize my changes and I will check the source control and the graph, so I can see that I successfully merged chapter 10.
That marks the end of this chapter I believe. Amazing, amazing job and see you